From 70b7df0e5e5cce5a3561fa5a6a8abd4ebc902e68 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Wed, 2 Oct 2024 04:25:08 +0900 Subject: [PATCH 01/17] Support repo license (#24872) Close #278 Close #24076 ## Solutions: - Use [google/licenseclassifier](https://github.com/google/licenseclassifier/) Test result between [google/licensecheck](https://github.com/google/licensecheck) and [go-license-detector](https://github.com/go-enry/go-license-detector): https://github.com/go-gitea/gitea/pull/24872#issuecomment-1560361167 Test result between [google/licensecheck](https://github.com/google/licensecheck) and [google/licenseclassifier](https://github.com/google/licenseclassifier/): https://github.com/go-gitea/gitea/pull/24872#issuecomment-1576092178 - Generate License Convert Name List to avoid import license templates with same contents Gitea automatically get latest license data from[ spdx/license-list-data](https://github.com/spdx/license-list-data). But unfortunately, some license templates have same contents. #20915 [click here to see the list](https://github.com/go-gitea/gitea/pull/24872#issuecomment-1584141684) So we will generate a list of these license templates with same contents and create a new file to save the result when using `make generate-license`. (Need to decide the save path) - Save License info into a new table `repo_license` Can easily support searching repo by license in the future. ## Screen shot Single License:  Multiple Licenses:  Triggers: - [x] Push commit to default branch - [x] Create repo - [x] Mirror repo - [x] When Default Branch is changed, licenses should be updated Todo: - [x] Save Licenses info in to DB when there's a change to license file in the commit - [x] DB Migration - [x] A nominal test? - [x] Select which library to use(https://github.com/go-gitea/gitea/pull/24872#issuecomment-1560361167) - [x] API Support - [x] Add repo license table - ~Select license in settings if there are several licenses(Not recommended)~ - License board(later, not in this PR)  --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Denys Konovalov <kontakt@denyskon.de> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: KN4CK3R <admin@oldschoolhack.me> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: 6543 <m.huber@kithara.com> Co-authored-by: a1012112796 <1012112796@qq.com> Co-authored-by: techknowlogick <techknowlogick@gitea.com> --- assets/go-licenses.json | 5 + build/generate-licenses.go | 64 +++++- build/license/aliasgenerator.go | 41 ++++ build/license/aliasgenerator_test.go | 39 ++++ go.mod | 1 + go.sum | 3 + models/fixtures/repo_license.yml | 1 + models/fixtures/repository.yml | 2 +- models/migrations/migrations.go | 4 +- models/migrations/v1_23/v305.go | 23 ++ models/repo/license.go | 120 ++++++++++ modules/repository/license.go | 2 +- modules/structs/repo.go | 1 + options/license/etc/license-aliases.json | 1 + options/locale/locale_en-US.ini | 2 + routers/api/v1/api.go | 1 + routers/api/v1/repo/license.go | 51 +++++ routers/api/v1/repo/repo.go | 11 + routers/api/v1/swagger/repo.go | 7 + routers/init.go | 2 + routers/private/default_branch.go | 11 + routers/private/hook_post_receive.go | 17 +- routers/web/repo/branch.go | 2 +- routers/web/repo/commit.go | 10 +- routers/web/repo/release.go | 1 - routers/web/repo/view.go | 2 + services/context/repo.go | 7 + services/convert/repository.go | 6 + services/cron/tasks_basic.go | 11 + services/migrations/gitea_uploader_test.go | 3 + services/mirror/mirror_pull.go | 9 + services/repository/branch.go | 8 + services/repository/create.go | 19 ++ services/repository/delete.go | 1 + services/repository/fork.go | 3 + services/repository/license.go | 205 ++++++++++++++++++ services/repository/license_test.go | 73 +++++++ services/repository/migrate.go | 5 + services/repository/repository.go | 7 + templates/repo/sub_menu.tmpl | 7 +- templates/swagger/v1_json.tmpl | 52 +++++ .../08/51b61d9f8ca0e9e63617e11907988ee88b1ca6 | Bin 0 -> 85 bytes .../12/8105ae73669ac2a4cb42751538f0c65c54e28a | Bin 0 -> 643 bytes .../90/c1019714259b24fb81711d4416ac0f18667dfa | 2 + .../user2/repo1.git/refs/heads/DefaultBranch | 2 +- tests/integration/api_admin_test.go | 4 +- tests/integration/api_repo_license_test.go | 80 +++++++ 47 files changed, 906 insertions(+), 22 deletions(-) create mode 100644 build/license/aliasgenerator.go create mode 100644 build/license/aliasgenerator_test.go create mode 100644 models/fixtures/repo_license.yml create mode 100644 models/migrations/v1_23/v305.go create mode 100644 models/repo/license.go create mode 100644 options/license/etc/license-aliases.json create mode 100644 routers/api/v1/repo/license.go create mode 100644 services/repository/license.go create mode 100644 services/repository/license_test.go create mode 100644 tests/gitea-repositories-meta/user2/repo1.git/objects/08/51b61d9f8ca0e9e63617e11907988ee88b1ca6 create mode 100644 tests/gitea-repositories-meta/user2/repo1.git/objects/12/8105ae73669ac2a4cb42751538f0c65c54e28a create mode 100644 tests/gitea-repositories-meta/user2/repo1.git/objects/90/c1019714259b24fb81711d4416ac0f18667dfa create mode 100644 tests/integration/api_repo_license_test.go diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 1c0711307fce..4b78a12030e3 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -664,6 +664,11 @@ "path": "github.com/google/go-tpm/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, + { + "name": "github.com/google/licenseclassifier/v2", + "path": "github.com/google/licenseclassifier/v2/LICENSE", + "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + }, { "name": "github.com/google/pprof/profile", "path": "github.com/google/pprof/profile/LICENSE", diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 9a111bc81115..66e1d3775515 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -1,3 +1,6 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + //go:build ignore package main @@ -5,6 +8,8 @@ package main import ( "archive/tar" "compress/gzip" + "crypto/md5" + "encoding/hex" "flag" "fmt" "io" @@ -15,6 +20,8 @@ import ( "path/filepath" "strings" + "code.gitea.io/gitea/build/license" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/util" ) @@ -77,7 +84,7 @@ func main() { } tr := tar.NewReader(gz) - + aliasesFiles := make(map[string][]string) for { hdr, err := tr.Next() @@ -97,26 +104,73 @@ func main() { continue } - if strings.HasPrefix(filepath.Base(hdr.Name), "README") { + fileBaseName := filepath.Base(hdr.Name) + licenseName := strings.TrimSuffix(fileBaseName, ".txt") + + if strings.HasPrefix(fileBaseName, "README") { continue } - if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") { + if strings.HasPrefix(fileBaseName, "deprecated_") { continue } - out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt"))) + out, err := os.Create(path.Join(destination, licenseName)) if err != nil { log.Fatalf("Failed to create new file. %s", err) } defer out.Close() - if _, err := io.Copy(out, tr); err != nil { + // some license files have same content, so we need to detect these files and create a convert map into a json file + // Later we use this convert map to avoid adding same license content with different license name + h := md5.New() + // calculate md5 and write file in the same time + r := io.TeeReader(tr, h) + if _, err := io.Copy(out, r); err != nil { log.Fatalf("Failed to write new file. %s", err) } else { fmt.Printf("Written %s\n", out.Name()) + + md5 := hex.EncodeToString(h.Sum(nil)) + aliasesFiles[md5] = append(aliasesFiles[md5], licenseName) } } + // generate convert license name map + licenseAliases := make(map[string]string) + for _, fileNames := range aliasesFiles { + if len(fileNames) > 1 { + licenseName := license.GetLicenseNameFromAliases(fileNames) + if licenseName == "" { + // license name should not be empty as expected + // if it is empty, we need to rewrite the logic of GetLicenseNameFromAliases + log.Fatalf("GetLicenseNameFromAliases: license name is empty") + } + for _, fileName := range fileNames { + licenseAliases[fileName] = licenseName + } + } + } + // save convert license name map to file + b, err := json.Marshal(licenseAliases) + if err != nil { + log.Fatalf("Failed to create json bytes. %s", err) + } + + licenseAliasesDestination := filepath.Join(destination, "etc", "license-aliases.json") + if err := os.MkdirAll(filepath.Dir(licenseAliasesDestination), 0o755); err != nil { + log.Fatalf("Failed to create directory for license aliases json file. %s", err) + } + + f, err := os.Create(licenseAliasesDestination) + if err != nil { + log.Fatalf("Failed to create license aliases json file. %s", err) + } + defer f.Close() + + if _, err = f.Write(b); err != nil { + log.Fatalf("Failed to write license aliases json file. %s", err) + } + fmt.Println("Done") } diff --git a/build/license/aliasgenerator.go b/build/license/aliasgenerator.go new file mode 100644 index 000000000000..7de1e6fbd6b5 --- /dev/null +++ b/build/license/aliasgenerator.go @@ -0,0 +1,41 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package license + +import "strings" + +func GetLicenseNameFromAliases(fnl []string) string { + if len(fnl) == 0 { + return "" + } + + shortestItem := func(list []string) string { + s := list[0] + for _, l := range list[1:] { + if len(l) < len(s) { + s = l + } + } + return s + } + allHasPrefix := func(list []string, s string) bool { + for _, l := range list { + if !strings.HasPrefix(l, s) { + return false + } + } + return true + } + + sl := shortestItem(fnl) + slv := strings.Split(sl, "-") + var result string + for i := len(slv); i >= 0; i-- { + result = strings.Join(slv[:i], "-") + if allHasPrefix(fnl, result) { + return result + } + } + return "" +} diff --git a/build/license/aliasgenerator_test.go b/build/license/aliasgenerator_test.go new file mode 100644 index 000000000000..239181b7365a --- /dev/null +++ b/build/license/aliasgenerator_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package license + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetLicenseNameFromAliases(t *testing.T) { + tests := []struct { + target string + inputs []string + }{ + { + // real case which you can find in license-aliases.json + target: "AGPL-1.0", + inputs: []string{ + "AGPL-1.0-only", + "AGPL-1.0-or-late", + }, + }, + { + target: "", + inputs: []string{ + "APSL-1.0", + "AGPL-1.0-only", + "AGPL-1.0-or-late", + }, + }, + } + + for _, tt := range tests { + result := GetLicenseNameFromAliases(tt.inputs) + assert.Equal(t, result, tt.target) + } +} diff --git a/go.mod b/go.mod index e620b8f70fcd..8dd694792528 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-github/v61 v61.0.0 + github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 diff --git a/go.sum b/go.sum index ee6e3c338237..aa592053b53e 100644 --- a/go.sum +++ b/go.sum @@ -441,6 +441,8 @@ github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA= +github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 h1:ASJ/LAqdCHOyMYI+dwNxn7Rd8FscNkMyTr1KZU1JI/M= github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= @@ -735,6 +737,7 @@ github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jN github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= diff --git a/models/fixtures/repo_license.yml b/models/fixtures/repo_license.yml new file mode 100644 index 000000000000..ca780a73aa0c --- /dev/null +++ b/models/fixtures/repo_license.yml @@ -0,0 +1 @@ +[] # empty diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 9adc6c855b1e..e141593f4157 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -26,7 +26,7 @@ fork_id: 0 is_template: false template_id: 0 - size: 7597 + size: 8478 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 13551423ce47..f99718ead285 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -500,7 +500,7 @@ var migrations = []Migration{ // v259 -> v260 NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens), - // Gitea 1.20.0 ends at 260 + // Gitea 1.20.0 ends at v260 // v260 -> v261 NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner), @@ -601,6 +601,8 @@ var migrations = []Migration{ NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn), // v304 -> v305 NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1), + // v305 -> v306 + NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_23/v305.go b/models/migrations/v1_23/v305.go new file mode 100644 index 000000000000..4d881192b277 --- /dev/null +++ b/models/migrations/v1_23/v305.go @@ -0,0 +1,23 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddRepositoryLicenses(x *xorm.Engine) error { + type RepoLicense struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) NOT NULL"` + CommitID string + License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"` + } + + return x.Sync(new(RepoLicense)) +} diff --git a/models/repo/license.go b/models/repo/license.go new file mode 100644 index 000000000000..366b4598cc4e --- /dev/null +++ b/models/repo/license.go @@ -0,0 +1,120 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" +) + +func init() { + db.RegisterModel(new(RepoLicense)) +} + +type RepoLicense struct { //revive:disable-line:exported + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) NOT NULL"` + CommitID string + License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"` +} + +// RepoLicenseList defines a list of repo licenses +type RepoLicenseList []*RepoLicense //revive:disable-line:exported + +func (rll RepoLicenseList) StringList() []string { + var licenses []string + for _, rl := range rll { + licenses = append(licenses, rl.License) + } + return licenses +} + +// GetRepoLicenses returns the license statistics for a repository +func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) { + licenses := make(RepoLicenseList, 0) + if err := db.GetEngine(ctx).Where("`repo_id` = ?", repo.ID).Asc("`license`").Find(&licenses); err != nil { + return nil, err + } + return licenses, nil +} + +// UpdateRepoLicenses updates the license statistics for repository +func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string, licenses []string) error { + oldLicenses, err := GetRepoLicenses(ctx, repo) + if err != nil { + return err + } + for _, license := range licenses { + upd := false + for _, o := range oldLicenses { + // Update already existing license + if o.License == license { + if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil { + return err + } + upd = true + break + } + } + // Insert new license + if !upd { + if err := db.Insert(ctx, &RepoLicense{ + RepoID: repo.ID, + CommitID: commitID, + License: license, + }); err != nil { + return err + } + } + } + // Delete old licenses + licenseToDelete := make([]int64, 0, len(oldLicenses)) + for _, o := range oldLicenses { + if o.CommitID != commitID { + licenseToDelete = append(licenseToDelete, o.ID) + } + } + if len(licenseToDelete) > 0 { + if _, err := db.GetEngine(ctx).In("`id`", licenseToDelete).Delete(&RepoLicense{}); err != nil { + return err + } + } + + return nil +} + +// CopyLicense Copy originalRepo license information to destRepo (use for forked repo) +func CopyLicense(ctx context.Context, originalRepo, destRepo *Repository) error { + repoLicenses, err := GetRepoLicenses(ctx, originalRepo) + if err != nil { + return err + } + if len(repoLicenses) > 0 { + newRepoLicenses := make(RepoLicenseList, 0, len(repoLicenses)) + + for _, rl := range repoLicenses { + newRepoLicense := &RepoLicense{ + RepoID: destRepo.ID, + CommitID: rl.CommitID, + License: rl.License, + } + newRepoLicenses = append(newRepoLicenses, newRepoLicense) + } + if err := db.Insert(ctx, &newRepoLicenses); err != nil { + return err + } + } + return nil +} + +// CleanRepoLicenses will remove all license record of the repo +func CleanRepoLicenses(ctx context.Context, repo *Repository) error { + return db.DeleteBeans(ctx, &RepoLicense{ + RepoID: repo.ID, + }) +} diff --git a/modules/repository/license.go b/modules/repository/license.go index 6ac3547e7b2f..9da3af84f852 100644 --- a/modules/repository/license.go +++ b/modules/repository/license.go @@ -23,7 +23,7 @@ type LicenseValues struct { func GetLicense(name string, values *LicenseValues) ([]byte, error) { data, err := options.License(name) if err != nil { - return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) + return nil, fmt.Errorf("GetLicense[%s]: %w", name, err) } return fillLicensePlaceholder(name, values, data), nil } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index fd27df384da2..832ffa8bcc95 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -114,6 +114,7 @@ type Repository struct { MirrorUpdated time.Time `json:"mirror_updated,omitempty"` RepoTransfer *RepoTransfer `json:"repo_transfer"` Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository diff --git a/options/license/etc/license-aliases.json b/options/license/etc/license-aliases.json new file mode 100644 index 000000000000..fe2cf2d58e40 --- /dev/null +++ b/options/license/etc/license-aliases.json @@ -0,0 +1 @@ +{"AGPL-1.0-only":"AGPL-1.0","AGPL-1.0-or-later":"AGPL-1.0","AGPL-3.0-only":"AGPL-3.0","AGPL-3.0-or-later":"AGPL-3.0","CAL-1.0":"CAL-1.0","CAL-1.0-Combined-Work-Exception":"CAL-1.0","GFDL-1.1-invariants-only":"GFDL-1.1","GFDL-1.1-invariants-or-later":"GFDL-1.1","GFDL-1.1-no-invariants-only":"GFDL-1.1","GFDL-1.1-no-invariants-or-later":"GFDL-1.1","GFDL-1.1-only":"GFDL-1.1","GFDL-1.1-or-later":"GFDL-1.1","GFDL-1.2-invariants-only":"GFDL-1.2","GFDL-1.2-invariants-or-later":"GFDL-1.2","GFDL-1.2-no-invariants-only":"GFDL-1.2","GFDL-1.2-no-invariants-or-later":"GFDL-1.2","GFDL-1.2-only":"GFDL-1.2","GFDL-1.2-or-later":"GFDL-1.2","GFDL-1.3-invariants-only":"GFDL-1.3","GFDL-1.3-invariants-or-later":"GFDL-1.3","GFDL-1.3-no-invariants-only":"GFDL-1.3","GFDL-1.3-no-invariants-or-later":"GFDL-1.3","GFDL-1.3-only":"GFDL-1.3","GFDL-1.3-or-later":"GFDL-1.3","GPL-1.0-only":"GPL-1.0","GPL-1.0-or-later":"GPL-1.0","GPL-2.0-only":"GPL-2.0","GPL-2.0-or-later":"GPL-2.0","GPL-3.0-only":"GPL-3.0","GPL-3.0-or-later":"GPL-3.0","LGPL-2.0-only":"LGPL-2.0","LGPL-2.0-or-later":"LGPL-2.0","LGPL-2.1-only":"LGPL-2.1","LGPL-2.1-or-later":"LGPL-2.1","LGPL-3.0-only":"LGPL-3.0","LGPL-3.0-or-later":"LGPL-3.0","MPL-2.0":"MPL-2.0","MPL-2.0-no-copyleft-exception":"MPL-2.0","OFL-1.0":"OFL-1.0","OFL-1.0-RFN":"OFL-1.0","OFL-1.0-no-RFN":"OFL-1.0","OFL-1.1":"OFL-1.1","OFL-1.1-RFN":"OFL-1.1","OFL-1.1-no-RFN":"OFL-1.1"} \ No newline at end of file diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f77fd203a2d6..e3b17f9a04f2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1040,6 +1040,7 @@ issue_labels_helper = Select an issue label set. license = License license_helper = Select a license file. license_helper_desc = A license governs what others can and can't do with your code. Not sure which one is right for your project? See <a target="_blank" rel="noopener noreferrer" href="%s">Choose a license.</a> +multiple_licenses = Multiple Licenses object_format = Object Format object_format_helper = Object format of the repository. Cannot be changed later. SHA1 is most compatible. readme = README @@ -2942,6 +2943,7 @@ dashboard.start_schedule_tasks = Start actions schedule tasks dashboard.sync_branch.started = Branches Sync started dashboard.sync_tag.started = Tags Sync started dashboard.rebuild_issue_indexer = Rebuild issue indexer +dashboard.sync_repo_licenses = Sync repo licenses users.user_manage_panel = User Account Management users.new_account = Create User Account diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 1244676508ee..5aa8ad44e5e3 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1327,6 +1327,7 @@ func Routes() *web.Router { m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig) m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig) m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages) + m.Get("/licenses", reqRepoReader(unit.TypeCode), repo.GetLicenses) m.Get("/activities/feeds", repo.ListRepoActivityFeeds) m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed) m.Group("/avatar", func() { diff --git a/routers/api/v1/repo/license.go b/routers/api/v1/repo/license.go new file mode 100644 index 000000000000..8a6bdfd42fa6 --- /dev/null +++ b/routers/api/v1/repo/license.go @@ -0,0 +1,51 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/services/context" +) + +// GetLicenses returns licenses +func GetLicenses(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/licenses repository repoGetLicenses + // --- + // summary: Get repo licenses + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "404": + // "$ref": "#/responses/notFound" + // "200": + // "$ref": "#/responses/LicensesList" + + licenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository) + if err != nil { + log.Error("GetRepoLicenses failed: %v", err) + ctx.InternalServerError(err) + return + } + + resp := make([]string, len(licenses)) + for i := range licenses { + resp[i] = licenses[i].License + } + + ctx.JSON(http.StatusOK, resp) +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 1bcec8fcf7e7..6c1a94ee168a 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -731,6 +731,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err } // Default branch only updated if changed and exist or the repository is empty + updateRepoLicense := false if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) { if !repo.IsEmpty { if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil { @@ -739,6 +740,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } } + updateRepoLicense = true } repo.DefaultBranch = *opts.DefaultBranch } @@ -748,6 +750,15 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } + if updateRepoLicense { + if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{ + RepoID: ctx.Repo.Repository.ID, + }); err != nil { + ctx.Error(http.StatusInternalServerError, "AddRepoToLicenseUpdaterQueue", err) + return err + } + } + log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) return nil } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index 345835f9a58c..b9d2a0217cd4 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -359,6 +359,13 @@ type swaggerLanguageStatistics struct { Body map[string]int64 `json:"body"` } +// LicensesList +// swagger:response LicensesList +type swaggerLicensesList struct { + // in: body + Body []string `json:"body"` +} + // CombinedStatus // swagger:response CombinedStatus type swaggerCombinedStatus struct { diff --git a/routers/init.go b/routers/init.go index e21f763c1e52..fe80dfd2cdd8 100644 --- a/routers/init.go +++ b/routers/init.go @@ -172,6 +172,8 @@ func InitWebInstalled(ctx context.Context) { actions_service.Init() + mustInit(repo_service.InitLicenseClassifier) + // Finally start up the cron cron.NewContext(ctx) } diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go index 7be909f955d1..03c19c8ff402 100644 --- a/routers/private/default_branch.go +++ b/routers/private/default_branch.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/private" gitea_context "code.gitea.io/gitea/services/context" + repo_service "code.gitea.io/gitea/services/repository" ) // SetDefaultBranch updates the default branch @@ -36,5 +37,15 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) { }) return } + + if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{ + RepoID: ctx.Repo.Repository.ID, + }); err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + ctx.PlainText(http.StatusOK, "success") } diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 2d1688523c48..5c01216356db 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -278,10 +278,19 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { branch := refFullName.BranchName() - // If our branch is the default branch of an unforked repo - there's no PR to create or refer to - if !repo.IsFork && branch == baseRepo.DefaultBranch { - results = append(results, private.HookPostReceiveBranchResult{}) - continue + if branch == baseRepo.DefaultBranch { + if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{ + RepoID: repo.ID, + }); err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{Err: err.Error()}) + return + } + + // If our branch is the default branch of an unforked repo - there's no PR to create or refer to + if !repo.IsFork { + results = append(results, private.HookPostReceiveBranchResult{}) + continue + } } pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, issues_model.PullRequestFlowGithub) diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 4897a5f4fcdf..4a62237838a4 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -89,7 +89,7 @@ func Branches(ctx *context.Context) { pager := context.NewPagination(int(branchesCount), pageSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager - + ctx.Data["LicenseFileName"] = repo_service.LicenseFileName ctx.HTML(http.StatusOK, tplBranch) } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index a433dd228e3e..0e4e10bf5094 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -29,7 +29,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/gitdiff" - git_service "code.gitea.io/gitea/services/repository" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -101,7 +101,7 @@ func Commits(ctx *context.Context) { pager := context.NewPagination(int(commitsCount), pageSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager - + ctx.Data["LicenseFileName"] = repo_service.LicenseFileName ctx.HTML(http.StatusOK, tplCommits) } @@ -218,6 +218,8 @@ func SearchCommits(ctx *context.Context) { } ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name + ctx.Data["RefName"] = ctx.Repo.RefName + ctx.Data["LicenseFileName"] = repo_service.LicenseFileName ctx.HTML(http.StatusOK, tplCommits) } @@ -263,12 +265,12 @@ func FileHistory(ctx *context.Context) { pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager - + ctx.Data["LicenseFileName"] = repo_service.LicenseFileName ctx.HTML(http.StatusOK, tplCommits) } func LoadBranchesAndTags(ctx *context.Context) { - response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha")) + response, err := repo_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha")) if err == nil { ctx.JSON(http.StatusOK, response) return diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index f551fffe956b..566a82316f8e 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -289,7 +289,6 @@ func releasesOrTagsFeed(ctx *context.Context, isReleasesOnly bool, formatType st // SingleRelease renders a single release's page func SingleRelease(ctx *context.Context) { ctx.Data["PageIsReleaseList"] = true - ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 12d202e4a0d4..976911760948 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -51,6 +51,7 @@ import ( "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/services/context" issue_service "code.gitea.io/gitea/services/issue" + repo_service "code.gitea.io/gitea/services/repository" files_service "code.gitea.io/gitea/services/repository/files" "github.com/nektos/act/pkg/model" @@ -1077,6 +1078,7 @@ func renderHomeCode(ctx *context.Context) { ctx.Data["TreeLink"] = treeLink ctx.Data["TreeNames"] = treeNames ctx.Data["BranchLink"] = branchLink + ctx.Data["LicenseFileName"] = repo_service.LicenseFileName ctx.HTML(http.StatusOK, tplRepoHome) } diff --git a/services/context/repo.go b/services/context/repo.go index e0d3a0bfd3e4..c001255283ae 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -404,6 +404,13 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { ctx.Data["PushMirrors"] = pushMirrors ctx.Data["RepoName"] = ctx.Repo.Repository.Name ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty + + repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository) + if err != nil { + ctx.ServerError("GetRepoLicenses", err) + return + } + ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList() } // RepoAssignment returns a middleware to handle repository assignment diff --git a/services/convert/repository.go b/services/convert/repository.go index 751260a45d3d..e026d0f4405f 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -175,6 +175,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR language = repo.PrimaryLanguage.Language } + repoLicenses, err := repo_model.GetRepoLicenses(ctx, repo) + if err != nil { + return nil + } + repoAPIURL := repo.APIURL() return &api.Repository{ @@ -238,6 +243,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR RepoTransfer: transfer, Topics: repo.Topics, ObjectFormatName: repo.ObjectFormatName, + Licenses: repoLicenses.StringList(), } } diff --git a/services/cron/tasks_basic.go b/services/cron/tasks_basic.go index 2a213ae51524..fb5938745e61 100644 --- a/services/cron/tasks_basic.go +++ b/services/cron/tasks_basic.go @@ -156,6 +156,16 @@ func registerCleanupPackages() { }) } +func registerSyncRepoLicenses() { + RegisterTaskFatal("sync_repo_licenses", &BaseConfig{ + Enabled: false, + RunAtStart: false, + Schedule: "@annually", + }, func(ctx context.Context, _ *user_model.User, config Config) error { + return repo_service.SyncRepoLicenses(ctx) + }) +} + func initBasicTasks() { if setting.Mirror.Enabled { registerUpdateMirrorTask() @@ -172,4 +182,5 @@ func initBasicTasks() { if setting.Packages.Enabled { registerCleanupPackages() } + registerSyncRepoLicenses() } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index c9b924809819..f2379dadf8fa 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" + repo_service "code.gitea.io/gitea/services/repository" "github.com/stretchr/testify/assert" ) @@ -302,6 +303,8 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { toRepoName := "migrated" uploader := NewGiteaLocalUploader(context.Background(), fromRepoOwner, fromRepoOwner.Name, toRepoName) uploader.gitServiceType = structs.GiteaService + + assert.NoError(t, repo_service.Init(context.Background())) assert.NoError(t, uploader.CreateRepo(&base.Repository{ Description: "description", OriginalURL: fromRepo.RepoPath(), diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 9f7ffb29c9f3..654a50d11efa 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" + repo_service "code.gitea.io/gitea/services/repository" ) // gitShortEmptySha Git short empty SHA @@ -559,6 +560,14 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { } } + // Update License + if err = repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{ + RepoID: m.Repo.ID, + }); err != nil { + log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err) + return false + } + log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) return true diff --git a/services/repository/branch.go b/services/repository/branch.go index f5cdb72a7bb0..67df4363e441 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -612,6 +612,14 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR return err } + if !repo.IsEmpty { + if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{ + RepoID: repo.ID, + }); err != nil { + log.Error("AddRepoToLicenseUpdaterQueue: %v", err) + } + } + notify_service.ChangeDefaultBranch(ctx, repo) return nil diff --git a/services/repository/create.go b/services/repository/create.go index 971793bcc6e3..282b2d3e58d4 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -303,6 +303,25 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt rollbackRepo.OwnerID = u.ID return fmt.Errorf("CreateRepository(git update-server-info): %w", err) } + + // update licenses + var licenses []string + if len(opts.License) > 0 { + licenses = append(licenses, ConvertLicenseName(opts.License)) + + stdout, _, err := git.NewCommand(ctx, "rev-parse", "HEAD"). + SetDescription(fmt.Sprintf("CreateRepository(git rev-parse HEAD): %s", repoPath)). + RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) + rollbackRepo = repo + rollbackRepo.OwnerID = u.ID + return fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) + } + if err := repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { + return err + } + } return nil }); err != nil { if rollbackRepo != nil { diff --git a/services/repository/delete.go b/services/repository/delete.go index cd779b05c350..e58083314089 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -140,6 +140,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID &git_model.Branch{RepoID: repoID}, &git_model.LFSLock{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID}, + &repo_model.RepoLicense{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID}, &repo_model.Mirror{RepoID: repoID}, &activities_model.Notification{RepoID: repoID}, diff --git a/services/repository/fork.go b/services/repository/fork.go index f074fd108211..e1145556791c 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -198,6 +198,9 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork if err := repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { log.Error("Copy language stat from oldRepo failed: %v", err) } + if err := repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { + return nil, err + } gitRepo, err := gitrepo.OpenRepository(ctx, repo) if err != nil { diff --git a/services/repository/license.go b/services/repository/license.go new file mode 100644 index 000000000000..2453be3c871a --- /dev/null +++ b/services/repository/license.go @@ -0,0 +1,205 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + "fmt" + "io" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/queue" + + licenseclassifier "github.com/google/licenseclassifier/v2" +) + +var ( + classifier *licenseclassifier.Classifier + LicenseFileName = "LICENSE" + licenseAliases map[string]string + + // licenseUpdaterQueue represents a queue to handle update repo licenses + licenseUpdaterQueue *queue.WorkerPoolQueue[*LicenseUpdaterOptions] +) + +func AddRepoToLicenseUpdaterQueue(opts *LicenseUpdaterOptions) error { + if opts == nil { + return nil + } + return licenseUpdaterQueue.Push(opts) +} + +func loadLicenseAliases() error { + if licenseAliases != nil { + return nil + } + + data, err := options.AssetFS().ReadFile("license", "etc", "license-aliases.json") + if err != nil { + return err + } + err = json.Unmarshal(data, &licenseAliases) + if err != nil { + return err + } + return nil +} + +func ConvertLicenseName(name string) string { + if err := loadLicenseAliases(); err != nil { + return name + } + + v, ok := licenseAliases[name] + if ok { + return v + } + return name +} + +func InitLicenseClassifier() error { + // threshold should be 0.84~0.86 or the test will be failed + classifier = licenseclassifier.NewClassifier(.85) + licenseFiles, err := options.AssetFS().ListFiles("license", true) + if err != nil { + return err + } + + existLicense := make(container.Set[string]) + if len(licenseFiles) > 0 { + for _, licenseFile := range licenseFiles { + licenseName := ConvertLicenseName(licenseFile) + if existLicense.Contains(licenseName) { + continue + } + existLicense.Add(licenseName) + data, err := options.License(licenseFile) + if err != nil { + return err + } + classifier.AddContent("License", licenseFile, licenseName, data) + } + } + return nil +} + +type LicenseUpdaterOptions struct { + RepoID int64 +} + +func repoLicenseUpdater(items ...*LicenseUpdaterOptions) []*LicenseUpdaterOptions { + ctx := graceful.GetManager().ShutdownContext() + + for _, opts := range items { + repo, err := repo_model.GetRepositoryByID(ctx, opts.RepoID) + if err != nil { + log.Error("repoLicenseUpdater [%d] failed: GetRepositoryByID: %v", opts.RepoID, err) + continue + } + if repo.IsEmpty { + continue + } + + gitRepo, err := gitrepo.OpenRepository(ctx, repo) + if err != nil { + log.Error("repoLicenseUpdater [%d] failed: OpenRepository: %v", opts.RepoID, err) + continue + } + defer gitRepo.Close() + + commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) + if err != nil { + log.Error("repoLicenseUpdater [%d] failed: GetBranchCommit: %v", opts.RepoID, err) + continue + } + if err = UpdateRepoLicenses(ctx, repo, commit); err != nil { + log.Error("repoLicenseUpdater [%d] failed: updateRepoLicenses: %v", opts.RepoID, err) + } + } + return nil +} + +func SyncRepoLicenses(ctx context.Context) error { + log.Trace("Doing: SyncRepoLicenses") + + if err := db.Iterate( + ctx, + nil, + func(ctx context.Context, repo *repo_model.Repository) error { + select { + case <-ctx.Done(): + return db.ErrCancelledf("before sync repo licenses for %s", repo.FullName()) + default: + } + return AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID}) + }, + ); err != nil { + log.Trace("Error: SyncRepoLicenses: %v", err) + return err + } + + log.Trace("Finished: SyncReposLicenses") + return nil +} + +// UpdateRepoLicenses will update repository licenses col if license file exists +func UpdateRepoLicenses(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) error { + if commit == nil { + return nil + } + + b, err := commit.GetBlobByPath(LicenseFileName) + if err != nil && !git.IsErrNotExist(err) { + return fmt.Errorf("GetBlobByPath: %w", err) + } + + if git.IsErrNotExist(err) { + return repo_model.CleanRepoLicenses(ctx, repo) + } + + licenses := make([]string, 0) + if b != nil { + r, err := b.DataAsync() + if err != nil { + return err + } + defer r.Close() + + licenses, err = detectLicense(r) + if err != nil { + return fmt.Errorf("detectLicense: %w", err) + } + } + return repo_model.UpdateRepoLicenses(ctx, repo, commit.ID.String(), licenses) +} + +// detectLicense returns the licenses detected by the given content buff +func detectLicense(r io.Reader) ([]string, error) { + if r == nil { + return nil, nil + } + + matches, err := classifier.MatchFrom(r) + if err != nil { + return nil, err + } + if len(matches.Matches) > 0 { + results := make(container.Set[string], len(matches.Matches)) + for _, r := range matches.Matches { + if r.MatchType == "License" && !results.Contains(r.Variant) { + results.Add(r.Variant) + } + } + return results.Values(), nil + } + return nil, nil +} diff --git a/services/repository/license_test.go b/services/repository/license_test.go new file mode 100644 index 000000000000..39e9738145c5 --- /dev/null +++ b/services/repository/license_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "fmt" + "strings" + "testing" + + repo_module "code.gitea.io/gitea/modules/repository" + + "github.com/stretchr/testify/assert" +) + +func Test_detectLicense(t *testing.T) { + type DetectLicenseTest struct { + name string + arg string + want []string + } + + tests := []DetectLicenseTest{ + { + name: "empty", + arg: "", + want: nil, + }, + { + name: "no detected license", + arg: "Copyright (c) 2023 Gitea", + want: nil, + }, + } + + repo_module.LoadRepoConfig() + err := loadLicenseAliases() + assert.NoError(t, err) + for _, licenseName := range repo_module.Licenses { + license, err := repo_module.GetLicense(licenseName, &repo_module.LicenseValues{ + Owner: "Gitea", + Email: "teabot@gitea.io", + Repo: "gitea", + Year: "2024", + }) + assert.NoError(t, err) + + tests = append(tests, DetectLicenseTest{ + name: fmt.Sprintf("single license test: %s", licenseName), + arg: string(license), + want: []string{ConvertLicenseName(licenseName)}, + }) + } + + err = InitLicenseClassifier() + assert.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + license, err := detectLicense(strings.NewReader(tt.arg)) + assert.NoError(t, err) + assert.Equal(t, tt.want, license) + }) + } + + result, err := detectLicense(strings.NewReader(tests[2].arg + tests[3].arg + tests[4].arg)) + assert.NoError(t, err) + t.Run("multiple licenses test", func(t *testing.T) { + assert.Equal(t, 3, len(result)) + assert.Contains(t, result, tests[2].want[0]) + assert.Contains(t, result, tests[3].want[0]) + assert.Contains(t, result, tests[4].want[0]) + }) +} diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 2e901791b4ad..c627b46fab56 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -172,6 +172,11 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("StoreMissingLfsObjectsInRepository: %w", err) } } + + // Update repo license + if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID}); err != nil { + log.Error("Failed to add repo to license updater queue: %v", err) + } } ctx, committer, err := db.TxContext(ctx) diff --git a/services/repository/repository.go b/services/repository/repository.go index 5306e7d45cce..59b4491132da 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -18,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" @@ -96,6 +97,12 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN // Init start repository service func Init(ctx context.Context) error { + licenseUpdaterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "repo_license_updater", repoLicenseUpdater) + if licenseUpdaterQueue == nil { + return fmt.Errorf("unable to create repo_license_updater queue") + } + go graceful.GetManager().RunWithCancel(licenseUpdaterQueue) + if err := repo_module.LoadRepoConfig(); err != nil { return err } diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl index 87d211031494..6f53acd31ece 100644 --- a/templates/repo/sub_menu.tmpl +++ b/templates/repo/sub_menu.tmpl @@ -13,7 +13,12 @@ {{svg "octicon-tag"}} <b>{{ctx.Locale.PrettyNumber .NumTags}}</b> {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}} </a> {{end}} - <span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}> + {{if .DetectedRepoLicenses}} + <a class="item muted" href="{{.RepoLink}}/src/{{.Repository.DefaultBranch}}/{{PathEscapeSegments .LicenseFileName}}" data-tooltip-placement="top" data-tooltip-content="{{StringUtils.Join .DetectedRepoLicenses ", "}}"> + {{svg "octicon-law"}} <b>{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}</b> + </a> + {{end}} + <span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-placement="top" data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}> {{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}} {{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}} {{svg "octicon-database"}} <b>{{ctx.Locale.PrettyNumber (index $fileSizeFields 0)}}</b> {{index $fileSizeFields 1}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 598350550276..bac918ac3899 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -10640,6 +10640,42 @@ } } }, + "/repos/{owner}/{repo}/licenses": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get repo licenses", + "operationId": "repoGetLicenses", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/LicensesList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/media/{filepath}": { "get": { "produces": [ @@ -24142,6 +24178,13 @@ "type": "string", "x-go-name": "LanguagesURL" }, + "licenses": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Licenses" + }, "link": { "type": "string", "x-go-name": "Link" @@ -25717,6 +25760,15 @@ } } }, + "LicensesList": { + "description": "LicensesList", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, "MarkdownRender": { "description": "MarkdownRender is a rendered markdown document", "schema": { diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/08/51b61d9f8ca0e9e63617e11907988ee88b1ca6 b/tests/gitea-repositories-meta/user2/repo1.git/objects/08/51b61d9f8ca0e9e63617e11907988ee88b1ca6 new file mode 100644 index 0000000000000000000000000000000000000000..69b1e0310be8cdcb0d9b0608094280c7da799277 GIT binary patch literal 85 zcmV-b0IL6Z0V^p=O;s>AVlXr?Ff%bx@bPqZ^$T`o5Nc#ySDZHM(2~<mrJ@!ej>UvL r>Vhf>a&>g^b=AvFVes|{TpM^lt9HKX$x4&R%2%(WP38jt9giIU<jE#< literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/12/8105ae73669ac2a4cb42751538f0c65c54e28a b/tests/gitea-repositories-meta/user2/repo1.git/objects/12/8105ae73669ac2a4cb42751538f0c65c54e28a new file mode 100644 index 0000000000000000000000000000000000000000..4c925560cb41eb801c87d12111567b880c67dff7 GIT binary patch literal 643 zcmV-}0(|{=0bNqvYuhjo-?RUUBTp@ut}A5^V+<5qPQ)D9$V#(3`a_8|94lDzLjU`n zl(eG*gVE{k_jPIp)4-R<ug{-0OhT?(HCh!#i8;Sc`q(?TYwzLv)8o_g4_IlZ>L@DJ zbkf#pGlI6zt4TGlaGdJVsV;`Yq!gF~w0%7tRSeERJ-)(OO%?}C<7z$X@d!1v$l*I( zk1*C8+@+orHg`~4Yg%0+9(1OiPil0v^Lz(AC<}M4SFosV6N~#m(y4j?JtCTdw=1}4 z*PGeFq^z5C>oH<%YKOV=8s4r4ebP4uKOT%*uY9)X!0U|RWIBEDe-&(<=Vs8hkD=2Z zx|tnzT3-wa#@@$gGl5mZ;1M+H4qpGM3q76%vqAn_M1l9xo6|=p7|~&#Mx?}0aHcaD zSz!FFTIVZX`N0f>xp-@>89VL$+HX-L@v3gj9~Hb@_c|JfQbXKf0YSudtG6rL*QmWw zx4@7W<MpLCJLqn1ti#1?%>Hbq!1uq+2jpK<sESPPh@ikKC`IwYQkueoRCr#*uw!yv zY$afaAYAUD$bj%Y{A4_hf&MB5ttu!4uuYjWiZ#ZQe48@9f+f!J0v|8KCyb!708e;> zVie~x*iezI5t%Gm&g4FZj7jb>GXy125+PZ#&53|=E6SpxNS`7sXFL<gLN}Dl2V}(> z&=)*FwI+EUsK^%8i=Zzl%DrH#wS;w%rxc4zipt0`r&lU8mE?qNVo1q`th_n_1qv3S z?WS(GrlEv<1iwVGf_ocD!KJ`sj4p+IAKkHv#y|wCd?1-95Z=d$6FxKU=QzEh_^5;G d_l;nS4{VF3-?gNa<cNjq;UAhGqQ4edRoWgaJ|F-9 literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/90/c1019714259b24fb81711d4416ac0f18667dfa b/tests/gitea-repositories-meta/user2/repo1.git/objects/90/c1019714259b24fb81711d4416ac0f18667dfa new file mode 100644 index 000000000000..bbd3bf7f30f4 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/90/c1019714259b24fb81711d4416ac0f18667dfa @@ -0,0 +1,2 @@ +x��M +�0F]��2�d&�*��`���ƅ��W�[>ރ���< �НƮj!�&=�*ʞ1*�@��TS*X3�W�u��c�.��K��90E�Ğ�$h+��f�g<��~_@�E{=,!��m��u�Y� ��B�g8fz|�_erk�9U]�j~2]<� \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch index f98a263be62f..5abf667b61ee 100644 --- a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch @@ -1 +1 @@ -65f1bf27bc3bf70f64657658635e66094edbcb4d +90c1019714259b24fb81711d4416ac0f18667dfa diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 92da7ce041cd..66209ee4e0c8 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -304,11 +304,11 @@ func TestAPICron(t *testing.T) { AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "28", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "29", resp.Header().Get("X-Total-Count")) var crons []api.Cron DecodeJSON(t, resp, &crons) - assert.Len(t, crons, 28) + assert.Len(t, crons, 29) }) t.Run("Execute", func(t *testing.T) { diff --git a/tests/integration/api_repo_license_test.go b/tests/integration/api_repo_license_test.go new file mode 100644 index 000000000000..52d30856944c --- /dev/null +++ b/tests/integration/api_repo_license_test.go @@ -0,0 +1,80 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + "time" + + auth_model "code.gitea.io/gitea/models/auth" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +var testLicenseContent = ` +Copyright (c) 2024 Gitea + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +` + +func TestAPIRepoLicense(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + + // Request editor page + req := NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "LICENSE", + "content": testLicenseContent, + "commit_choice": "direct", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // let gitea update repo license + time.Sleep(time.Second) + checkRepoLicense(t, "user2", "repo1", []string{"BSD-2-Clause"}) + + // Change default branch + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + branchName := "DefaultBranch" + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1", api.EditRepoOption{ + DefaultBranch: &branchName, + }).AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusOK) + + // let gitea update repo license + time.Sleep(time.Second) + checkRepoLicense(t, "user2", "repo1", []string{"MIT"}) + }) +} + +func checkRepoLicense(t *testing.T, owner, repo string, expected []string) { + reqURL := fmt.Sprintf("/api/v1/repos/%s/%s/licenses", owner, repo) + req := NewRequest(t, "GET", reqURL) + resp := MakeRequest(t, req, http.StatusOK) + + var licenses []string + DecodeJSON(t, resp, &licenses) + + assert.ElementsMatch(t, expected, licenses, 0) +} From 3a4a1bffbebd8a6f024a7fc4849cebbd7f0274d4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Wed, 2 Oct 2024 08:03:19 +0800 Subject: [PATCH 02/17] Make oauth2 code clear. Move oauth2 provider code to their own packages/files (#32148) Fix #30266 Replace #31533 --- routers/init.go | 3 +- routers/web/auth/oauth.go | 844 ------------------ routers/web/auth/oauth2_provider.go | 666 ++++++++++++++ routers/web/auth/oauth_test.go | 12 +- services/auth/oauth2.go | 6 +- services/auth/source/oauth2/init.go | 4 - services/oauth2_provider/access_token.go | 214 +++++ services/oauth2_provider/init.go | 19 + .../jwtsigningkey.go | 2 +- .../oauth2 => oauth2_provider}/token.go | 23 +- tests/integration/oauth_test.go | 28 +- 11 files changed, 933 insertions(+), 888 deletions(-) create mode 100644 routers/web/auth/oauth2_provider.go create mode 100644 services/oauth2_provider/access_token.go create mode 100644 services/oauth2_provider/init.go rename services/{auth/source/oauth2 => oauth2_provider}/jwtsigningkey.go (99%) rename services/{auth/source/oauth2 => oauth2_provider}/token.go (83%) diff --git a/routers/init.go b/routers/init.go index fe80dfd2cdd8..2091f5967aca 100644 --- a/routers/init.go +++ b/routers/init.go @@ -47,6 +47,7 @@ import ( markup_service "code.gitea.io/gitea/services/markup" repo_migrations "code.gitea.io/gitea/services/migrations" mirror_service "code.gitea.io/gitea/services/mirror" + "code.gitea.io/gitea/services/oauth2_provider" pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" @@ -144,7 +145,7 @@ func InitWebInstalled(ctx context.Context) { log.Info("ORM engine initialization successful!") mustInit(system.Init) mustInitCtx(ctx, oauth2.Init) - + mustInitCtx(ctx, oauth2_provider.Init) mustInit(release_service.Init) mustInitCtx(ctx, models.Init) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index c61a0a624043..ccbb3bebf1f5 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -4,878 +4,34 @@ package auth import ( - go_context "context" "errors" "fmt" "html" - "html/template" "io" "net/http" - "net/url" "sort" "strings" "code.gitea.io/gitea/models/auth" - org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" - auth_service "code.gitea.io/gitea/services/auth" source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/externalaccount" - "code.gitea.io/gitea/services/forms" user_service "code.gitea.io/gitea/services/user" - "gitea.com/go-chi/binding" - "github.com/golang-jwt/jwt/v5" "github.com/markbates/goth" "github.com/markbates/goth/gothic" go_oauth2 "golang.org/x/oauth2" ) -const ( - tplGrantAccess base.TplName = "user/auth/grant" - tplGrantError base.TplName = "user/auth/grant_error" -) - -// TODO move error and responses to SDK or models - -// AuthorizeErrorCode represents an error code specified in RFC 6749 -// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1 -type AuthorizeErrorCode string - -const ( - // ErrorCodeInvalidRequest represents the according error in RFC 6749 - ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request" - // ErrorCodeUnauthorizedClient represents the according error in RFC 6749 - ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client" - // ErrorCodeAccessDenied represents the according error in RFC 6749 - ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied" - // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749 - ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type" - // ErrorCodeInvalidScope represents the according error in RFC 6749 - ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope" - // ErrorCodeServerError represents the according error in RFC 6749 - ErrorCodeServerError AuthorizeErrorCode = "server_error" - // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749 - ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable" -) - -// AuthorizeError represents an error type specified in RFC 6749 -// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1 -type AuthorizeError struct { - ErrorCode AuthorizeErrorCode `json:"error" form:"error"` - ErrorDescription string - State string -} - -// Error returns the error message -func (err AuthorizeError) Error() string { - return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription) -} - -// AccessTokenErrorCode represents an error code specified in RFC 6749 -// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 -type AccessTokenErrorCode string - -const ( - // AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749 - AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request" - // AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749 - AccessTokenErrorCodeInvalidClient = "invalid_client" - // AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749 - AccessTokenErrorCodeInvalidGrant = "invalid_grant" - // AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749 - AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client" - // AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749 - AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type" - // AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749 - AccessTokenErrorCodeInvalidScope = "invalid_scope" -) - -// AccessTokenError represents an error response specified in RFC 6749 -// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 -type AccessTokenError struct { - ErrorCode AccessTokenErrorCode `json:"error" form:"error"` - ErrorDescription string `json:"error_description"` -} - -// Error returns the error message -func (err AccessTokenError) Error() string { - return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription) -} - -// errCallback represents a oauth2 callback error -type errCallback struct { - Code string - Description string -} - -func (err errCallback) Error() string { - return err.Description -} - -// TokenType specifies the kind of token -type TokenType string - -const ( - // TokenTypeBearer represents a token type specified in RFC 6749 - TokenTypeBearer TokenType = "bearer" - // TokenTypeMAC represents a token type specified in RFC 6749 - TokenTypeMAC = "mac" -) - -// AccessTokenResponse represents a successful access token response -// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2 -type AccessTokenResponse struct { - AccessToken string `json:"access_token"` - TokenType TokenType `json:"token_type"` - ExpiresIn int64 `json:"expires_in"` - RefreshToken string `json:"refresh_token"` - IDToken string `json:"id_token,omitempty"` -} - -func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) { - if setting.OAuth2.InvalidateRefreshTokens { - if err := grant.IncreaseCounter(ctx); err != nil { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidGrant, - ErrorDescription: "cannot increase the grant counter", - } - } - } - // generate access token to access the API - expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime) - accessToken := &oauth2.Token{ - GrantID: grant.ID, - Type: oauth2.TypeAccessToken, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), - }, - } - signedAccessToken, err := accessToken.SignToken(serverKey) - if err != nil { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot sign token", - } - } - - // generate refresh token to request an access token after it expired later - refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime() - refreshToken := &oauth2.Token{ - GrantID: grant.ID, - Counter: grant.Counter, - Type: oauth2.TypeRefreshToken, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(refreshExpirationDate), - }, - } - signedRefreshToken, err := refreshToken.SignToken(serverKey) - if err != nil { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot sign token", - } - } - - // generate OpenID Connect id_token - signedIDToken := "" - if grant.ScopeContains("openid") { - app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) - if err != nil { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot find application", - } - } - user, err := user_model.GetUserByID(ctx, grant.UserID) - if err != nil { - if user_model.IsErrUserNotExist(err) { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot find user", - } - } - log.Error("Error loading user: %v", err) - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "server error", - } - } - - idToken := &oauth2.OIDCToken{ - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), - Issuer: setting.AppURL, - Audience: []string{app.ClientID}, - Subject: fmt.Sprint(grant.UserID), - }, - Nonce: grant.Nonce, - } - if grant.ScopeContains("profile") { - idToken.Name = user.GetDisplayName() - idToken.PreferredUsername = user.Name - idToken.Profile = user.HTMLURL() - idToken.Picture = user.AvatarLink(ctx) - idToken.Website = user.Website - idToken.Locale = user.Language - idToken.UpdatedAt = user.UpdatedUnix - } - if grant.ScopeContains("email") { - idToken.Email = user.Email - idToken.EmailVerified = user.IsActive - } - if grant.ScopeContains("groups") { - groups, err := getOAuthGroupsForUser(ctx, user) - if err != nil { - log.Error("Error getting groups: %v", err) - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "server error", - } - } - idToken.Groups = groups - } - - signedIDToken, err = idToken.SignToken(clientKey) - if err != nil { - return nil, &AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot sign token", - } - } - } - - return &AccessTokenResponse{ - AccessToken: signedAccessToken, - TokenType: TokenTypeBearer, - ExpiresIn: setting.OAuth2.AccessTokenExpirationTime, - RefreshToken: signedRefreshToken, - IDToken: signedIDToken, - }, nil -} - -type userInfoResponse struct { - Sub string `json:"sub"` - Name string `json:"name"` - Username string `json:"preferred_username"` - Email string `json:"email"` - Picture string `json:"picture"` - Groups []string `json:"groups"` -} - -// InfoOAuth manages request for userinfo endpoint -func InfoOAuth(ctx *context.Context) { - if ctx.Doer == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() { - ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`) - ctx.PlainText(http.StatusUnauthorized, "no valid authorization") - return - } - - response := &userInfoResponse{ - Sub: fmt.Sprint(ctx.Doer.ID), - Name: ctx.Doer.FullName, - Username: ctx.Doer.Name, - Email: ctx.Doer.Email, - Picture: ctx.Doer.AvatarLink(ctx), - } - - groups, err := getOAuthGroupsForUser(ctx, ctx.Doer) - if err != nil { - ctx.ServerError("Oauth groups for user", err) - return - } - response.Groups = groups - - ctx.JSON(http.StatusOK, response) -} - -// returns a list of "org" and "org:team" strings, -// that the given user is a part of. -func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]string, error) { - orgs, err := org_model.GetUserOrgsList(ctx, user) - if err != nil { - return nil, fmt.Errorf("GetUserOrgList: %w", err) - } - - var groups []string - for _, org := range orgs { - groups = append(groups, org.Name) - teams, err := org.LoadTeams(ctx) - if err != nil { - return nil, fmt.Errorf("LoadTeams: %w", err) - } - for _, team := range teams { - if team.IsMember(ctx, user.ID) { - groups = append(groups, org.Name+":"+team.LowerName) - } - } - } - return groups, nil -} - -func parseBasicAuth(ctx *context.Context) (username, password string, err error) { - authHeader := ctx.Req.Header.Get("Authorization") - if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { - return base.BasicAuthDecode(authData) - } - return "", "", errors.New("invalid basic authentication") -} - -// IntrospectOAuth introspects an oauth token -func IntrospectOAuth(ctx *context.Context) { - clientIDValid := false - if clientID, clientSecret, err := parseBasicAuth(ctx); err == nil { - app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID) - if err != nil && !auth.IsErrOauthClientIDInvalid(err) { - // this is likely a database error; log it and respond without details - log.Error("Error retrieving client_id: %v", err) - ctx.Error(http.StatusInternalServerError) - return - } - clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret)) - } - if !clientIDValid { - ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm=""`) - ctx.PlainText(http.StatusUnauthorized, "no valid authorization") - return - } - - var response struct { - Active bool `json:"active"` - Scope string `json:"scope,omitempty"` - Username string `json:"username,omitempty"` - jwt.RegisteredClaims - } - - form := web.GetForm(ctx).(*forms.IntrospectTokenForm) - token, err := oauth2.ParseToken(form.Token, oauth2.DefaultSigningKey) - if err == nil { - grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) - if err == nil && grant != nil { - app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) - if err == nil && app != nil { - response.Active = true - response.Scope = grant.Scope - response.Issuer = setting.AppURL - response.Audience = []string{app.ClientID} - response.Subject = fmt.Sprint(grant.UserID) - } - if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil { - response.Username = user.Name - } - } - } - - ctx.JSON(http.StatusOK, response) -} - -// AuthorizeOAuth manages authorize requests -func AuthorizeOAuth(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.AuthorizationForm) - errs := binding.Errors{} - errs = form.Validate(ctx.Req, errs) - if len(errs) > 0 { - errstring := "" - for _, e := range errs { - errstring += e.Error() + "\n" - } - ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring)) - return - } - - app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) - if err != nil { - if auth.IsErrOauthClientIDInvalid(err) { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeUnauthorizedClient, - ErrorDescription: "Client ID not registered", - State: form.State, - }, "") - return - } - ctx.ServerError("GetOAuth2ApplicationByClientID", err) - return - } - - var user *user_model.User - if app.UID != 0 { - user, err = user_model.GetUserByID(ctx, app.UID) - if err != nil { - ctx.ServerError("GetUserByID", err) - return - } - } - - if !app.ContainsRedirectURI(form.RedirectURI) { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeInvalidRequest, - ErrorDescription: "Unregistered Redirect URI", - State: form.State, - }, "") - return - } - - if form.ResponseType != "code" { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeUnsupportedResponseType, - ErrorDescription: "Only code response type is supported.", - State: form.State, - }, form.RedirectURI) - return - } - - // pkce support - switch form.CodeChallengeMethod { - case "S256": - case "plain": - if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeServerError, - ErrorDescription: "cannot set code challenge method", - State: form.State, - }, form.RedirectURI) - return - } - if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeServerError, - ErrorDescription: "cannot set code challenge", - State: form.State, - }, form.RedirectURI) - return - } - // Here we're just going to try to release the session early - if err := ctx.Session.Release(); err != nil { - // we'll tolerate errors here as they *should* get saved elsewhere - log.Error("Unable to save changes to the session: %v", err) - } - case "": - // "Authorization servers SHOULD reject authorization requests from native apps that don't use PKCE by returning an error message" - // https://datatracker.ietf.org/doc/html/rfc8252#section-8.1 - if !app.ConfidentialClient { - // "the authorization endpoint MUST return the authorization error response with the "error" value set to "invalid_request"" - // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1 - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeInvalidRequest, - ErrorDescription: "PKCE is required for public clients", - State: form.State, - }, form.RedirectURI) - return - } - default: - // "If the server supporting PKCE does not support the requested transformation, the authorization endpoint MUST return the authorization error response with "error" value set to "invalid_request"." - // https://www.rfc-editor.org/rfc/rfc7636#section-4.4.1 - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeInvalidRequest, - ErrorDescription: "unsupported code challenge method", - State: form.State, - }, form.RedirectURI) - return - } - - grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - - // Redirect if user already granted access and the application is confidential or trusted otherwise - // I.e. always require authorization for untrusted public clients as recommended by RFC 6749 Section 10.2 - if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil { - code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - redirect, err := code.GenerateRedirectURI(form.State) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - // Update nonce to reflect the new session - if len(form.Nonce) > 0 { - err := grant.SetNonce(ctx, form.Nonce) - if err != nil { - log.Error("Unable to update nonce: %v", err) - } - } - ctx.Redirect(redirect.String()) - return - } - - // show authorize page to grant access - ctx.Data["Application"] = app - ctx.Data["RedirectURI"] = form.RedirectURI - ctx.Data["State"] = form.State - ctx.Data["Scope"] = form.Scope - ctx.Data["Nonce"] = form.Nonce - if user != nil { - ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">@%s</a>`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name))) - } else { - ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName))) - } - ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("<strong>" + html.EscapeString(form.RedirectURI) + "</strong>") - // TODO document SESSION <=> FORM - err = ctx.Session.Set("client_id", app.ClientID) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - log.Error(err.Error()) - return - } - err = ctx.Session.Set("redirect_uri", form.RedirectURI) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - log.Error(err.Error()) - return - } - err = ctx.Session.Set("state", form.State) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - log.Error(err.Error()) - return - } - // Here we're just going to try to release the session early - if err := ctx.Session.Release(); err != nil { - // we'll tolerate errors here as they *should* get saved elsewhere - log.Error("Unable to save changes to the session: %v", err) - } - ctx.HTML(http.StatusOK, tplGrantAccess) -} - -// GrantApplicationOAuth manages the post request submitted when a user grants access to an application -func GrantApplicationOAuth(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.GrantApplicationForm) - if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State || - ctx.Session.Get("redirect_uri") != form.RedirectURI { - ctx.Error(http.StatusBadRequest) - return - } - - if !form.Granted { - handleAuthorizeError(ctx, AuthorizeError{ - State: form.State, - ErrorDescription: "the request is denied", - ErrorCode: ErrorCodeAccessDenied, - }, form.RedirectURI) - return - } - - app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) - if err != nil { - ctx.ServerError("GetOAuth2ApplicationByClientID", err) - return - } - grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - if grant == nil { - grant, err = app.CreateGrant(ctx, ctx.Doer.ID, form.Scope) - if err != nil { - handleAuthorizeError(ctx, AuthorizeError{ - State: form.State, - ErrorDescription: "cannot create grant for user", - ErrorCode: ErrorCodeServerError, - }, form.RedirectURI) - return - } - } else if grant.Scope != form.Scope { - handleAuthorizeError(ctx, AuthorizeError{ - State: form.State, - ErrorDescription: "a grant exists with different scope", - ErrorCode: ErrorCodeServerError, - }, form.RedirectURI) - return - } - - if len(form.Nonce) > 0 { - err := grant.SetNonce(ctx, form.Nonce) - if err != nil { - log.Error("Unable to update nonce: %v", err) - } - } - - var codeChallenge, codeChallengeMethod string - codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string) - codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string) - - code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, codeChallenge, codeChallengeMethod) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - redirect, err := code.GenerateRedirectURI(form.State) - if err != nil { - handleServerError(ctx, form.State, form.RedirectURI) - return - } - ctx.Redirect(redirect.String(), http.StatusSeeOther) -} - -// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities -func OIDCWellKnown(ctx *context.Context) { - ctx.Data["SigningKey"] = oauth2.DefaultSigningKey - ctx.JSONTemplate("user/auth/oidc_wellknown") -} - -// OIDCKeys generates the JSON Web Key Set -func OIDCKeys(ctx *context.Context) { - jwk, err := oauth2.DefaultSigningKey.ToJWK() - if err != nil { - log.Error("Error converting signing key to JWK: %v", err) - ctx.Error(http.StatusInternalServerError) - return - } - - jwk["use"] = "sig" - - jwks := map[string][]map[string]string{ - "keys": { - jwk, - }, - } - - ctx.Resp.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(ctx.Resp) - if err := enc.Encode(jwks); err != nil { - log.Error("Failed to encode representation as json. Error: %v", err) - } -} - -// AccessTokenOAuth manages all access token requests by the client -func AccessTokenOAuth(ctx *context.Context) { - form := *web.GetForm(ctx).(*forms.AccessTokenForm) - // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header - if form.ClientID == "" || form.ClientSecret == "" { - authHeader := ctx.Req.Header.Get("Authorization") - if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { - clientID, clientSecret, err := base.BasicAuthDecode(authData) - if err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot parse basic auth header", - }) - return - } - // validate that any fields present in the form match the Basic auth header - if form.ClientID != "" && form.ClientID != clientID { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "client_id in request body inconsistent with Authorization header", - }) - return - } - form.ClientID = clientID - if form.ClientSecret != "" && form.ClientSecret != clientSecret { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "client_secret in request body inconsistent with Authorization header", - }) - return - } - form.ClientSecret = clientSecret - } - } - - serverKey := oauth2.DefaultSigningKey - clientKey := serverKey - if serverKey.IsSymmetric() { - var err error - clientKey, err = oauth2.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret)) - if err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "Error creating signing key", - }) - return - } - } - - switch form.GrantType { - case "refresh_token": - handleRefreshToken(ctx, form, serverKey, clientKey) - case "authorization_code": - handleAuthorizationCode(ctx, form, serverKey, clientKey) - default: - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnsupportedGrantType, - ErrorDescription: "Only refresh_token or authorization_code grant type is supported", - }) - } -} - -func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) { - app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) - if err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidClient, - ErrorDescription: fmt.Sprintf("cannot load client with client id: %q", form.ClientID), - }) - return - } - // "The authorization server MUST ... require client authentication for confidential clients" - // https://datatracker.ietf.org/doc/html/rfc6749#section-6 - if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) { - errorDescription := "invalid client secret" - if form.ClientSecret == "" { - errorDescription = "invalid empty client secret" - } - // "invalid_client ... Client authentication failed" - // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidClient, - ErrorDescription: errorDescription, - }) - return - } - - token, err := oauth2.ParseToken(form.RefreshToken, serverKey) - if err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: "unable to parse refresh token", - }) - return - } - // get grant before increasing counter - grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) - if err != nil || grant == nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidGrant, - ErrorDescription: "grant does not exist", - }) - return - } - - // check if token got already used - if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: "token was already used", - }) - log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID) - return - } - accessToken, tokenErr := newAccessTokenResponse(ctx, grant, serverKey, clientKey) - if tokenErr != nil { - handleAccessTokenError(ctx, *tokenErr) - return - } - ctx.JSON(http.StatusOK, accessToken) -} - -func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) { - app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) - if err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidClient, - ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID), - }) - return - } - if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) { - errorDescription := "invalid client secret" - if form.ClientSecret == "" { - errorDescription = "invalid empty client secret" - } - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: errorDescription, - }) - return - } - if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: "unexpected redirect URI", - }) - return - } - authorizationCode, err := auth.GetOAuth2AuthorizationByCode(ctx, form.Code) - if err != nil || authorizationCode == nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: "client is not authorized", - }) - return - } - // check if code verifier authorizes the client, PKCE support - if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeUnauthorizedClient, - ErrorDescription: "failed PKCE code challenge", - }) - return - } - // check if granted for this application - if authorizationCode.Grant.ApplicationID != app.ID { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidGrant, - ErrorDescription: "invalid grant", - }) - return - } - // remove token from database to deny duplicate usage - if err := authorizationCode.Invalidate(ctx); err != nil { - handleAccessTokenError(ctx, AccessTokenError{ - ErrorCode: AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot proceed your request", - }) - } - resp, tokenErr := newAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey) - if tokenErr != nil { - handleAccessTokenError(ctx, *tokenErr) - return - } - // send successful response - ctx.JSON(http.StatusOK, resp) -} - -func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) { - ctx.JSON(http.StatusBadRequest, acErr) -} - -func handleServerError(ctx *context.Context, state, redirectURI string) { - handleAuthorizeError(ctx, AuthorizeError{ - ErrorCode: ErrorCodeServerError, - ErrorDescription: "A server error occurred", - State: state, - }, redirectURI) -} - -func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) { - if redirectURI == "" { - log.Warn("Authorization failed: %v", authErr.ErrorDescription) - ctx.Data["Error"] = authErr - ctx.HTML(http.StatusBadRequest, tplGrantError) - return - } - redirect, err := url.Parse(redirectURI) - if err != nil { - ctx.ServerError("url.Parse", err) - return - } - q := redirect.Query() - q.Set("error", string(authErr.ErrorCode)) - q.Set("error_description", authErr.ErrorDescription) - q.Set("state", authErr.State) - redirect.RawQuery = q.Encode() - ctx.Redirect(redirect.String(), http.StatusSeeOther) -} - // SignInOAuth handles the OAuth2 login buttons func SignInOAuth(ctx *context.Context) { provider := ctx.PathParam(":provider") diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go new file mode 100644 index 000000000000..29827b062de8 --- /dev/null +++ b/routers/web/auth/oauth2_provider.go @@ -0,0 +1,666 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package auth + +import ( + "errors" + "fmt" + "html" + "html/template" + "net/http" + "net/url" + "strings" + + "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web" + auth_service "code.gitea.io/gitea/services/auth" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/forms" + "code.gitea.io/gitea/services/oauth2_provider" + + "gitea.com/go-chi/binding" + jwt "github.com/golang-jwt/jwt/v5" +) + +const ( + tplGrantAccess base.TplName = "user/auth/grant" + tplGrantError base.TplName = "user/auth/grant_error" +) + +// TODO move error and responses to SDK or models + +// AuthorizeErrorCode represents an error code specified in RFC 6749 +// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1 +type AuthorizeErrorCode string + +const ( + // ErrorCodeInvalidRequest represents the according error in RFC 6749 + ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request" + // ErrorCodeUnauthorizedClient represents the according error in RFC 6749 + ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client" + // ErrorCodeAccessDenied represents the according error in RFC 6749 + ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied" + // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749 + ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type" + // ErrorCodeInvalidScope represents the according error in RFC 6749 + ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope" + // ErrorCodeServerError represents the according error in RFC 6749 + ErrorCodeServerError AuthorizeErrorCode = "server_error" + // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749 + ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable" +) + +// AuthorizeError represents an error type specified in RFC 6749 +// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1 +type AuthorizeError struct { + ErrorCode AuthorizeErrorCode `json:"error" form:"error"` + ErrorDescription string + State string +} + +// Error returns the error message +func (err AuthorizeError) Error() string { + return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription) +} + +// errCallback represents a oauth2 callback error +type errCallback struct { + Code string + Description string +} + +func (err errCallback) Error() string { + return err.Description +} + +type userInfoResponse struct { + Sub string `json:"sub"` + Name string `json:"name"` + Username string `json:"preferred_username"` + Email string `json:"email"` + Picture string `json:"picture"` + Groups []string `json:"groups"` +} + +// InfoOAuth manages request for userinfo endpoint +func InfoOAuth(ctx *context.Context) { + if ctx.Doer == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() { + ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`) + ctx.PlainText(http.StatusUnauthorized, "no valid authorization") + return + } + + response := &userInfoResponse{ + Sub: fmt.Sprint(ctx.Doer.ID), + Name: ctx.Doer.FullName, + Username: ctx.Doer.Name, + Email: ctx.Doer.Email, + Picture: ctx.Doer.AvatarLink(ctx), + } + + groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer) + if err != nil { + ctx.ServerError("Oauth groups for user", err) + return + } + response.Groups = groups + + ctx.JSON(http.StatusOK, response) +} + +func parseBasicAuth(ctx *context.Context) (username, password string, err error) { + authHeader := ctx.Req.Header.Get("Authorization") + if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { + return base.BasicAuthDecode(authData) + } + return "", "", errors.New("invalid basic authentication") +} + +// IntrospectOAuth introspects an oauth token +func IntrospectOAuth(ctx *context.Context) { + clientIDValid := false + if clientID, clientSecret, err := parseBasicAuth(ctx); err == nil { + app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID) + if err != nil && !auth.IsErrOauthClientIDInvalid(err) { + // this is likely a database error; log it and respond without details + log.Error("Error retrieving client_id: %v", err) + ctx.Error(http.StatusInternalServerError) + return + } + clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret)) + } + if !clientIDValid { + ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm=""`) + ctx.PlainText(http.StatusUnauthorized, "no valid authorization") + return + } + + var response struct { + Active bool `json:"active"` + Scope string `json:"scope,omitempty"` + Username string `json:"username,omitempty"` + jwt.RegisteredClaims + } + + form := web.GetForm(ctx).(*forms.IntrospectTokenForm) + token, err := oauth2_provider.ParseToken(form.Token, oauth2_provider.DefaultSigningKey) + if err == nil { + grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) + if err == nil && grant != nil { + app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) + if err == nil && app != nil { + response.Active = true + response.Scope = grant.Scope + response.Issuer = setting.AppURL + response.Audience = []string{app.ClientID} + response.Subject = fmt.Sprint(grant.UserID) + } + if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil { + response.Username = user.Name + } + } + } + + ctx.JSON(http.StatusOK, response) +} + +// AuthorizeOAuth manages authorize requests +func AuthorizeOAuth(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AuthorizationForm) + errs := binding.Errors{} + errs = form.Validate(ctx.Req, errs) + if len(errs) > 0 { + errstring := "" + for _, e := range errs { + errstring += e.Error() + "\n" + } + ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring)) + return + } + + app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) + if err != nil { + if auth.IsErrOauthClientIDInvalid(err) { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeUnauthorizedClient, + ErrorDescription: "Client ID not registered", + State: form.State, + }, "") + return + } + ctx.ServerError("GetOAuth2ApplicationByClientID", err) + return + } + + var user *user_model.User + if app.UID != 0 { + user, err = user_model.GetUserByID(ctx, app.UID) + if err != nil { + ctx.ServerError("GetUserByID", err) + return + } + } + + if !app.ContainsRedirectURI(form.RedirectURI) { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeInvalidRequest, + ErrorDescription: "Unregistered Redirect URI", + State: form.State, + }, "") + return + } + + if form.ResponseType != "code" { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeUnsupportedResponseType, + ErrorDescription: "Only code response type is supported.", + State: form.State, + }, form.RedirectURI) + return + } + + // pkce support + switch form.CodeChallengeMethod { + case "S256": + case "plain": + if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeServerError, + ErrorDescription: "cannot set code challenge method", + State: form.State, + }, form.RedirectURI) + return + } + if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeServerError, + ErrorDescription: "cannot set code challenge", + State: form.State, + }, form.RedirectURI) + return + } + // Here we're just going to try to release the session early + if err := ctx.Session.Release(); err != nil { + // we'll tolerate errors here as they *should* get saved elsewhere + log.Error("Unable to save changes to the session: %v", err) + } + case "": + // "Authorization servers SHOULD reject authorization requests from native apps that don't use PKCE by returning an error message" + // https://datatracker.ietf.org/doc/html/rfc8252#section-8.1 + if !app.ConfidentialClient { + // "the authorization endpoint MUST return the authorization error response with the "error" value set to "invalid_request"" + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1 + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeInvalidRequest, + ErrorDescription: "PKCE is required for public clients", + State: form.State, + }, form.RedirectURI) + return + } + default: + // "If the server supporting PKCE does not support the requested transformation, the authorization endpoint MUST return the authorization error response with "error" value set to "invalid_request"." + // https://www.rfc-editor.org/rfc/rfc7636#section-4.4.1 + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeInvalidRequest, + ErrorDescription: "unsupported code challenge method", + State: form.State, + }, form.RedirectURI) + return + } + + grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + + // Redirect if user already granted access and the application is confidential or trusted otherwise + // I.e. always require authorization for untrusted public clients as recommended by RFC 6749 Section 10.2 + if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil { + code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + redirect, err := code.GenerateRedirectURI(form.State) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + // Update nonce to reflect the new session + if len(form.Nonce) > 0 { + err := grant.SetNonce(ctx, form.Nonce) + if err != nil { + log.Error("Unable to update nonce: %v", err) + } + } + ctx.Redirect(redirect.String()) + return + } + + // show authorize page to grant access + ctx.Data["Application"] = app + ctx.Data["RedirectURI"] = form.RedirectURI + ctx.Data["State"] = form.State + ctx.Data["Scope"] = form.Scope + ctx.Data["Nonce"] = form.Nonce + if user != nil { + ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">@%s</a>`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name))) + } else { + ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName))) + } + ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("<strong>" + html.EscapeString(form.RedirectURI) + "</strong>") + // TODO document SESSION <=> FORM + err = ctx.Session.Set("client_id", app.ClientID) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + log.Error(err.Error()) + return + } + err = ctx.Session.Set("redirect_uri", form.RedirectURI) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + log.Error(err.Error()) + return + } + err = ctx.Session.Set("state", form.State) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + log.Error(err.Error()) + return + } + // Here we're just going to try to release the session early + if err := ctx.Session.Release(); err != nil { + // we'll tolerate errors here as they *should* get saved elsewhere + log.Error("Unable to save changes to the session: %v", err) + } + ctx.HTML(http.StatusOK, tplGrantAccess) +} + +// GrantApplicationOAuth manages the post request submitted when a user grants access to an application +func GrantApplicationOAuth(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.GrantApplicationForm) + if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State || + ctx.Session.Get("redirect_uri") != form.RedirectURI { + ctx.Error(http.StatusBadRequest) + return + } + + if !form.Granted { + handleAuthorizeError(ctx, AuthorizeError{ + State: form.State, + ErrorDescription: "the request is denied", + ErrorCode: ErrorCodeAccessDenied, + }, form.RedirectURI) + return + } + + app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) + if err != nil { + ctx.ServerError("GetOAuth2ApplicationByClientID", err) + return + } + grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + if grant == nil { + grant, err = app.CreateGrant(ctx, ctx.Doer.ID, form.Scope) + if err != nil { + handleAuthorizeError(ctx, AuthorizeError{ + State: form.State, + ErrorDescription: "cannot create grant for user", + ErrorCode: ErrorCodeServerError, + }, form.RedirectURI) + return + } + } else if grant.Scope != form.Scope { + handleAuthorizeError(ctx, AuthorizeError{ + State: form.State, + ErrorDescription: "a grant exists with different scope", + ErrorCode: ErrorCodeServerError, + }, form.RedirectURI) + return + } + + if len(form.Nonce) > 0 { + err := grant.SetNonce(ctx, form.Nonce) + if err != nil { + log.Error("Unable to update nonce: %v", err) + } + } + + var codeChallenge, codeChallengeMethod string + codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string) + codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string) + + code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, codeChallenge, codeChallengeMethod) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + redirect, err := code.GenerateRedirectURI(form.State) + if err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + ctx.Redirect(redirect.String(), http.StatusSeeOther) +} + +// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities +func OIDCWellKnown(ctx *context.Context) { + ctx.Data["SigningKey"] = oauth2_provider.DefaultSigningKey + ctx.JSONTemplate("user/auth/oidc_wellknown") +} + +// OIDCKeys generates the JSON Web Key Set +func OIDCKeys(ctx *context.Context) { + jwk, err := oauth2_provider.DefaultSigningKey.ToJWK() + if err != nil { + log.Error("Error converting signing key to JWK: %v", err) + ctx.Error(http.StatusInternalServerError) + return + } + + jwk["use"] = "sig" + + jwks := map[string][]map[string]string{ + "keys": { + jwk, + }, + } + + ctx.Resp.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(ctx.Resp) + if err := enc.Encode(jwks); err != nil { + log.Error("Failed to encode representation as json. Error: %v", err) + } +} + +// AccessTokenOAuth manages all access token requests by the client +func AccessTokenOAuth(ctx *context.Context) { + form := *web.GetForm(ctx).(*forms.AccessTokenForm) + // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header + if form.ClientID == "" || form.ClientSecret == "" { + authHeader := ctx.Req.Header.Get("Authorization") + if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { + clientID, clientSecret, err := base.BasicAuthDecode(authData) + if err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot parse basic auth header", + }) + return + } + // validate that any fields present in the form match the Basic auth header + if form.ClientID != "" && form.ClientID != clientID { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "client_id in request body inconsistent with Authorization header", + }) + return + } + form.ClientID = clientID + if form.ClientSecret != "" && form.ClientSecret != clientSecret { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "client_secret in request body inconsistent with Authorization header", + }) + return + } + form.ClientSecret = clientSecret + } + } + + serverKey := oauth2_provider.DefaultSigningKey + clientKey := serverKey + if serverKey.IsSymmetric() { + var err error + clientKey, err = oauth2_provider.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret)) + if err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "Error creating signing key", + }) + return + } + } + + switch form.GrantType { + case "refresh_token": + handleRefreshToken(ctx, form, serverKey, clientKey) + case "authorization_code": + handleAuthorizationCode(ctx, form, serverKey, clientKey) + default: + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnsupportedGrantType, + ErrorDescription: "Only refresh_token or authorization_code grant type is supported", + }) + } +} + +func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2_provider.JWTSigningKey) { + app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) + if err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient, + ErrorDescription: fmt.Sprintf("cannot load client with client id: %q", form.ClientID), + }) + return + } + // "The authorization server MUST ... require client authentication for confidential clients" + // https://datatracker.ietf.org/doc/html/rfc6749#section-6 + if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) { + errorDescription := "invalid client secret" + if form.ClientSecret == "" { + errorDescription = "invalid empty client secret" + } + // "invalid_client ... Client authentication failed" + // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient, + ErrorDescription: errorDescription, + }) + return + } + + token, err := oauth2_provider.ParseToken(form.RefreshToken, serverKey) + if err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: "unable to parse refresh token", + }) + return + } + // get grant before increasing counter + grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) + if err != nil || grant == nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidGrant, + ErrorDescription: "grant does not exist", + }) + return + } + + // check if token got already used + if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: "token was already used", + }) + log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID) + return + } + accessToken, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, grant, serverKey, clientKey) + if tokenErr != nil { + handleAccessTokenError(ctx, *tokenErr) + return + } + ctx.JSON(http.StatusOK, accessToken) +} + +func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2_provider.JWTSigningKey) { + app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) + if err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient, + ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID), + }) + return + } + if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) { + errorDescription := "invalid client secret" + if form.ClientSecret == "" { + errorDescription = "invalid empty client secret" + } + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: errorDescription, + }) + return + } + if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: "unexpected redirect URI", + }) + return + } + authorizationCode, err := auth.GetOAuth2AuthorizationByCode(ctx, form.Code) + if err != nil || authorizationCode == nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: "client is not authorized", + }) + return + } + // check if code verifier authorizes the client, PKCE support + if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient, + ErrorDescription: "failed PKCE code challenge", + }) + return + } + // check if granted for this application + if authorizationCode.Grant.ApplicationID != app.ID { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidGrant, + ErrorDescription: "invalid grant", + }) + return + } + // remove token from database to deny duplicate usage + if err := authorizationCode.Invalidate(ctx); err != nil { + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot proceed your request", + }) + } + resp, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey) + if tokenErr != nil { + handleAccessTokenError(ctx, *tokenErr) + return + } + // send successful response + ctx.JSON(http.StatusOK, resp) +} + +func handleAccessTokenError(ctx *context.Context, acErr oauth2_provider.AccessTokenError) { + ctx.JSON(http.StatusBadRequest, acErr) +} + +func handleServerError(ctx *context.Context, state, redirectURI string) { + handleAuthorizeError(ctx, AuthorizeError{ + ErrorCode: ErrorCodeServerError, + ErrorDescription: "A server error occurred", + State: state, + }, redirectURI) +} + +func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) { + if redirectURI == "" { + log.Warn("Authorization failed: %v", authErr.ErrorDescription) + ctx.Data["Error"] = authErr + ctx.HTML(http.StatusBadRequest, tplGrantError) + return + } + redirect, err := url.Parse(redirectURI) + if err != nil { + ctx.ServerError("url.Parse", err) + return + } + q := redirect.Query() + q.Set("error", string(authErr.ErrorCode)) + q.Set("error_description", authErr.ErrorDescription) + q.Set("state", authErr.State) + redirect.RawQuery = q.Encode() + ctx.Redirect(redirect.String(), http.StatusSeeOther) +} diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 4339d9d1ebae..78af97fa9c66 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -11,22 +11,22 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/services/auth/source/oauth2" + "code.gitea.io/gitea/services/oauth2_provider" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) -func createAndParseToken(t *testing.T, grant *auth.OAuth2Grant) *oauth2.OIDCToken { - signingKey, err := oauth2.CreateJWTSigningKey("HS256", make([]byte, 32)) +func createAndParseToken(t *testing.T, grant *auth.OAuth2Grant) *oauth2_provider.OIDCToken { + signingKey, err := oauth2_provider.CreateJWTSigningKey("HS256", make([]byte, 32)) assert.NoError(t, err) assert.NotNil(t, signingKey) - response, terr := newAccessTokenResponse(db.DefaultContext, grant, signingKey, signingKey) + response, terr := oauth2_provider.NewAccessTokenResponse(db.DefaultContext, grant, signingKey, signingKey) assert.Nil(t, terr) assert.NotNil(t, response) - parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2.OIDCToken{}, func(token *jwt.Token) (any, error) { + parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2_provider.OIDCToken{}, func(token *jwt.Token) (any, error) { assert.NotNil(t, token.Method) assert.Equal(t, signingKey.SigningMethod().Alg(), token.Method.Alg()) return signingKey.VerifyKey(), nil @@ -34,7 +34,7 @@ func createAndParseToken(t *testing.T, grant *auth.OAuth2Grant) *oauth2.OIDCToke assert.NoError(t, err) assert.True(t, parsedToken.Valid) - oidcToken, ok := parsedToken.Claims.(*oauth2.OIDCToken) + oidcToken, ok := parsedToken.Claims.(*oauth2_provider.OIDCToken) assert.True(t, ok) assert.NotNil(t, oidcToken) diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index 46d851014367..523998a63452 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -17,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/services/auth/source/oauth2" + "code.gitea.io/gitea/services/oauth2_provider" ) // Ensure the struct implements the interface. @@ -31,7 +31,7 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 { if !strings.Contains(accessToken, ".") { return 0 } - token, err := oauth2.ParseToken(accessToken, oauth2.DefaultSigningKey) + token, err := oauth2_provider.ParseToken(accessToken, oauth2_provider.DefaultSigningKey) if err != nil { log.Trace("oauth2.ParseToken: %v", err) return 0 @@ -40,7 +40,7 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 { if grant, err = auth_model.GetOAuth2GrantByID(ctx, token.GrantID); err != nil || grant == nil { return 0 } - if token.Type != oauth2.TypeAccessToken { + if token.Kind != oauth2_provider.KindAccessToken { return 0 } if token.ExpiresAt.Before(time.Now()) || token.IssuedAt.After(time.Now()) { diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go index 5c2568154863..313f375281b2 100644 --- a/services/auth/source/oauth2/init.go +++ b/services/auth/source/oauth2/init.go @@ -30,10 +30,6 @@ const ProviderHeaderKey = "gitea-oauth2-provider" // Init initializes the oauth source func Init(ctx context.Context) error { - if err := InitSigningKey(); err != nil { - return err - } - // Lock our mutex gothRWMutex.Lock() diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go new file mode 100644 index 000000000000..00c960caf2ee --- /dev/null +++ b/services/oauth2_provider/access_token.go @@ -0,0 +1,214 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package oauth2_provider //nolint + +import ( + "context" + "fmt" + + auth "code.gitea.io/gitea/models/auth" + org_model "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/golang-jwt/jwt/v5" +) + +// AccessTokenErrorCode represents an error code specified in RFC 6749 +// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 +type AccessTokenErrorCode string + +const ( + // AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749 + AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request" + // AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749 + AccessTokenErrorCodeInvalidClient = "invalid_client" + // AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749 + AccessTokenErrorCodeInvalidGrant = "invalid_grant" + // AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749 + AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client" + // AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749 + AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type" + // AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749 + AccessTokenErrorCodeInvalidScope = "invalid_scope" +) + +// AccessTokenError represents an error response specified in RFC 6749 +// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 +type AccessTokenError struct { + ErrorCode AccessTokenErrorCode `json:"error" form:"error"` + ErrorDescription string `json:"error_description"` +} + +// Error returns the error message +func (err AccessTokenError) Error() string { + return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription) +} + +// TokenType specifies the kind of token +type TokenType string + +const ( + // TokenTypeBearer represents a token type specified in RFC 6749 + TokenTypeBearer TokenType = "bearer" + // TokenTypeMAC represents a token type specified in RFC 6749 + TokenTypeMAC = "mac" +) + +// AccessTokenResponse represents a successful access token response +// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2 +type AccessTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType TokenType `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + IDToken string `json:"id_token,omitempty"` +} + +func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) { + if setting.OAuth2.InvalidateRefreshTokens { + if err := grant.IncreaseCounter(ctx); err != nil { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidGrant, + ErrorDescription: "cannot increase the grant counter", + } + } + } + // generate access token to access the API + expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime) + accessToken := &Token{ + GrantID: grant.ID, + Kind: KindAccessToken, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), + }, + } + signedAccessToken, err := accessToken.SignToken(serverKey) + if err != nil { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot sign token", + } + } + + // generate refresh token to request an access token after it expired later + refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime() + refreshToken := &Token{ + GrantID: grant.ID, + Counter: grant.Counter, + Kind: KindRefreshToken, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(refreshExpirationDate), + }, + } + signedRefreshToken, err := refreshToken.SignToken(serverKey) + if err != nil { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot sign token", + } + } + + // generate OpenID Connect id_token + signedIDToken := "" + if grant.ScopeContains("openid") { + app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) + if err != nil { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot find application", + } + } + user, err := user_model.GetUserByID(ctx, grant.UserID) + if err != nil { + if user_model.IsErrUserNotExist(err) { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot find user", + } + } + log.Error("Error loading user: %v", err) + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "server error", + } + } + + idToken := &OIDCToken{ + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), + Issuer: setting.AppURL, + Audience: []string{app.ClientID}, + Subject: fmt.Sprint(grant.UserID), + }, + Nonce: grant.Nonce, + } + if grant.ScopeContains("profile") { + idToken.Name = user.GetDisplayName() + idToken.PreferredUsername = user.Name + idToken.Profile = user.HTMLURL() + idToken.Picture = user.AvatarLink(ctx) + idToken.Website = user.Website + idToken.Locale = user.Language + idToken.UpdatedAt = user.UpdatedUnix + } + if grant.ScopeContains("email") { + idToken.Email = user.Email + idToken.EmailVerified = user.IsActive + } + if grant.ScopeContains("groups") { + groups, err := GetOAuthGroupsForUser(ctx, user) + if err != nil { + log.Error("Error getting groups: %v", err) + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "server error", + } + } + idToken.Groups = groups + } + + signedIDToken, err = idToken.SignToken(clientKey) + if err != nil { + return nil, &AccessTokenError{ + ErrorCode: AccessTokenErrorCodeInvalidRequest, + ErrorDescription: "cannot sign token", + } + } + } + + return &AccessTokenResponse{ + AccessToken: signedAccessToken, + TokenType: TokenTypeBearer, + ExpiresIn: setting.OAuth2.AccessTokenExpirationTime, + RefreshToken: signedRefreshToken, + IDToken: signedIDToken, + }, nil +} + +// returns a list of "org" and "org:team" strings, +// that the given user is a part of. +func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User) ([]string, error) { + orgs, err := org_model.GetUserOrgsList(ctx, user) + if err != nil { + return nil, fmt.Errorf("GetUserOrgList: %w", err) + } + + var groups []string + for _, org := range orgs { + groups = append(groups, org.Name) + teams, err := org.LoadTeams(ctx) + if err != nil { + return nil, fmt.Errorf("LoadTeams: %w", err) + } + for _, team := range teams { + if team.IsMember(ctx, user.ID) { + groups = append(groups, org.Name+":"+team.LowerName) + } + } + } + return groups, nil +} diff --git a/services/oauth2_provider/init.go b/services/oauth2_provider/init.go new file mode 100644 index 000000000000..e5958099a6b3 --- /dev/null +++ b/services/oauth2_provider/init.go @@ -0,0 +1,19 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package oauth2_provider //nolint + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" +) + +// Init initializes the oauth source +func Init(ctx context.Context) error { + if !setting.OAuth2.Enabled { + return nil + } + + return InitSigningKey() +} diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/oauth2_provider/jwtsigningkey.go similarity index 99% rename from services/auth/source/oauth2/jwtsigningkey.go rename to services/oauth2_provider/jwtsigningkey.go index 070fffe60f7f..6c668db46357 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/oauth2_provider/jwtsigningkey.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package oauth2 +package oauth2_provider //nolint import ( "crypto/ecdsa" diff --git a/services/auth/source/oauth2/token.go b/services/oauth2_provider/token.go similarity index 83% rename from services/auth/source/oauth2/token.go rename to services/oauth2_provider/token.go index 3405619d3fa5..b71b11906e5e 100644 --- a/services/auth/source/oauth2/token.go +++ b/services/oauth2_provider/token.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package oauth2 +package oauth2_provider //nolint import ( "fmt" @@ -12,29 +12,22 @@ import ( "github.com/golang-jwt/jwt/v5" ) -// ___________ __ -// \__ ___/___ | | __ ____ ____ -// | | / _ \| |/ // __ \ / \ -// | |( <_> ) <\ ___/| | \ -// |____| \____/|__|_ \\___ >___| / -// \/ \/ \/ - // Token represents an Oauth grant -// TokenType represents the type of token for an oauth application -type TokenType int +// TokenKind represents the type of token for an oauth application +type TokenKind int const ( - // TypeAccessToken is a token with short lifetime to access the api - TypeAccessToken TokenType = 0 - // TypeRefreshToken is token with long lifetime to refresh access tokens obtained by the client - TypeRefreshToken = iota + // KindAccessToken is a token with short lifetime to access the api + KindAccessToken TokenKind = 0 + // KindRefreshToken is token with long lifetime to refresh access tokens obtained by the client + KindRefreshToken = iota ) // Token represents a JWT token used to authenticate a client type Token struct { GrantID int64 `json:"gnt"` - Type TokenType `json:"tt"` + Kind TokenKind `json:"tt"` Counter int64 `json:"cnt,omitempty"` jwt.RegisteredClaims } diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index b1acf90d147d..b32d365b04d1 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/routers/web/auth" + oauth2_provider "code.gitea.io/gitea/services/oauth2_provider" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -177,7 +177,7 @@ func TestAccessTokenExchangeWithoutPKCE(t *testing.T) { "code": "authcode", }) resp := MakeRequest(t, req, http.StatusBadRequest) - parsedError := new(auth.AccessTokenError) + parsedError := new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription) @@ -195,7 +195,7 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp := MakeRequest(t, req, http.StatusBadRequest) - parsedError := new(auth.AccessTokenError) + parsedError := new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "invalid_client", string(parsedError.ErrorCode)) assert.Equal(t, "cannot load client with client id: '???'", parsedError.ErrorDescription) @@ -210,7 +210,7 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "invalid client secret", parsedError.ErrorDescription) @@ -225,7 +225,7 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "unexpected redirect URI", parsedError.ErrorDescription) @@ -240,7 +240,7 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "client is not authorized", parsedError.ErrorDescription) @@ -255,7 +255,7 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unsupported_grant_type", string(parsedError.ErrorCode)) assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription) @@ -292,7 +292,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) { }) req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==") resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError := new(auth.AccessTokenError) + parsedError := new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "invalid client secret", parsedError.ErrorDescription) @@ -305,7 +305,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) { "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "invalid_client", string(parsedError.ErrorCode)) assert.Equal(t, "cannot load client with client id: ''", parsedError.ErrorDescription) @@ -319,7 +319,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) { }) req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9") resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "invalid_request", string(parsedError.ErrorCode)) assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription) @@ -333,7 +333,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) { }) req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9") resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "invalid_request", string(parsedError.ErrorCode)) assert.Equal(t, "client_secret in request body inconsistent with Authorization header", parsedError.ErrorDescription) @@ -371,7 +371,7 @@ func TestRefreshTokenInvalidation(t *testing.T) { "refresh_token": parsed.RefreshToken, }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError := new(auth.AccessTokenError) + parsedError := new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "invalid_client", string(parsedError.ErrorCode)) assert.Equal(t, "invalid empty client secret", parsedError.ErrorDescription) @@ -384,7 +384,7 @@ func TestRefreshTokenInvalidation(t *testing.T) { "refresh_token": "UNEXPECTED", }) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "unable to parse refresh token", parsedError.ErrorDescription) @@ -414,7 +414,7 @@ func TestRefreshTokenInvalidation(t *testing.T) { // repeat request should fail req.Body = io.NopCloser(bytes.NewReader(bs)) resp = MakeRequest(t, req, http.StatusBadRequest) - parsedError = new(auth.AccessTokenError) + parsedError = new(oauth2_provider.AccessTokenError) assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError)) assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode)) assert.Equal(t, "token was already used", parsedError.ErrorDescription) From a989404e23f736e7ce1c71e7105506e3bc6cdd76 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Wed, 2 Oct 2024 12:37:16 +0800 Subject: [PATCH 03/17] Fix javascript error when an anonymous user visiting migration page (#32144) This PR fixes javascript errors when an anonymous user visits the migration page. It also makes task view checking more restrictive. The router moved from `/user/task/{id}/status` to `/username/reponame/-/migrate/status` because it's a migrate status. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> --- models/admin/task.go | 21 ----------- routers/web/repo/migrate.go | 38 +++++++++++++++++++ routers/web/user/task.go | 53 --------------------------- routers/web/web.go | 8 +++- services/context/repo.go | 5 ++- templates/repo/migrate/migrating.tmpl | 2 +- web_src/js/features/repo-migrate.ts | 8 ++-- 7 files changed, 53 insertions(+), 82 deletions(-) delete mode 100644 routers/web/user/task.go diff --git a/models/admin/task.go b/models/admin/task.go index c8bc95f98142..10f8e6d57046 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -179,27 +179,6 @@ func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) { return &task, nil } -// GetMigratingTaskByID returns the migrating task by repo's id -func GetMigratingTaskByID(ctx context.Context, id, doerID int64) (*Task, *migration.MigrateOptions, error) { - task := Task{ - ID: id, - DoerID: doerID, - Type: structs.TaskTypeMigrateRepo, - } - has, err := db.GetEngine(ctx).Get(&task) - if err != nil { - return nil, nil, err - } else if !has { - return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type} - } - - var opts migration.MigrateOptions - if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil { - return nil, nil, err - } - return &task, &opts, nil -} - // CreateTask creates a task on database func CreateTask(ctx context.Context, task *Task) error { return db.Insert(ctx, task) diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index 31a65c65ea26..3eaf05f38335 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -15,6 +15,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -288,3 +289,40 @@ func MigrateCancelPost(ctx *context.Context) { } ctx.Redirect(ctx.Repo.Repository.Link()) } + +// MigrateStatus returns migrate task's status +func MigrateStatus(ctx *context.Context) { + task, err := admin_model.GetMigratingTask(ctx, ctx.Repo.Repository.ID) + if err != nil { + if admin_model.IsErrTaskDoesNotExist(err) { + ctx.JSON(http.StatusNotFound, map[string]any{ + "err": "task does not exist or you do not have access to this task", + }) + return + } + log.Error("GetMigratingTask: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]any{ + "err": http.StatusText(http.StatusInternalServerError), + }) + return + } + + message := task.Message + + if task.Message != "" && task.Message[0] == '{' { + // assume message is actually a translatable string + var translatableMessage admin_model.TranslatableMessage + if err := json.Unmarshal([]byte(message), &translatableMessage); err != nil { + translatableMessage = admin_model.TranslatableMessage{ + Format: "migrate.migrating_failed.error", + Args: []any{task.Message}, + } + } + message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...) + } + + ctx.JSON(http.StatusOK, map[string]any{ + "status": task.Status, + "message": message, + }) +} diff --git a/routers/web/user/task.go b/routers/web/user/task.go deleted file mode 100644 index 475ef16212f0..000000000000 --- a/routers/web/user/task.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -import ( - "net/http" - "strconv" - - admin_model "code.gitea.io/gitea/models/admin" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/services/context" -) - -// TaskStatus returns task's status -func TaskStatus(ctx *context.Context) { - task, opts, err := admin_model.GetMigratingTaskByID(ctx, ctx.PathParamInt64("task"), ctx.Doer.ID) - if err != nil { - if admin_model.IsErrTaskDoesNotExist(err) { - ctx.JSON(http.StatusNotFound, map[string]any{ - "error": "task `" + strconv.FormatInt(ctx.PathParamInt64("task"), 10) + "` does not exist", - }) - return - } - ctx.JSON(http.StatusInternalServerError, map[string]any{ - "err": err, - }) - return - } - - message := task.Message - - if task.Message != "" && task.Message[0] == '{' { - // assume message is actually a translatable string - var translatableMessage admin_model.TranslatableMessage - if err := json.Unmarshal([]byte(message), &translatableMessage); err != nil { - translatableMessage = admin_model.TranslatableMessage{ - Format: "migrate.migrating_failed.error", - Args: []any{task.Message}, - } - } - message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...) - } - - ctx.JSON(http.StatusOK, map[string]any{ - "status": task.Status, - "message": message, - "repo-id": task.RepoID, - "repo-name": opts.RepoName, - "start": task.StartTime, - "end": task.EndTime, - }) -} diff --git a/routers/web/web.go b/routers/web/web.go index af46c36fe7d2..69258bca1818 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -669,7 +669,6 @@ func registerRoutes(m *web.Router) { m.Get("/forgot_password", auth.ForgotPasswd) m.Post("/forgot_password", auth.ForgotPasswdPost) m.Post("/logout", auth.SignOut) - m.Get("/task/{task}", reqSignIn, user.TaskStatus) m.Get("/stopwatches", reqSignIn, user.GetStopwatches) m.Get("/search", ignExploreSignIn, user.Search) m.Group("/oauth2", func() { @@ -1042,6 +1041,13 @@ func registerRoutes(m *web.Router) { }, ignSignIn, context.UserAssignmentWeb(), context.OrgAssignment()) // end "/{username}/-": packages, projects, code + m.Group("/{username}/{reponame}/-", func() { + m.Group("/migrate", func() { + m.Get("/status", repo.MigrateStatus) + }) + }, ignSignIn, context.RepoAssignment, reqRepoCodeReader) + // end "/{username}/{reponame}/-": migrate + m.Group("/{username}/{reponame}/settings", func() { m.Group("", func() { m.Combo("").Get(repo_setting.Settings). diff --git a/services/context/repo.go b/services/context/repo.go index c001255283ae..0072b63b7c99 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -614,7 +614,10 @@ func RepoAssignment(ctx *Context) context.CancelFunc { } } - isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || ctx.Link == ctx.Repo.RepoLink+"/settings" || strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/") + isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || + ctx.Link == ctx.Repo.RepoLink+"/settings" || + strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/") || + ctx.Link == ctx.Repo.RepoLink+"/-/migrate/status" // Disable everything when the repo is being created if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() { diff --git a/templates/repo/migrate/migrating.tmpl b/templates/repo/migrate/migrating.tmpl index ed03db2ebd7a..bc07b488f242 100644 --- a/templates/repo/migrate/migrating.tmpl +++ b/templates/repo/migrate/migrating.tmpl @@ -7,7 +7,7 @@ {{template "base/alert" .}} <div class="home"> <div class="ui stackable middle very relaxed page grid"> - <div id="repo_migrating" class="sixteen wide center aligned centered column" data-migrating-task-id="{{.MigrateTask.ID}}"> + <div id="repo_migrating" class="sixteen wide center aligned centered column" data-migrating-repo-link="{{.Link}}"> <div> <img src="{{AssetUrlPrefix}}/img/loading.png"> </div> diff --git a/web_src/js/features/repo-migrate.ts b/web_src/js/features/repo-migrate.ts index dc361779409b..b75289feec9f 100644 --- a/web_src/js/features/repo-migrate.ts +++ b/web_src/js/features/repo-migrate.ts @@ -1,19 +1,17 @@ import {hideElem, showElem} from '../utils/dom.ts'; import {GET, POST} from '../modules/fetch.ts'; -const {appSubUrl} = window.config; - export function initRepoMigrationStatusChecker() { const repoMigrating = document.querySelector('#repo_migrating'); if (!repoMigrating) return; - document.querySelector('#repo_migrating_retry').addEventListener('click', doMigrationRetry); + document.querySelector('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry); - const task = repoMigrating.getAttribute('data-migrating-task-id'); + const repoLink = repoMigrating.getAttribute('data-migrating-repo-link'); // returns true if the refresh still needs to be called after a while const refresh = async () => { - const res = await GET(`${appSubUrl}/user/task/${task}`); + const res = await GET(`${repoLink}/-/migrate/status`); if (res.status !== 200) return true; // continue to refresh if network error occurs const data = await res.json(); From 4416a4199b3e6a1181c73893345be4ca8343d9b8 Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Thu, 3 Oct 2024 00:30:51 +0000 Subject: [PATCH 04/17] [skip ci] Updated translations via Crowdin --- options/locale/locale_ja-JP.ini | 1 + options/locale/locale_pt-PT.ini | 2 ++ 2 files changed, 3 insertions(+) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 3a80c741db4f..2e861df207ba 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1926,6 +1926,7 @@ pulls.delete.text=本当にこのプルリクエストを削除しますか? ( pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ <strong>%[1]s</strong> にプッシュしました pull.deleted_branch=(削除済み):%s +pull.agit_documentation=AGitに関するドキュメントを確認する comments.edit.already_changed=コメントの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index e423832e9ee1..cb521fe58c99 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1040,6 +1040,7 @@ issue_labels_helper=Escolha um conjunto de rótulos para as questões. license=Licença license_helper=Escolha um ficheiro de licença. license_helper_desc=Uma licença rege o que os outros podem, ou não, fazer com o seu código fonte. Não tem a certeza sobre qual a mais indicada para o seu trabalho? Veja: <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença.</a> +multiple_licenses=Múltiplas licenças object_format=Formato dos elementos object_format_helper=Formato dos elementos do repositório. Não poderá ser alterado mais tarde. SHA1 é o mais compatível. readme=README @@ -2941,6 +2942,7 @@ dashboard.start_schedule_tasks=Iniciar tarefas de agendamento das operações dashboard.sync_branch.started=Sincronização de ramos iniciada dashboard.sync_tag.started=Sincronização de etiquetas iniciada dashboard.rebuild_issue_indexer=Reconstruir indexador de questões +dashboard.sync_repo_licenses=Sincronizar licenças do repositório users.user_manage_panel=Gestão das contas de utilizadores users.new_account=Criar conta de utilizador From 0b1b4030e66b1312fc9c0270f128bcf368f14178 Mon Sep 17 00:00:00 2001 From: sommerf-lf <159693954+sommerf-lf@users.noreply.github.com> Date: Thu, 3 Oct 2024 03:00:56 +0200 Subject: [PATCH 05/17] add {{TEST_MINIO_ENDPOINT}} for local testing "with/without" docker + fix pgsql testing doc (#32105) while testing i found out that testing locally as documented in the changed README.md for pgsql isn't working because of the minio dependency. reworked this to by default be still docker, but allow for for local with only minio in docker and testing on bare metal. also depending on this: fixed docs for running pgsql test Closes: #32168 (by changing documentation for pgsql tests) Closes: #32169 (by changing documentation, Makefile & pgsql.ini.tmpl: adding {{TEST_MINIO_ENDPOINT}}) sry for the combined pr, but when testing I ran into this issue and first thought they were related and now finally address the same problem: not beeing able to run pgsql integration tests as described in the according README.md --- Makefile | 2 ++ tests/integration/README.md | 8 ++++++-- tests/integration/README_ZH.md | 12 ++++++++---- tests/pgsql.ini.tmpl | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 065b66d28c66..b9e940b2485a 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,7 @@ TEST_PGSQL_DBNAME ?= testgitea TEST_PGSQL_USERNAME ?= postgres TEST_PGSQL_PASSWORD ?= postgres TEST_PGSQL_SCHEMA ?= gtestschema +TEST_MINIO_ENDPOINT ?= minio:9000 TEST_MSSQL_HOST ?= mssql:1433 TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa @@ -574,6 +575,7 @@ generate-ini-pgsql: -e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \ -e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ -e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \ + -e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \ -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ diff --git a/tests/integration/README.md b/tests/integration/README.md index f6f74ca21ff9..e673bca228ec 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -56,11 +56,15 @@ TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root T ## Run pgsql integration tests Setup a pgsql database inside docker ``` -docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container) +docker run -e "POSTGRES_DB=test" -e "POSTGRES_USER=postgres" -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container) +``` +Setup minio inside docker +``` +docker run --rm -p 9000:9000 -e MINIO_ROOT_USER=123456 -e MINIO_ROOT_PASSWORD=12345678 --name minio bitnami/minio:2023.8.31 ``` Start tests based on the database container ``` -TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +TEST_MINIO_ENDPOINT=localhost:9000 TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=postgres TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql ``` ## Run mssql integration tests diff --git a/tests/integration/README_ZH.md b/tests/integration/README_ZH.md index 6aea4ab212ba..a6768fd0f3c5 100644 --- a/tests/integration/README_ZH.md +++ b/tests/integration/README_ZH.md @@ -42,7 +42,7 @@ make test-sqlite ## 如何使用 mysql 数据库进行集成测试 首先在docker容器里部署一个 mysql 数据库 ``` -docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:8 #(just ctrl-c to stop db and clean the container) +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:8 #(just ctrl-c to stop db and clean the container) ``` 之后便可以基于这个数据库进行集成测试 ``` @@ -52,17 +52,21 @@ TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root T ## 如何使用 pgsql 数据库进行集成测试 同上,首先在 docker 容器里部署一个 pgsql 数据库 ``` -docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:14 #(just ctrl-c to stop db and clean the container) +docker run -e "POSTGRES_DB=test" -e "POSTGRES_USER=postgres" -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container) +``` +在docker内设置minio +``` +docker run --rm -p 9000:9000 -e MINIO_ROOT_USER=123456 -e MINIO_ROOT_PASSWORD=12345678 --name minio bitnami/minio:2023.8.31 ``` 之后便可以基于这个数据库进行集成测试 ``` -TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +TEST_MINIO_ENDPOINT=localhost:9000 TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=postgres TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql ``` ## Run mssql integration tests 同上,首先在 docker 容器里部署一个 mssql 数据库 ``` -docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) ``` 之后便可以基于这个数据库进行集成测试 ``` diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 6b54f790c52a..695662c2e9d2 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -115,7 +115,7 @@ MINIO_BASE_PATH = repo-avatars/ [storage] STORAGE_TYPE = minio SERVE_DIRECT = false -MINIO_ENDPOINT = minio:9000 +MINIO_ENDPOINT = {{TEST_MINIO_ENDPOINT}} MINIO_ACCESS_KEY_ID = 123456 MINIO_SECRET_ACCESS_KEY = 12345678 MINIO_BUCKET = gitea From d266d190bd744b7b6f572bf69a42013e21b9be62 Mon Sep 17 00:00:00 2001 From: Bruno Sofiato <bruno.sofiato@gmail.com> Date: Thu, 3 Oct 2024 13:03:36 -0300 Subject: [PATCH 06/17] Fixed race condition when deleting documents by repoId in ElasticSearch (#32185) Resolves #32184 --------- Signed-off-by: Bruno Sofiato <bruno.sofiato@gmail.com> --- .../code/elasticsearch/elasticsearch.go | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index d64d99433d98..5c01034450be 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -20,6 +20,7 @@ import ( indexer_internal "code.gitea.io/gitea/modules/indexer/internal" inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/typesniffer" @@ -197,8 +198,33 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st return nil } -// Delete deletes indexes by ids +// Delete entries by repoId func (b *Indexer) Delete(ctx context.Context, repoID int64) error { + if err := b.doDelete(ctx, repoID); err != nil { + // Maybe there is a conflict during the delete operation, so we should retry after a refresh + log.Warn("Deletion of entries of repo %v within index %v was erroneus. Trying to refresh index before trying again", repoID, b.inner.VersionedIndexName(), err) + if err := b.refreshIndex(ctx); err != nil { + return err + } + if err := b.doDelete(ctx, repoID); err != nil { + log.Error("Could not delete entries of repo %v within index %v", repoID, b.inner.VersionedIndexName()) + return err + } + } + return nil +} + +func (b *Indexer) refreshIndex(ctx context.Context) error { + if _, err := b.inner.Client.Refresh(b.inner.VersionedIndexName()).Do(ctx); err != nil { + log.Error("Error while trying to refresh index %v", b.inner.VersionedIndexName(), err) + return err + } + + return nil +} + +// Delete entries by repoId +func (b *Indexer) doDelete(ctx context.Context, repoID int64) error { _, err := b.inner.Client.DeleteByQuery(b.inner.VersionedIndexName()). Query(elastic.NewTermsQuery("repo_id", repoID)). Do(ctx) From 0bd75390f535c2f7f6422117476c953a178103ba Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Fri, 4 Oct 2024 00:30:54 +0000 Subject: [PATCH 07/17] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.ini | 1374 +++++++++++++++++++++++++++++++ 1 file changed, 1374 insertions(+) create mode 100644 options/locale/locale_ga-IE.ini diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini new file mode 100644 index 000000000000..4f579c0dd74e --- /dev/null +++ b/options/locale/locale_ga-IE.ini @@ -0,0 +1,1374 @@ +home=Baile +dashboard=Deais +explore=Iniúch +help=Cabhair +logo=Lógó +sign_in=Sínigh isteach +sign_in_with_provider=Sínigh isteach le %s +sign_in_or=nó +sign_out=Sínigh amach +sign_up=Cláraigh +link_account=Cuntas Nasc +register=Cláraigh +version=Leagan +powered_by=Cumhachtaithe ag %s +page=Leathanach +template=Teimpléad +language=Teanga +notifications=Fógraí +active_stopwatch=Rianaitheoir Ama Gníomhach +tracked_time_summary=Achoimre ar an am rianaithe bunaithe ar scagairí an liosta eisiúna +create_new=Cruthaigh... +user_profile_and_more=Próifíl agus Socruithe... +signed_in_as=Sínithe isteach mar +enable_javascript=Éilíonn JavaScript ar an suíomh Gréasáin seo. +toc=Tábla na nÁbhar +licenses=Ceadúnais +return_to_gitea=Fill ar Gitea +more_items=Tuilleadh míreanna + +username=Ainm úsáideora +email=Seoladh ríomhphoist +password=Pasfhocal +access_token=Comhartha Rochtana +re_type=Deimhnigh Pasfhocal +captcha=CAPTCHA +twofa=Fíordheimhniú Dhá-Fhachtóir +twofa_scratch=Cód Scratch Dhá-Fhachtóra +passcode=Paschód + +webauthn_insert_key=Cuir isteach d'eochair slándála +webauthn_sign_in=Brúigh an cnaipe ar d'eochair slándála. Mura bhfuil aon chnaipe ag d'eochair slándála, cuir isteach é arís. +webauthn_press_button=Brúigh an cnaipe ar d'eochair slándála le do thoil… +webauthn_use_twofa=Úsáid cód dhá fhachtóir ó do ghuthán +webauthn_error=Ní fhéadfaí do eochair slándála a léamh. +webauthn_unsupported_browser=Ní thacaíonn do bhrabhsálaí le WebAuthn faoi láthair. +webauthn_error_unknown=Tharla earráid anaithnid. Déan iarracht arís. +webauthn_error_insecure=Ní thacaíonn WebAuthn ach le naisc slán. Le haghaidh tástála thar HTTP, is féidir leat an bunús “localhost” nó "127.0.0.1" a úsáid +webauthn_error_unable_to_process=Ní fhéadfadh an freastalaí d'iarratas a phróiseáil. +webauthn_error_duplicated=Ní cheadaítear an eochair slándála don iarratas seo. Déan cinnte le do thoil nach bhfuil an eochair cláraithe cheana féin. +webauthn_error_empty=Ní mór duit ainm a shocrú don eochair seo. +webauthn_error_timeout=Sroicheadh an teorainn ama sula bhféadfaí d’eochair a léamh. Athlódáil an leathanach seo, le do thoil, agus déan iarracht arís. +webauthn_reload=Athlódáil + +repository=Stór +organization=Eagraíocht +mirror=Scáthán +new_repo=Stór Nua +new_migrate=Imirce Nua +new_mirror=Scáthán Nua +new_fork=Forc Stór Nua +new_org=Eagraíocht Nua +new_project=Tionscadal Nua +new_project_column=Colún Nua +manage_org=Eagraíochtaí a bhainistiú +admin_panel=Riarachán Láithreáin +account_settings=Socruithe Cuntais +settings=Socruithe +your_profile=Próifíl +your_starred=Réaltaigh +your_settings=Socruithe + +all=Gach +sources=Foinsí +mirrors=Scátháin +collaborative=Comhoibritheach +forks=Forcanna + +activities=Gníomhaíochtaí +pull_requests=Iarrataí Tarraing +issues=Saincheisteanna +milestones=Clocha míle + +ok=CEART GO LEOR +cancel=Cealaigh +retry=Atriail +rerun=Ath-rith +rerun_all=Ath-rith na poist go léir +save=Sábháil +add=Cuir +add_all=Cuir Gach +remove=Bain +remove_all=Bain Gach +remove_label_str=Bain mír “%s” +edit=Cuir in eagar +view=Amharc +test=Tástáil + +enabled=Cumasaithe +disabled=Díchumasaithe +locked=Faoi ghlas + +copy=Cóipeáil +copy_url=Cóipeáil URL +copy_hash=Cóipeáil hais +copy_content=Cóipeáil ábhair +copy_branch=Ainm brainse cóipeáil +copy_success=Cóipeáil! +copy_error=Theip ar an gcóip +copy_type_unsupported=Ní féidir an cineál comhaid seo a chóipeáil + +write=Scríobh +preview=Réamhamharc +loading=Á lódáil... + +error=Earráid +error404=Níl an leathanach atá tú ag iarraidh a bhaint amach <strong>ann</strong> nó <strong>níl tú údaraithe</strong> chun é a fheiceáil. +go_back=Ar ais +invalid_data=Sonraí neamhbhailí: %v + +never=Riamh +unknown=Anaithnid + +rss_feed=Fothú RSS + +pin=Bioráin +unpin=Díphoráil + +artifacts=Déantáin +confirm_delete_artifact=An bhfuil tú cinnte gur mhaith leat an déantán '%s' a scriosadh? + +archived=Cartlann + +concept_system_global=Domhanda +concept_user_individual=Duine aonair +concept_code_repository=Stóráil +concept_user_organization=Eagraíocht + +show_timestamps=Taispeáin stampaí ama +show_log_seconds=Taispeáin soicindí +show_full_screen=Taispeáin scáileán iomlán +download_logs=Íoslódáil logaí + +confirm_delete_selected=Deimhnigh chun gach earra roghnaithe a scriosadh? + +name=Ainm +value=Luach + +filter=Scagaire +filter.clear=Scagaire Soiléir +filter.is_archived=Cartlannaithe +filter.not_archived=Gan Cartlannaithe +filter.is_fork=Forcailte +filter.not_fork=Gan Forcailte +filter.is_mirror=Scáthánaithe +filter.not_mirror=Gan Scáthánaithe +filter.is_template=Teimpléad +filter.not_template=Gan Teimpléad +filter.public=Poiblí +filter.private=Príobháideach + +no_results_found=Níor aimsíodh aon torthaí. +internal_error_skipped=Tharla earráid inmheánach ach éirithe as: %s + +[search] +search=Cuardaigh... +type_tooltip=Cineál cuardaigh +fuzzy=Doiléir +fuzzy_tooltip=Cuir san áireamh torthaí a mheaitseálann an téarma cuardaigh go dlúth freisin +exact=Beacht +exact_tooltip=Ní chuir san áireamh ach torthaí a mheaitseálann leis an téarma +repo_kind=Cuardaigh stórtha... +user_kind=Cuardaigh úsáideoirí... +org_kind=Cuardaigh eagraíochtaí... +team_kind=Cuardaigh foirne... +code_kind=Cód cuardaigh... +code_search_unavailable=Níl cuardach cód ar fáil faoi láthair. Déan teagmháil le riarthóir an láithreáin. +code_search_by_git_grep=Soláthraíonn “git grep” torthaí cuardaigh cód reatha. D'fhéadfadh torthaí níos fearr a bheith ann má chuireann riarthóir an láithreáin ar chumas Innéacsaithe +package_kind=Cuardaigh pacáistí... +project_kind=Cuardaigh tionscadail... +branch_kind=Cuardaigh brainsí... +tag_kind=Cuardaigh clibeanna... +tag_tooltip=Cuardaigh clibeanna meaitseála. Úsáid '%' le haon seicheamh uimhreacha a mheaitseáil. +commit_kind=Cuardaigh tiomáintí... +runner_kind=Cuardaigh reathaithe... +no_results=Níl aon torthaí meaitseála le fáil. +issue_kind=Saincheisteanna cuardaigh... +pull_kind=Cuardaigh iarratais tarraingthe... +keyword_search_unavailable=Níl cuardach de réir eochairfhocal ar fáil faoi láthair. Déan teagmháil le riarthóir an láithreáin. + +[aria] +navbar=Barra Nascleanúint +footer=Buntásc +footer.software=Maidir le Bogearraí +footer.links=Naisc + +[heatmap] +number_of_contributions_in_the_last_12_months=%s ranníocaíochtaí le 12 mhí anuas +no_contributions=Gan ranníocaíochtaí +less=Níos lú +more=Níos mó + +[editor] +buttons.heading.tooltip=Cuir ceannteideal leis +buttons.bold.tooltip=Cuir téacs trom leis +buttons.italic.tooltip=Cuir téacs iodálach leis +buttons.quote.tooltip=Téacs luaigh +buttons.code.tooltip=Cuir cód leis +buttons.link.tooltip=Cuir nasc leis +buttons.list.unordered.tooltip=Cuir liosta piléar leis +buttons.list.ordered.tooltip=Cuir liosta uimhrithe +buttons.list.task.tooltip=Cuir liosta tascanna leis +buttons.mention.tooltip=Luaigh úsáideoir nó foireann +buttons.ref.tooltip=Déan tagairt d'eisiúint nó iarratas tarraingthe +buttons.switch_to_legacy.tooltip=Úsáid an eagarthóir oidhreachta ina ionad +buttons.enable_monospace_font=Cumasaigh cló monospace +buttons.disable_monospace_font=Díchumasaigh cló monospace + +[filter] +string.asc=A - Z +string.desc=Z - A + +[error] +occurred=Tharla earráid +report_message=Má chreideann tú gur fabht Gitea é seo, déan cuardach le haghaidh ceisteanna ar <a href="%s" target="_blank">GitHub</a> nó oscail eagrán nua más gá. +not_found=Ní raibh an sprioc in ann a fháil. +network_error=Earráid líonra + +[startpage] +app_desc=Seirbhís Git gan phian, féin-óstáil +install=Éasca a shuiteáil +install_desc=Níl ort ach <a target="_blank" rel="noopener noreferrer" href="%[1]s">rith an dénártha</a> do d'ardán, seol é le <a target="_blank" rel="noopener noreferrer " href="%[2]s">Docker</a>, nó faigh <a target="_blank" rel="noopener noreferrer" href="%[3]s">pacáilte</a> é. +platform=Tras-ardán +platform_desc=Ritheann Gitea áit ar <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">bith is féidir le Go</a> tiomsú le haghaidh: Windows, macOS, Linux, ARM, srl Roghnaigh an ceann is breá leat! +lightweight=Éadrom +lightweight_desc=Tá íosta riachtanais íseal ag Gitea agus is féidir leo rith ar Raspberry Pi saor. Sábháil fuinneamh do mheaisín! +license=Foinse Oscailte +license_desc=Téigh go bhfaighidh <a target="_blank" rel="noopener noreferrer" href="%[1]s">%[2]s</a>! Bí linn trí <a target="_blank" rel="noopener noreferrer" href="%[3]s">cur leis</a> chun an tionscadal seo a fheabhsú fós. Ná bíodh cúthail ort a bheith i do rannpháirtí! + +[install] +install=Suiteáil +title=Cumraíocht Tosaigh +docker_helper=Má ritheann tú Gitea taobh istigh de Docker, léigh an <a target="_blank" rel="noopener noreferrer" href="%s">doiciméadúchán</a> roimh aon socruithe a athrú. +require_db_desc=Éilíonn Gitea MySQL, PostgreSQL, MSSQL, SQLite3 nó TiDB (prótacal MySQL). +db_title=Socruithe Bunachar Sonraí +db_type=Cineál Bunachar Sonraí +host=Óstach +user=Ainm úsáideora +password=Pasfhocal +db_name=Ainm Bunachar Sonraí +db_schema=Scéim +db_schema_helper=Fág bán le haghaidh réamhshocraithe bunachar sonraí ("poiblí"). +ssl_mode=SSL +path=Cosán +sqlite_helper=Conair comhad don bhunachar sonraí SQLite3. Cuir <br>isteach cosán iomlán má reáchtáil tú Gitea mar sheirbhís. +reinstall_error=Tá tú ag iarraidh a shuiteáil i mbunachar sonraí Gitea atá ann cheana +reinstall_confirm_message=Is féidir fadhbanna iolracha a bheith ina chúis le hathshuiteáil le bunachar sonraí Gitea I bhformhór na gcásanna, ba chóir duit an "app.ini" atá agat cheana a úsáid chun Gitea a reáchtáil. Má tá a fhios agat cad atá á dhéanamh agat, deimhnigh an méid seo a leanas: +reinstall_confirm_check_1=Féadfaidh na sonraí criptithe ag an SECRET_KEY i app.ini a chailleadh: b'fhéidir nach mbeidh úsáideoirí in ann logáil isteach le 2FA/OTP & b'fhéidir nach bhfeidhmeoidh scátháin i gceart. Trí an bhosca seo a sheiceáil deimhníonn tú go bhfuil an ceart an SECRET_KEY sa chomhad reatha app.ini. +reinstall_confirm_check_2=B'fhéidir go gcaithfear na stórais agus na socruithe a athshioncronú. Trí an bhosca seo a sheiceáil deimhníonn tú go ndéanfaidh tú na crúcaí do na stórálacha agus an chomhad authorized_keys a athshioncronú de láimh. Deimhníonn tú go gcinnteoidh tú go bhfuil socruithe stórais agus scátháin ceart. +reinstall_confirm_check_3=Deimhníonn tú go bhfuil tú cinnte go bhfuil an Gitea seo ag rith leis an suíomh ceart app.ini agus go bhfuil tú cinnte go gcaithfidh tú athshuiteáil. Deimhníonn tú go n-admhaíonn tú na rioscaí thuas. +err_empty_db_path=Ní féidir cosán bunachar sonraí SQLite3 a bheith folamh. +no_admin_and_disable_registration=Ní féidir leat féinchlárú úsáideora a dhíchumasú gan cuntas riarthóra a chruthú. +err_empty_admin_password=Ní féidir le pasfhocal an riarthóra a bheith folamh. +err_empty_admin_email=Ní féidir le ríomhphost an riarthóra a bheith folamh. +err_admin_name_is_reserved=Riarthóir Tá an t-ainm úsáideora neamhbhailí, tá an t-ainm úsáideora curtha in áirithe +err_admin_name_pattern_not_allowed=Tá ainm úsáideora an riarthóra neamhbhailí, meaitseálann an t-ainm úsáideora patrún in áirithe +err_admin_name_is_invalid=Tá an t-ainm úsáideora Riarthóra neamhbhailí + +general_title=Socruithe Ginearálta +app_name=Teideal an tSuímh +app_name_helper=Is féidir leat ainm do chuideachta a iontráil anseo. +repo_path=Cosán Fréimhe an Stór +repo_path_helper=Sábhálfar stórais iargúlta Git chuig an eolaire seo. +lfs_path=Cosán Fréamh Git LFS +lfs_path_helper=Stórálfar comhaid a rianóidh Git LFS san eolaire seo. Fág folamh le díchumasú. +run_user=Rith mar Ainm Úsáideora +run_user_helper=An ainm úsáideora an chórais oibriúcháin a ritheann Gitea mar. Tabhair faoi deara go gcaithfidh rochtain a bheith ag an úsáideoir seo ar fhréamhchosán an taisclainne. +domain=Fearann Freastalaí +domain_helper=Seoladh fearainn nó óstach don fhreastalaí. +ssh_port=Port Freastalaí SSH +ssh_port_helper=Uimhir chalafoirt éisteann do fhreastalaí SSH air. Fág folamh le díchumasú. +http_port=Port Éisteachta HTTP Gitea +http_port_helper=Uimhir chalafoirt a éistfidh an freastalaí gréasáin Gitea air. +app_url=URL Bonn Gitea +app_url_helper=Seoladh bonn le haghaidh URLanna clóin HTTP(S) agus fógraí ríomhphoist. +log_root_path=Cosán Logála +log_root_path_helper=Scríofar comhaid logála chuig an eolaire seo. + +optional_title=Socruithe Roghnacha +email_title=Socruithe ríomhphoist +smtp_addr=Óstach SMTP +smtp_port=Port SMTP +smtp_from=Seol Ríomhphost Mar +smtp_from_invalid=Tá an seoladh “Seol Ríomhphost Mar” neamhbhailí +smtp_from_helper=Seoladh ríomhphoist a úsáidfidh Gitea. Cuir isteach seoladh ríomhphoist simplí nó úsáid an fhormáid "Ainm" <email@example.com>. +mailer_user=SMTP Ainm úsáideora +mailer_password=Pasfhocal SMTP +register_confirm=Deimhniú Ríomhphoist a cheangal le Clárú +mail_notify=Cumasaigh Fógraí Ríomhphoist +server_service_title=Socruithe Freastalaí agus Seirbhíse Tríú Páirtí +offline_mode=Cumasaigh Mód Áitiúil +offline_mode_popup=Díchumasaigh líonraí seachadta ábhair tríú páirtí agus freastal ar na hacmhainní go léir go háitiúil. +disable_gravatar=Díchumasaigh Gravatar +disable_gravatar_popup=Díchumasaigh foinsí abhatár Gravatar agus tríú páirtí. Úsáidfear abhatár réamhshocraithe mura n-uaslódálann úsáideoir abhatár go háitiúil. +federated_avatar_lookup=Cumasaigh Abhatáir Chónaidhme +federated_avatar_lookup_popup=Cumasaigh cuardach avatar cónaidhme ag baint úsáide as Libravatar. +disable_registration=Díchumasaigh Féin-Chlárú +disable_registration_popup=Díchumasaigh féinchlárú úsáideora. Ní bheidh ach riarthóirí in ann cuntais úsáideora nua a chruthú. +allow_only_external_registration_popup=Ceadaigh Clárú Trí Sheirbhísí Seachtracha amháin +openid_signin=Cumasaigh Síniú isteach OpenID +openid_signin_popup=Cumasaigh síniú isteach úsáideora trí OpenID. +openid_signup=Cumasaigh Féinchlárú OpenID +openid_signup_popup=Cumasaigh féinchlárú úsáideora bunaithe ar OpenID. +enable_captcha=Cumasaigh clárú CAPTCHA +enable_captcha_popup=Teastaíonn CAPTCHA le haghaidh féinchlárú úsáideora. +require_sign_in_view=Teastaíonn Sínigh isteach chun Leathanaigh Amharc +require_sign_in_view_popup=Teorainn a chur ar rochtain leathanaigh d'úsáideoirí sínithe isteach. Ní fheicfidh cuairteoirí ach na leathanaigh sínithe isteach agus clárúcháin. +admin_setting_desc=Tá cuntas riarthóra a chruthú roghnach. Beidh an chéad úsáideoir cláraithe ina rialtóir go huathoibríoch +admin_title=Socruithe Cuntas Riarthóra +admin_name=Ainm Úsáideora an Riarthóra +admin_password=Pasfhocal +confirm_password=Deimhnigh Pasfhocal +admin_email=Seoladh ríomhphoist +install_btn_confirm=Suiteáil Gitea +test_git_failed=Ní féidir ordú 'git' a thástáil: %v +sqlite3_not_available=Ní thacaíonn an leagan Gitea seo le SQLite3. Íoslódáil an leagan dénártha oifigiúil ó %s (ní an leagan 'gobuild'). +invalid_db_setting=Tá na socruithe bunachar sonraí neamhbhailí:%v +invalid_db_table=Tá an tábla bunachar sonraí "%s" neamhbhailí: %v +invalid_repo_path=Tá cosán fréimhe an stór neamhbhailí:%v +invalid_app_data_path=Tá cosán sonraí an aip neamhbhailí:%v +run_user_not_match=Ní hé an t-ainm úsáideora 'rith mar' an t-ainm úsáideora reatha: %s -> %s +internal_token_failed=Theip ar chomhartha inmheánach a ghiniúint:%v +secret_key_failed=Theip ar an eochair rúnda a ghiniúint:%v +save_config_failed=Theip ar chumraíocht a shábháil:%v +invalid_admin_setting=Tá socrú cuntas riarthóra neamhbhailí: %v +invalid_log_root_path=Tá an cosán logála neamhbhailí:%v +default_keep_email_private=Folaigh Seoltaí Ríomhphoist de réir Réamhshocrú +default_keep_email_private_popup=Folaigh seoltaí ríomhphoist cuntas úsáideora nua de réir réamhshocraithe. +default_allow_create_organization=Ceadaigh Cruthú Eagraíochtaí de réir Réamhshocrú +default_allow_create_organization_popup=Lig cuntais úsáideora nua eagraíochtaí a chruthú de réir réamhshocraithe. +default_enable_timetracking=Cumasaigh Rianú Ama de réir Réamhshocrú +default_enable_timetracking_popup=Cumasaigh rianú ama do stórais nua de réir réamhshocraithe. +no_reply_address=Fearann Ríomhphoist Folaite +no_reply_address_helper=Ainm fearainn d'úsáideoirí a bhfuil seoladh ríomhphoist i bhfolach acu. Mar shampla, logálfar an t-ainm úsáideora 'joe' i Git mar 'joe@noreply.example.org' má tá an fearainn ríomhphoist i bhfolach socraithe go 'noreply.example.org'. +password_algorithm=Algartam Hais Pasfhocal +invalid_password_algorithm=Algartam hais pasfhocail neamhbhailí +password_algorithm_helper=Socraigh an algartam hashing pasfhocal. Tá riachtanais agus neart éagsúla ag halgartaim. Tá an algartam argon2 sách slán ach úsáideann sé go leor cuimhne agus d'fhéadfadh sé a bheith míchuí do chórais bheaga. +enable_update_checker=Cumasaigh Seiceoir Nuashonraithe +enable_update_checker_helper=Seiceálacha ar eisiúintí leagan nua go tréimhsiúil trí nascadh le gitea.io. +env_config_keys=Cumraíocht Comhshaoil +env_config_keys_prompt=Cuirfear na hathróga comhshaoil seo a leanas i bhfeidhm ar do chomhad cumraíochta freisin: + +[home] +nav_menu=Roghchlár Nascleanúint +uname_holder=Ainm Úsáideora nó Seoladh Ríomhphoist +password_holder=Pasfhocal +switch_dashboard_context=Athraigh Comhthéacs an Deais +my_repos=Stórais +show_more_repos=Taispeáin níos mó stórais... +collaborative_repos=Stórais Comhoibríoch +my_orgs=Mo Eagraíochtaí +my_mirrors=Mo Scátháin +view_home=Amharc %s +filter=Scagairí Eile +filter_by_team_repositories=Scag de réir stórais foirne +feed_of=`Fotha de "%s"` + +show_archived=Cartlannaithe +show_both_archived_unarchived=Ag taispeáint idir chartlannaithe agus neamhchartlann +show_only_archived=Ag taispeáint ach na cartlannaigh +show_only_unarchived=Ag taispeáint ach na cartlannaigh neamh + +show_private=Príobháideach +show_both_private_public=Ag taispeáint poiblí agus príobháideach araon +show_only_private=Ag taispeáint príobháideach amháin +show_only_public=Ag taispeáint poiblí amháin + +issues.in_your_repos=I do stórais + +[explore] +repos=Stórais +users=Úsáideoirí +organizations=Eagraíochtaí +go_to=Téigh chuig +code=Cód +code_last_indexed_at=Innéacsaithe %s is déanaí +relevant_repositories_tooltip=Tá stórais atá forca iad nó nach bhfuil aon ábhar acu, gan aon deilbhín, agus gan aon tuairisc i bhfolach. +relevant_repositories=Níl ach stórtha ábhartha á dtaispeáint, <a href="%s">taispeáin torthaí neamhscagtha</a>. + +[auth] +create_new_account=Cláraigh Cuntas +already_have_account=An bhfuil cuntas agat cheana féin? +sign_in_now=Sínigh isteach anois! +disable_register_prompt=Tá clárú faoi dhíchumasú. Téigh i dteagmháil le do riarthóir suíomh. +disable_register_mail=Tá deimhniú ríomhphoist le haghaidh clárú faoi dhíchum +manual_activation_only=Déan teagmháil le riarthóir do tsuímh chun gníomhachtú a chur i gcrích. +remember_me=Cuimhnigh ar an nGléas seo +remember_me.compromised=Níl an comhartha logála isteach bailí níos mó a d'fhéadfadh cuntas i gcontúirt a léiriú. Seiceáil do chuntas le haghaidh gníomhaíochtaí neamhghnácha. +forgot_password_title=Dearmad ar an bPasfhocal +forgot_password=Dearmad ar an bPasfhocal? +need_account=An bhfuil cuntas ag teastáil uait? +sign_up_now=Cláraigh anois. +sign_up_successful=Cruthaíodh cuntas go rathúil. Fáilte romhat! +confirmation_mail_sent_prompt_ex=Tá ríomhphost dearbhaithe nua seolta chuig <b>%s</b>. Seiceáil do bhosca isteach laistigh den chéad %s eile chun an próiseas clárúcháin a chur i gcrích. Má tá do sheoladh ríomhphoist clárúcháin mícheart, is féidir leat síniú isteach arís agus é a athrú. +must_change_password=Nuashonraigh do phasfhocal +allow_password_change=A cheangal ar an úsáideoir pasfhocal a athrú (molta) +reset_password_mail_sent_prompt=Seoladh ríomhphost deimhnithe chu <b>ig %s</b>. Seiceáil do bhosca isteach laistigh den chéad %s eile chun an próiseas aisghabhála cuntais a chríochnú. +active_your_account=Gníomhachtaigh do chuntas +account_activated=Cuireadh cuntas gníomhachtaithe +prohibit_login=Sínigh isteach Toirmiscthe +prohibit_login_desc=Tá cosc ar do chuntas síniú isteach, déan teagmháil le do riarthóir láithreáin le do thoil. +resent_limit_prompt=D'iarr tú ríomhphost gníomhachtaithe cheana féin le déanaí. Fan 3 nóiméad le do thoil agus bain triail as arís. +has_unconfirmed_mail=Dia duit %s, tá seoladh ríomhphoist neamhdheimhnithe agat (<b>%s</b>). Mura bhfuair tú ríomhphost dearbhaithe nó mura gcaithfidh tú ceann nua a athsheoladh, cliceáil ar an gcnaipe thíos le do thoil. +change_unconfirmed_mail_address=Má tá do sheoladh ríomhphoist cláraithe mícheart, is féidir leat é a athrú anseo agus ríomhphost dearbhaithe nua a sheoladh arís. +resend_mail=Cliceáil anseo chun do r-phost gníomhachtaithe a athshe +email_not_associate=Níl baint ag an seoladh ríomhphoist le haon chuntas. +send_reset_mail=Seol Ríomhphost Aisghabháil Cuntas +reset_password=Aisghabháil Cuntas +invalid_code=Tá do chód deimhnithe neamhbhailí nó tá sé in éag. +invalid_code_forgot_password=Tá do chód deimhnithe neamhbhailí nó tá sé in éag. Cliceáil <a href="%s">anseo</a> chun seisiún nua a thosú. +invalid_password=Ní mheaitseálann do phasfhocal leis an bhfocal faire a úsáideadh chun an cuntas a chruthú. +reset_password_helper=Gnóthaigh Cuntas +reset_password_wrong_user=Tá tú sínithe isteach mar %s, ach tá an nasc aisghabhála cuntas i gceist le haghaidh %s +password_too_short=Ní féidir fad pasfhocal a bheith níos lú ná %d carachtair. +non_local_account=Ní féidir le húsáideoirí neamháitiúla a bhfocal faire a nuashonrú trí chomhéadan gréasáin Gitea. +verify=Fíoraigh +scratch_code=Cód Scratch +use_scratch_code=Úsáid cód scratch +twofa_scratch_used=D'úsáid tú do chód scratch. Tá tú atreoraithe chuig an leathanach socruithe dhá fhachtóir ionas gur féidir leat clárú do ghléas a bhaint nó cód scratch nua a ghiniúint. +twofa_passcode_incorrect=Tá do phaschód mícheart. Má chuir tú do ghléas míchuir tú, bain úsáid as do chód scratch chun síniú isteach. +twofa_scratch_token_incorrect=Tá do chód scratch mícheart. +login_userpass=Sínigh isteach +login_openid=OpenID +oauth_signup_tab=Cláraigh Cuntas Nua +oauth_signup_title=Comhlánaigh Cuntas Nua +oauth_signup_submit=Cuntas Comhlánaigh +oauth_signin_tab=Nasc leis an gCuntas Reatha +oauth_signin_title=Sínigh isteach chun Cuntas Nasctha a Údarú +oauth_signin_submit=Cuntas Nasc +oauth.signin.error=Bhí earráid ann ag próiseáil an t-iarratas ar údarú. Má leanann an earráid seo, déan teagmháil le riarthóir an láithreáin. +oauth.signin.error.access_denied=Diúltaíodh an t-iarratas ar údarú. +oauth.signin.error.temporarily_unavailable=Theip ar údarú toisc nach bhfuil an fhreastalaí fíordheimhnithe ar fáil Bain triail as arís níos déanaí. +oauth_callback_unable_auto_reg=Tá Clárú Uathoibríoch cumasaithe, ach sheol Soláthraí OAuth2 %[1]s réimsí in easnamh ar ais: %[2]s, ní raibh sé in ann cuntas a chruthú go huathoibríoch, cruthaigh nó nasc le cuntas, nó déan teagmháil le riarthóir an tsuímh. +openid_connect_submit=Ceangail +openid_connect_title=Ceangail le cuntas atá ann cheana +openid_connect_desc=Níl an URI OpenID roghnaithe ar eolas. Comhcheangail é le cuntas nua anseo. +openid_register_title=Cruthaigh cuntas nua +openid_register_desc=Níl an URI OpenID roghnaithe ar eolas. Comhcheangail é le cuntas nua anseo. +openid_signin_desc=Cuir isteach do URI OpenID. Mar shampla: alice.openid.example.org nó https://openid.example.org/alice. +disable_forgot_password_mail=Tá aisghabháil cuntas díchumasaithe toisc nach bhfuil aon ríomhphost ar bun. Téigh i dteagmháil le do riarthóir suíomh. +disable_forgot_password_mail_admin=Níl aisghabháil cuntas ar fáil ach amháin nuair a bhíonn ríomhphost ar bun. Bunaigh ríomhphost le do thoil chun aisghabháil cuntas a chumasú +email_domain_blacklisted=Ní féidir leat clárú le do sheoladh ríomhphoist. +authorize_application=Údaraigh an Feidhmchlár +authorize_redirect_notice=Déanfar tú a atreorú chuig %s má údaraíonn tú an feidhmchlár seo. +authorize_application_created_by=Chruthaigh %s an feidhmchlár seo. +authorize_application_description=Má dheonaíonn tú an rochtain, beidh sé in ann rochtain a fháil agus scríobh chuig faisnéis uile do chuntais, lena n-áirítear repos príobháideacha agus eagraíochtaí. +authorize_title=Údaraigh "%s" chun rochtain a fháil ar do chuntas? +authorization_failed=Theip ar údarú +authorization_failed_desc=Theip ar an údarú toisc gur bhraitheamar iarratas neamhbhailí. Téigh i dteagmháil le cothabhálaí an aip a rinne tú iarracht a údarú. +sspi_auth_failed=Theip ar fhíordheimhniú SSPI +password_pwned=Tá an pasfhocal a roghnaigh tú ar <a target="_blank" rel="noopener noreferrer" href="%s">liosta na bhfocal faire goidte</a> a nochtadh cheana i sáruithe sonraí poiblí. Bain triail eile as le pasfhocal eile agus smaoinigh ar an bpasfhocal seo a athrú áit eile freisin. +password_pwned_err=Ní fhéadfaí iarratas a chomhlánú ar HaveIBeenPwned +last_admin=Ní féidir leat an riarachán deireanach a bhaint. Caithfidh riarachán amháin ar a laghad a bheith ann. +signin_passkey=Sínigh isteach le passkey +back_to_sign_in=Ar ais go Sínigh Isteach + +[mail] +view_it_on=Féach air ar %s +reply=nó freagra a thabhairt ar an r-phost seo go díreach +link_not_working_do_paste=Níl ag obair? Bain triail as é a chóipeáil agus a ghreamú le do bhrabh +hi_user_x=Dia duit <b>%s</b>, + +activate_account=Gníomhachtaigh do chuntas le do thoil +activate_account.title=%s, gníomhachtaigh do chuntas le do thoil +activate_account.text_1=Dia duit <b>%[1]s</b>, go raibh maith agat as clárú ag %[2]s! +activate_account.text_2=Cliceáil ar an nasc seo a leanas chun do chuntas a ghníomhachtú laistigh de <b>%s</b>: + +activate_email=Fíoraigh do sheoladh ríomhphoist +activate_email.title=%s, fíoraigh do sheoladh ríomhphoist le do thoil +activate_email.text=Cliceáil ar an nasc seo a leanas le do sheoladh ríomhphoist a fhíorú laistigh de <b>%s</b>: + +register_notify=Fáilte go dtí %s +register_notify.title=%[1]s, fáilte go %[2]s +register_notify.text_1=is é seo do ríomhphost deimhnithe clárúcháin do %s! +register_notify.text_2=Is féidir leat logáil isteach anois trí ainm úsáideora: %s. +register_notify.text_3=Má cruthaíodh an cuntas seo duit, <a href="%s">socraigh do phasfhocal</a> ar dtús. + +reset_password=Aisghabháil do chuntas +reset_password.title=%s, d'iarr tú do chuntas a aisghabháil +reset_password.text=Cliceáil ar an nasc seo a leanas chun do chuntas a athshlánú laistigh de <b>%s</b>: + +register_success=Clárú rathúil + +issue_assigned.pull=@%[1]s shann tú don iarratas tarraingthe %[2]s i stór %[3]s. +issue_assigned.issue=@%[1]s shann tú don eisiúint %[2]s i stór %[3]s. + +issue.x_mentioned_you=Luaigh <b>@%s</b> tú: +issue.action.force_push=Bhrúigh <b>%[1]s</b> an <b>%[2]s</b> go fórsa ó %[3]s go %[4]s. +issue.action.push_1=Bhrúigh <b>@%[1]s</b> %[3]d tiomáintí go %[2]s +issue.action.push_n=<b>@%[1]s</b> brúite % [3]d tiomáintí chuig %[2]s +issue.action.close=<b>@%[1]s</b> dúnta #%[2]d. +issue.action.reopen=D'athoscail <b>@%[1]s</b> #%[2]d. +issue.action.merge=Chomhcheangail <b>@%[1]s</b> #%[2]d le %[3]s. +issue.action.approve=Cheadaigh <b>@%[1]s</b> an t-iarratas tarraingthe seo. +issue.action.reject=D'iarr <b>@%[1]s</b> athruithe ar an iarratas tarraingthe seo. +issue.action.review=Rinne <b>@%[1]s</b> trácht ar an iarratas tarraingthe seo. +issue.action.review_dismissed=Dhiúltaigh <b>@%[1]s</b> an léirmheas deiridh ó %[2]s don iarratas tarraingthe seo. +issue.action.ready_for_review=Mharcáil <b>@%[1]s</b> an t-iarratas tarraingthe seo réidh lena athbhreithniú. +issue.action.new=Chruthaigh <b>@%[1]s</b> #%[2]d. +issue.in_tree_path=I %s: + +release.new.subject=Scaoileadh %s i %s +release.new.text=D'eisigh <b>@%[1]s</b> %[2]s i %[3]s +release.title=Teideal: %s +release.note=Nóta: +release.downloads=Íoslódálacha: +release.download.zip=Cód Foinse (ZIP) +release.download.targz=Cód Foinse (TAR.GZ) + +repo.transfer.subject_to=Ba mhaith le %s "%s" a aistriú go %s +repo.transfer.subject_to_you=Ba mhaith le %s "%s" a aistriú chugat +repo.transfer.to_you=tú +repo.transfer.body=Chun glacadh leis nó a dhiúltú tabhair cuairt ar %s nó neamhaird a dhéanamh air. + +repo.collaborator.added.subject=Chuir %s le %s tú +repo.collaborator.added.text=Cuireadh tú leis mar chomhoibritheoir stórais: + +team_invite.subject=Tá cuireadh tugtha agat ag %[1]s chun dul le heagraíocht %[2]s +team_invite.text_1=Tá cuireadh tugtha ag %[1]s duit chun dul le foireann %[2]s in eagraíocht %[3]s. +team_invite.text_2=Cliceáil ar an nasc seo a leanas le do thoil chun dul isteach san fhoireann: +team_invite.text_3=Nóta: Bhí an cuireadh seo beartaithe do %[1]s. Mura raibh tú ag súil leis an gcuireadh seo, is féidir leat neamhaird a dhéanamh den ríomhphost seo. + +[modal] +yes=Tá +no=Níl +confirm=Deimhnigh +cancel=Cealaigh +modify=Nuashonraigh + +[form] +UserName=Ainm úsáideora +RepoName=Ainm stórais +Email=Seoladh ríomhphoist +Password=Pasfhocal +Retype=Deimhnigh Pasfhocal +SSHTitle=Ainm eochair SSH +HttpsUrl=URL HTTPS +PayloadUrl=URL Pálasta +TeamName=Ainm foirne +AuthName=Ainm údaraithe +AdminEmail=Ríomhphost riaracháin + +NewBranchName=Ainm brainse nua +CommitSummary=Achoimre tiomáintí +CommitMessage=Tiomantas teachtaireacht +CommitChoice=Rogha tiomanta +TreeName=Cosán comhaid +Content=Ábhar + +SSPISeparatorReplacement=Deighilteoir +SSPIDefaultLanguage=Teanga Réamhshocraithe + +require_error=` ní féidir a bheith folamh.` +alpha_dash_error=` níor cheart go mbeadh ach carachtair alfauméireacha, daingean ('-') agus béim ('_') ann. ` +alpha_dash_dot_error=` níor cheart go mbeadh ach alfa-uimhriúil, dash ('-'), cuir béim ar ('_') agus ponc ('.') carachtair ann.` +git_ref_name_error=` caithfidh gur ainm tagartha Git dea-chruthaithe é.` +size_error=` ní mór méid %s.` +min_size_error=` ní mór go mbeadh carachtar %s ar a laghad ann.` +max_size_error=` caithfidh `%s carachtar ar a mhéad a bheith ann.` +email_error=`ní seoladh ríomhphoist bailí é.` +url_error=`ní URL bailí é `"%s". ` +include_error=` ní mór fotheaghrán a bheith ann "%s".` +glob_pattern_error=` tá patrún glob neamhbhailí: %s.` +regex_pattern_error=`tá patrún regex neamhbhailí: %s.` +username_error=` ní féidir ach carachtair alfa-uimhriúla ('0-9', 'a-z', 'A-Z'), dash ('-'), béim ('_') agus ponc ('.') a bheith ann. Ní féidir tús a chur leis ná deireadh a chur le carachtair neamh-alfamanacha, agus tá cosc freisin ar charthanna neamh-alfanuimhriúla i ndiaidh a chéile.` +invalid_group_team_map_error=` tá mapáil neamhbhailí: %s` +unknown_error=Earráid anaithnid: +captcha_incorrect=Tá an cód CAPTCHA mícheart. +password_not_match=Ní mheaitseálann na pasfhocail. +lang_select_error=Roghnaigh teanga ón liosta. + +username_been_taken=Tá an t-ainm úsáideora tógtha cheana féin. +username_change_not_local_user=Ní cheadaítear d'úsáideoirí neamháitiúla a n-ainm úsáideora a athrú. +username_has_not_been_changed=Níor athraíodh ainm úsáideora +repo_name_been_taken=Úsáidtear ainm an stór cheana féin. +repository_force_private=Tá Force Private cumasaithe: ní féidir stórais phríobháideacha a dhéanamh poiblí. +repository_files_already_exist=Tá comhaid ann cheana féin don stór seo. Déan teagmháil leis an riarthóir córais. +repository_files_already_exist.adopt=Tá comhaid ann cheana don stór seo agus ní féidir iad a ghlacadh ach amháin. +repository_files_already_exist.delete=Tá comhaid ann cheana féin don stór seo. Ní mór duit iad a scriosadh. +repository_files_already_exist.adopt_or_delete=Tá comhaid ann cheana féin don stór seo. Glac iad nó scrios iad. +visit_rate_limit=Thug cuairt chianda aghaidh ar theorannú rátaí. +2fa_auth_required=Bhí fíordheimhniú dhá thoisc ag teastáil ó chianchuairt. +org_name_been_taken=Tá ainm na heagraíochta glactha cheana féin. +team_name_been_taken=Tá ainm na foirne glactha cheana féin. +team_no_units_error=Ceadaigh rochtain ar chuid stórais amháin ar a laghad. +email_been_used=Úsáidtear an seoladh ríomhphoist cheana féin. +email_invalid=Tá an seoladh ríomhphoist neamhbhailí. +email_domain_is_not_allowed=Tá réimse ríomhphoist úsáideora <b>%s</b> ag teacht i gcoitinne le EMAIL_DOMAIN_ALLOWLIST nó EMAIL_DOMAIN_BLOCKLIST. Déan cinnte go bhfuil súil le d'oibríocht. +openid_been_used=Úsáidtear an seoladh OpenID "%s" cheana féin. +username_password_incorrect=Tá ainm úsáideora nó pasfhocal mícheart. +password_complexity=Ní shásaíonn pasfhocal ceanglais castachta: +password_lowercase_one=Carachtar beaga amháin ar a laghad +password_uppercase_one=Carachtar cás uachtair amháin ar a laghad +password_digit_one=Digit amháin ar a laghad +password_special_one=Carachtar speisialta amháin ar a laghad (poncaíocht, lúibíní, luachana, srl.) +enterred_invalid_repo_name=Tá ainm an stórais a chuir tú isteach mícheart. +enterred_invalid_org_name=Tá ainm na heagraíochta a chuir tú isteach mícheart. +enterred_invalid_owner_name=Níl ainm an úinéara nua bailí. +enterred_invalid_password=Tá an pasfhocal a chuir tú isteach mícheart. +unset_password=Níor shocraigh an t-úsáideoir logála isteach an pasfhocal. +unsupported_login_type=Ní thacaítear leis an gcineál logála isteach chun cuntas a scriosadh. +user_not_exist=Níl an t-úsáideoir ann. +team_not_exist=Níl an fhoireann ann. +last_org_owner=Ní féidir leat an t-úsáideoir deireanach a bhaint as an bhfoireann 'úinéirí'. Caithfidh úinéir amháin ar a laghad a bheith ann d'eagraíocht. +cannot_add_org_to_team=Ní féidir eagraíocht a chur leis mar bhall foirne. +duplicate_invite_to_team=Tugadh cuireadh don úsáideoir cheana féin mar bhall foirne. +organization_leave_success=D'fhág tú an eagraíocht %s go rathúil. + +invalid_ssh_key=Ní féidir d'eochair SSH a fhíorú: %s +invalid_gpg_key=Ní féidir d'eochair GPG a fhíorú: %s +invalid_ssh_principal=Príomhoide neamhbhailí: %s +must_use_public_key=Is eochair phríobháideach an eochair a sholáthraíonn tú. Ná uaslódáil d'eochair phríobháideach áit ar bith Úsáid d'eochair phoiblí ina ionad. +unable_verify_ssh_key=Ní féidir an eochair SSH a fhíorú, seiceáil faoi dhó é le haghaidh botúin. +auth_failed=Theip ar fhíordheimhniú:%v + +still_own_repo=Tá stór amháin nó níos mó ag do chuntas, scriosadh nó aistrigh iad ar dtús. +still_has_org=Is ball d'eagraíocht amháin nó níos mó é do chuntas, fág iad ar dtús. +still_own_packages=Tá pacáiste amháin nó níos mó ag do chuntas, scrios iad ar dtús. +org_still_own_repo=Tá stór amháin nó níos mó ag an eagraíocht seo fós, scriosadh nó aistrigh iad ar dtús. +org_still_own_packages=Tá pacáiste amháin nó níos mó ag an eagraíocht seo fós, scrios iad ar dtús. + +target_branch_not_exist=Níl spriocbhrainse ann. +target_ref_not_exist=Níl an sprioctagartha %s ann + +admin_cannot_delete_self=Ní féidir leat tú féin a scriosadh nuair is riarachán tú. Bain do phribhléidí riaracháin ar dtús. + +[user] +change_avatar=Athraigh do abhatár… +joined_on=Cláraigh ar %s +repositories=Stórais +activity=Gníomhaíocht Phoiblí +followers=Leantóirí +starred=Stórais Réaltaithe +watched=Stórais Breathnaithe +code=Cód +projects=Tionscadail +overview=Forbhreathnú +following=Ag leanúint +follow=Lean +unfollow=Dílean +user_bio=Beathaisnéis +disabled_public_activity=Dhíchumasaigh an t-úsáideoir seo infheictheacht phoiblí na gníomhaíochta. +email_visibility.limited=Tá do sheoladh ríomhphoist le feiceáil do gach úsáideoir fíordheimhnithe +email_visibility.private=Níl do sheoladh ríomhphoist le feiceáil ach duit féin agus do riarthóirí +show_on_map=Taispeáin an áit seo ar léarscáil +settings=Socruithe Úsáideora + +form.name_reserved=Tá an t-ainm úsáideora "%s" in áirithe. +form.name_pattern_not_allowed=Ní cheadaítear an patrún "%s" in ainm úsáideora. +form.name_chars_not_allowed=Tá carachtair neamhbhailí in ainm úsáideora "%s". + +block.block=Bloc +block.block.user=Bloc úsáideoir +block.block.org=Bloc úsáideoir don eagraíocht +block.block.failure=Theip ar an úsáideoir a bhac: %s +block.unblock=Díbhlocáil +block.unblock.failure=Theip ar an úsáideoir a díbhlocáil: %s +block.blocked=Chuir tú bac ar an úsáideoir seo. +block.title=Cuir bac ar úsáideoir +block.info=Cuireann blocáil úsáideora cosc orthu idirghníomhú le stórais, mar shampla iarratais tarraingthe nó saincheisteanna a oscailt nó trácht a dhéanamh orthu. Níos mó a fhoghlaim faoi bhac úsáideora. +block.info_1=Cuireann blocáil úsáideora cosc ar na gníomhartha seo a leanas ar do chuntas agus ar do stór: +block.info_2=ag leanúint do chuntas +block.info_3=seol fógraí chugat ag @mentioning d'ainm úsáideora +block.info_4=ag tabhairt cuireadh duit mar chomhoibritheoir chuig a stórtha +block.info_5=ag réaladh, ag forcáil nó ag féachaint ar stórais +block.info_6=ceisteanna nó iarrataí tarraingthe a oscailt agus trácht +block.info_7=freagairt ar do chuid tuairimí i saincheisteanna nó iarratais tarraingthe +block.user_to_block=Úsáideoir chun blocáil +block.note=Nóta +block.note.title=Nóta roghnach: +block.note.info=Níl an nóta le feiceáil don úsáideoir blocáilte. +block.note.edit=Cuir nóta in eagar +block.list=Úsáideoirí blocáilte +block.list.none=Níor chuir tú bac ar aon úsáideoirí. + +[settings] +profile=Próifíl +account=Cuntas +appearance=Dealramh +password=Pasfhocal +security=Slándáil +avatar=Abhatár +ssh_gpg_keys=Eochracha SSH/GPG +social=Cuntais Shóisialta +applications=Iarratais +orgs=Eagraíochtaí a bhainistiú +repos=Stórais +delete=Scrios Cuntas +twofa=Fíordheimhniú Dhá Fachtóir (TOTP) +account_link=Cuntais Nasctha +organization=Eagraíochtaí +uid=UID +webauthn=Fíordheimhniú Dhá-Fachtóir (Eochracha Slándála) + +public_profile=Próifíl Phoiblí +biography_placeholder=Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid) +location_placeholder=Comhroinn do shuíomh thart le daoine eile +profile_desc=Rialú conas a thaispeánfar do phróifíl d'úsáideoirí eile. Úsáidfear do phríomhsheoladh ríomhphoist le haghaidh fógraí, aisghabháil pasfhocail agus oibríochtaí Git gréasán-bhunaithe. +password_username_disabled=Ní cheadaítear d'úsáideoirí neamháitiúla a n-ainm úsáideora a athrú. Déan teagmháil le do riarthóir láithreáin le haghaidh tuilleadh sonraí +full_name=Ainm Iomlán +website=Láithreán Gréasáin +location=Suíomh +update_theme=Nuashonraigh Téama +update_profile=Nuashonraigh Próifíl +update_language=Nuashonraigh Teanga +update_language_not_found=Níl teanga “%s” ar fáil. +update_language_success=Tá an teanga nuashonraithe. +update_profile_success=Nuashonraíodh do phróifíl. +change_username=Tá d'ainm úsáideora athraithe. +change_username_prompt=Nóta: Athraíonn athrú d'ainm úsáideora URL do chuntais freisin. +change_username_redirect_prompt=Athreoróidh an sean-ainm úsáideora go dtí go n-éilíonn duine é +continue=Lean ar aghaidh +cancel=Cealaigh +language=Teanga +ui=Téama +hidden_comment_types=Cineálacha tráchtaireachta ceilte +hidden_comment_types_description=Ní thaispeánfar cineálacha tráchta a sheiceáiltear anseo taobh istigh de leathan Cuireann seiceáil “Lipéad” mar shampla baintear gach trácht “{user} cursaí/bainte {label}”. +hidden_comment_types.ref_tooltip=Tuairimí ina dtagraíodh an tsaincheist seo ó shaincheiste/coiste eile... +hidden_comment_types.issue_ref_tooltip=Tuairimí ina n-athraíonn an t-úsáideoir an brainse/clib a bhaineann leis an tsaincheist +comment_type_group_reference=Tagairt +comment_type_group_label=Lipéad +comment_type_group_milestone=Cloch Mhíle +comment_type_group_assignee=Sannaitheoir +comment_type_group_title=Teideal +comment_type_group_branch=Brainse +comment_type_group_time_tracking=Rianú Ama +comment_type_group_deadline=Spriocdháta +comment_type_group_dependency=Spleáchas +comment_type_group_lock=Stádas Glas +comment_type_group_review_request=Iarratas athbhreithnithe +comment_type_group_pull_request_push=Tiomáintí curtha leis +comment_type_group_project=Tionscadal +comment_type_group_issue_ref=Tagairt eisiúna +saved_successfully=Sábháiltear do shocruithe go rathúil. +privacy=Príobháideacht +keep_activity_private=Folaigh gníomhaíocht ó leathanach próifíle +keep_activity_private_popup=Ní dhéanann an gníomhaíocht le feiceáil ach duit féin agus do na riarthóirí + +lookup_avatar_by_mail=Cuardaigh Avatar trí Seoladh Ríomhphoist +federated_avatar_lookup=Cuardach Avatar Cónaidhme +enable_custom_avatar=Úsáid Avatar Saincheaptha +choose_new_avatar=Roghnaigh avatar nua +update_avatar=Nuashonrú Avatar +delete_current_avatar=Scrios Avatar Reatha +uploaded_avatar_not_a_image=Ní íomhá é an comhad uaslódáilte. +uploaded_avatar_is_too_big=Sáraíonn méid an chomhaid uaslódáilte (%d KiB) an méid uasta (%d KiB). +update_avatar_success=Tá do avatar nuashonraithe. +update_user_avatar_success=Nuashonraíodh avatar an úsáideora. + +change_password=Nuashonrú Pasfhocal +old_password=Pasfhocal Reatha +new_password=Pasfhocal Nua +retype_new_password=Deimhnigh Pasfhocal Nua +password_incorrect=Tá an pasfhocal reatha mícheart. +change_password_success=Tá do phasfhocal nuashonraithe. Sínigh isteach ag baint úsáide as do phasfhocal nua as seo amach. +password_change_disabled=Ní féidir le húsáideoirí neamháitiúla a bhfocal faire a nuashonrú trí chomhéadan gréasáin Gitea. + +emails=Seoltaí ríomhphoist +manage_emails=Bainistigh Seoltaí Ríomhphoist +manage_themes=Roghnaigh téama réamhshocraithe +manage_openid=Seoltaí OpenID a bhainistiú +email_desc=Úsáidfear do phríomhsheoladh ríomhphoist le haghaidh fógraí, aisghabháil pasfhocal agus, ar choinníoll nach bhfuil sé i bhfolach, oibríochtaí Git bunaithe ar an ngréas +theme_desc=Beidh sé seo do théama réamhshocraithe ar fud an láithreáin. +theme_colorblindness_help=Tacaíocht Téama Dathdallacht +theme_colorblindness_prompt=Ní fhaigheann Gitea ach roinnt téamaí le tacaíocht bhunúsach daille datha, nach bhfuil ach cúpla dathanna sainithe acu. Tá an obair fós ar siúl. D’fhéadfaí tuilleadh feabhsuithe a dhéanamh trí níos mó dathanna a shainiú sna comhaid CSS téamaí. +primary=Príomhúil +activated=Gníomhachtaithe +requires_activation=Éilíonn gníomhachtú +primary_email=Déan príomhúil +activate_email=Seol Gníomhachtaithe +activations_pending=Gníomhartha ar Feitheamh +can_not_add_email_activations_pending=Tá gníomhachtú ar feitheamh, déan iarracht arís i gceann cúpla nóiméad más mian leat ríomhphost nua a chur leis. +delete_email=Bain +email_deletion=Bain Seoladh R-phoist +email_deletion_desc=Bainfear an seoladh ríomhphoist agus an fhaisnéis ghaolmhar as do chuntas. Ní bheidh na tiomáintí Git a bhaineann leis an seoladh ríomhphoist seo athraithe. Lean ar aghaidh? +email_deletion_success=Tá an seoladh ríomhphoist bainte. +theme_update_success=Nuashonraíodh do théama. +theme_update_error=Níl an téama roghnaithe ann. +openid_deletion=Bain Seoladh OpenID +openid_deletion_desc=Cuirfidh an seoladh OpenID seo a bhaint as do chuntas cosc ort síniú isteach leis. Lean ar aghaidh? +openid_deletion_success=Tá an seoladh OpenID bainte. +add_new_email=Cuir Seoladh Ríomhphoist nua +add_new_openid=Cuir URI OpenID nua leis +add_email=Cuir Seoladh R-phoist leis +add_openid=Cuir OpenID URI +add_email_confirmation_sent=Seoladh ríomhphost deimhnithe chuig “%s”. Seiceáil do bhosca isteach laistigh den chéad %s eile chun do sheoladh ríomhphoist a dhearbhú. +add_email_success=Cuireadh an seoladh ríomhphoist nua leis. +email_preference_set_success=Socraíodh rogha ríomhphoist go rathúil. +add_openid_success=Cuireadh an seoladh OpenID nua leis. +keep_email_private=Folaigh Seoladh ríomhphoist +keep_email_private_popup=Folóidh sé seo do sheoladh ríomhphoist ó do phróifíl, chomh maith le nuair a dhéanann tú iarratas tarraingthe nó comhad a chur in eagar ag baint úsáide as an gcomhéadan gréasáin. Ní mhodhnófar na tiomáintí a bhrúitear. Úsáid %s i ngealltanais chun iad a cheangal le do chuntas. +openid_desc=Ligeann OpenID duit fíordheimhniú a tharmligean chuig soláthraí seachtrach. + +manage_ssh_keys=Bainistigh Eochracha SSH +manage_ssh_principals=Bainistigh Príomhoidí Teastas SSH +manage_gpg_keys=Bainistigh Eochracha GPG +add_key=Cuir Eochair +ssh_desc=Tá na heochracha SSH poiblí seo bainteach le do chuntas. Ceadaíonn na heochracha príobháideacha comhfhreagracha rochtain iomlán ar do stórtha. +principal_desc=Tá baint ag na príomhoidí deimhnithe SSH seo le do chuntas agus ceadaíonn siad rochtain iomlán ar do stórtha. +gpg_desc=Tá na heochracha GPG poiblí seo bainteach le do chuntas. Coinnigh d'eochracha príobháideacha sábháilte mar a cheadaíonn siad gealltanais a fhíorú. +ssh_helper=<strong>An bhfuil cabhair uait?</strong> Féach ar threoir GitHub chun <a href="%s">d'eochracha SSH féin a chruthú</a> nó réitigh <a href="%s">fadhbanna coitianta</a> a> seans go dtiocfaidh tú ar úsáid SSH. +gpg_helper=<strong>Cabhair uait?</strong> <a href="%s">Féach ar threoir GitHub faoi GPG.</a> +add_new_key=Cuir eochair SSH leis +add_new_gpg_key=Cuir Eochair GPG leis +key_content_ssh_placeholder=Tosaíonn sé le 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', nó 'sk-ssh-ed25519@openssh.com' +key_content_gpg_placeholder=Tosaíonn sé le '----- BEGIN PGP POIBLÍ EOCHAIR BLOC ------ ' +add_new_principal=Cuir Príomhoide +ssh_key_been_used=Cuireadh an eochair SSH seo leis an bhfreastalaí cheana féin. +ssh_key_name_used=Tá eochair SSH leis an ainm céanna ar do chuntas cheana féin. +ssh_principal_been_used=Cuireadh an príomhoide seo leis an bhfreastalaí cheana féin. +gpg_key_id_used=Tá eochair GPG poiblí leis an aitheantas céanna ann cheana féin. +gpg_no_key_email_found=Ní mheaitseálann an eochair GPG seo aon seoladh ríomhphoist gníomhachtaithe a bhaineann le do chuntas. Féadfar é a chur leis fós má shíníonn tú an comhartha a chuirtear ar fáil. +gpg_key_matched_identities=Aitheantais Meaitseáilte: +gpg_key_matched_identities_long=Meaitseálann na haitheantais leabaithe san eochair seo na seoltaí ríomhphoist gníomhachtaithe seo a leanas don úsáideoir seo. Is féidir gealltanais a mheaitseálann na seoltaí ríomhphoist seo a fhíorú leis an eochair seo. +gpg_key_verified=Eochair Fhíoraithe +gpg_key_verified_long=Fíoraíodh an eochair le heochairchomhartha agus is féidir í a úsáid chun a fhíorú go bhfuil geallta ag meaitseáil aon seoltaí ríomhphoist gníomhachtaithe don úsáideoir seo chomh maith le haon aitheantas comhoiriúnaithe don eochair seo. +gpg_key_verify=Fíoraigh +gpg_invalid_token_signature=Ní mheaitseálann an eochair, an síniú agus an comhartha GPG a sholáthraítear nó tá an comhartha as dáta. +gpg_token_required=Ní mór duit síniú a sholáthar don chomhartha thíos +gpg_token=Comhartha +gpg_token_help=Is féidir leat síniú a ghiniúint ag úsáid: +gpg_token_signature=Síniú Armúrtha GPG +key_signature_gpg_placeholder=Tosaíonn sé le '-----BEGIN SÍNIÚ PGP -----' +verify_gpg_key_success=Tá eochair GPG “%s” fíoraithe. +ssh_key_verified=Eochair Fhíoraithe +ssh_key_verified_long=Fíoraíodh an eochair le heochairchomhartha agus is féidir í a úsáid chun a fhíorú go bhfuil geallta ag teacht le haon seoltaí ríomhphoist gníomhachtaithe don úsáideoir seo. +ssh_key_verify=Fíoraigh +ssh_invalid_token_signature=Ní mheaitseálann an eochair, an síniú nó an comhartha SSH a sholáthraítear nó tá an comhartha as dáta. +ssh_token_required=Ní mór duit síniú a sholáthar don chomhartha thíos +ssh_token=Comhartha +ssh_token_help=Is féidir leat síniú a ghiniúint ag úsáid: +ssh_token_signature=Síniú armúrtha SSH +key_signature_ssh_placeholder=Tosaíonn sé le '-----BEGIN SSH SÍNITURE-----' +verify_ssh_key_success=Tá eochair SSH “%s” fíoraithe. +subkeys=Fo-eochracha +key_id=Eochair ID +key_name=Ainm Eochair +key_content=Ábhar +principal_content=Ábhar +add_key_success=Cuireadh an eochair SSH “%s” leis. +add_gpg_key_success=Cuireadh an eochair GPG “%s” leis. +add_principal_success=Cuireadh príomhoide an deimhnithe SSH “%s” leis. +delete_key=Bain +ssh_key_deletion=Bain Eochair SSH +gpg_key_deletion=Bain Eochair GPG +ssh_principal_deletion=Bain Príomhoide Teastas SSH +ssh_key_deletion_desc=Ag baint eochair SSH, cuirtear a rochtain ar do chuntas a chúlghairm. Lean ar aghaidh? +gpg_key_deletion_desc=Má bhaintear eochair GPG, ní fhíoraítear gealltanais a shínigh sé. An leanfaidh tú ar aghaidh? +ssh_principal_deletion_desc=Cúlghairtear a rochtain ar do chuntas Príomhoide Teastas SSH. Lean ar aghaidh? +ssh_key_deletion_success=Tá an eochair SSH bainte. +gpg_key_deletion_success=Tá an eochair GPG bainte amach. +ssh_principal_deletion_success=Tá an príomhoide bainte. +added_on=Cuireadh leis ar %s +valid_until_date=Bailí go dtí %s +valid_forever=Bailí go deo +last_used=Úsáidtear go deireanach ar +no_activity=Gan gníomhaíocht le déanaí +can_read_info=Léigh +can_write_info=Scríobh +key_state_desc=Úsáideadh an eochair seo le 7 lá anuas +token_state_desc=Úsáideadh an comhartha seo le 7 lá anuas +principal_state_desc=Úsáideadh an príomhoide seo le 7 lá anuas +show_openid=Taispeáin ar phróifíl +hide_openid=Folaigh ón bpróifíl +ssh_disabled=SSH faoi mhíchumas +ssh_signonly=Tá SSH faoi láthair faoi láthair mar sin ní úsáidtear na heochracha seo ach le haghaidh fíorú sínithe tiomanta. +ssh_externally_managed=Déantar an eochair SSH seo a bhainistiú go seachtrach don úsáideoir seo +manage_social=Cuntais Shóisialta Ghaolmhara a bhainistiú +social_desc=Is féidir na cuntais shóisialta seo a úsáid chun síniú isteach i do chuntas. Déan cinnte go n-aithníonn tú gach ceann acu. +unbind=Dínascadh +unbind_success=Tá an cuntas sóisialta bainte go rathúil. + +manage_access_token=Bainistigh Comharthaí Rochtana +generate_new_token=Gin Comhartha Nua +tokens_desc=Tugann na comharthaí seo rochtain ar do chuntas ag baint úsáide as an API Gitea. +token_name=Ainm Comhartha +generate_token=Gin Comhartha +generate_token_success=Gintear do chomhartha nua. Cóipeáil é anois mar ní thaispeánfar é arís. +generate_token_name_duplicate=<strong>Úsáideadh %s</strong> mar ainm feidhmchláir cheana féin. Úsáid ceann nua le do thoil. +delete_token=Scrios +access_token_deletion=Scrios Comhartha Rochtana +access_token_deletion_cancel_action=Cealaigh +access_token_deletion_confirm_action=Scrios +access_token_deletion_desc=Cúlghairfear rochtain ar do chuntas le haghaidh feidhmchláir a úsáideann é a scriosadh comhartha. Ní féidir é seo a chur ar ais. Lean ar aghaidh? +delete_token_success=Tá an comhartha scriosta. Níl rochtain ag iarratais a úsáideann é ar do chuntas a thuilleadh. +repo_and_org_access=Rochtain Stórála agus Eagraíochta +permissions_public_only=Poiblí amháin +permissions_access_all=Gach (poiblí, príobháideach agus teoranta) +select_permissions=Roghnaigh ceadanna +permission_not_set=Níl leagtha +permission_no_access=Gan rochtain +permission_read=Léigh +permission_write=Léigh agus Scríobh +access_token_desc=Ní chuireann ceadchomharthaí roghnaithe ach teorainn leis an údarú do na bealaí <a %s>API</a> comhfhreagracha. Léigh <a %s>doiciméadúchán</a> chun tuilleadh eolais a fháil. +at_least_one_permission=Ní mór duit cead amháin ar a laghad a roghnú chun comhartha a chruthú +permissions_list=Ceadanna: + +manage_oauth2_applications=Bainistigh Feidhmchláir OAuth2 +edit_oauth2_application=Cuir Feidhmchlár OAuth2 in eagar +oauth2_applications_desc=Cumasaíonn feidhmchláir OAuth2 d’fheidhmchlár tríú páirtí úsáideoirí a fhíordheimhniú go slán ag an ásc Gitea seo. +remove_oauth2_application=Bain Feidhmchlár OAuth2 +remove_oauth2_application_desc=Ag baint feidhmchlár OAuth2, cúlghairfear rochtain ar gach comhartha rochtana sínithe. Lean ar aghaidh? +remove_oauth2_application_success=Scriosadh an feidhmchlár. +create_oauth2_application=Cruthaigh Feidhmchlár OAuth2 nua +create_oauth2_application_button=Cruthaigh Feidhmchlár +create_oauth2_application_success=D'éirigh leat feidhmchlár nua OAuth2 a chruthú. +update_oauth2_application_success=D'éirigh leat an feidhmchlár OAuth2 a nuashonrú. +oauth2_application_name=Ainm Feidhmchláir +oauth2_confidential_client=Cliant Rúnda. Roghnaigh le haghaidh aipeanna a choimeádann an rún faoi rún, mar aipeanna gréasáin. Ná roghnaigh le haghaidh aipeanna dúchasacha lena n-áirítear aipeanna deisce agus soghluaiste. +oauth2_skip_secondary_authorization=Scipeáil údarú do chliaint poiblí tar éis rochtain a dheonú <strong>D'fhéadfadh sé go mbeadh riosca slándála</strong> +oauth2_redirect_uris=URIs a atreorú. Úsáid líne nua do gach URI le do thoil. +save_application=Sábháil +oauth2_client_id=ID Cliant +oauth2_client_secret=Rúnda Cliant +oauth2_regenerate_secret=Athghin Rún +oauth2_regenerate_secret_hint=Chaill tú do rún? +oauth2_client_secret_hint=Ní thaispeánfar an rún arís tar éis duit an leathanach seo a fhágáil nó a athnuachan. Déan cinnte le do thoil gur shábháil tú é. +oauth2_application_edit=Cuir in eagar +oauth2_application_create_description=Tugann feidhmchláir OAuth2 rochtain d'iarratas tríú páirtí ar chuntais úsáideora ar an gcás seo. +oauth2_application_remove_description=Cuirfear feidhmchlár OAuth2 a bhaint cosc air rochtain a fháil ar chuntais úsáideora údaraithe ar an gcás seo. Lean ar aghaidh? +oauth2_application_locked=Réamhchláraíonn Gitea roinnt feidhmchlár OAuth2 ar thosú má tá sé cumasaithe i gcumraíocht. Chun iompar gan choinne a chosc, ní féidir iad seo a chur in eagar ná a bhaint. Féach do thoil do dhoiciméadú OAuth2 le haghaidh tuilleadh faisnéise. + +authorized_oauth2_applications=Feidhmchláir Údaraithe OAuth2 +authorized_oauth2_applications_description=Tá rochtain tugtha agat ar do chuntas pearsanta Gitea ar na feidhmchláir tríú páirtí seo. Cúlghairm rochtain d'iarratais nach bhfuil uait a thuilleadh. +revoke_key=Cúlghairm +revoke_oauth2_grant=Rochtain a chúlghairm +revoke_oauth2_grant_description=Cuirfidh rochtain ar an bhfeidhmchlár tríú páirtí seo a chúlghairm cosc ar an bhfeidhmchlár seo rochtain An bhfuil tú cinnte? +revoke_oauth2_grant_success=Cúlghairtear rochtain go rathúil. + +twofa_desc=Chun do chuntas a chosaint ar goid pasfhocal, is féidir leat fón cliste nó gléas eile a úsáid chun pasfhocail aon-uaire bunaithe ar am (“TOTP”) a fháil. +twofa_recovery_tip=Má chailleann tú do ghléas, beidh tú in ann eochair aisghabhála aonúsáide a úsáid chun rochtain ar do chuntas a fháil ar ais. +twofa_is_enrolled=Tá do chuntas cláraithe i bhfíord <strong>heimhniú</strong> dhá fhachtóir faoi láthair. +twofa_not_enrolled=Níl do chuntas cláraithe faoi láthair i bhfíordheimhniú dhá fhachtóir. +twofa_disable=Díchumasaigh Fíordheimhniú Dhá-Fachtóir +twofa_scratch_token_regenerate=Athghin Eochair Aisghabhála Aonúsáide +twofa_scratch_token_regenerated=Is é %s d'eochair aisghabhála aonúsáide anois. Stóráil é in áit shábháilte, mar ní thaispeánfar é arís. +twofa_enroll=Cláraigh le Fíordheimhniú Dhá-Fachtóir +twofa_disable_note=Is féidir leat fíordheimhniú dhá fhachtóir a dhíchumasú más gá. +twofa_disable_desc=Má dhíchumasaítear fíordheimhniú dhá fhachtóir beidh do chuntas chomh slán. Lean ar aghaidh? +regenerate_scratch_token_desc=Má chuir tú d'eochair aisghabhála míchuir tú nó má d'úsáid tú é cheana féin chun síniú isteach, is féidir leat é a athshocrú anseo. +twofa_disabled=Tá fíordheimhniú dhá fhachtóir díchumasaithe. +scan_this_image=Scan an íomhá seo le d'fheidhmchlár fíordheimhnithe: +or_enter_secret=Nó cuir isteach an rún: %s +then_enter_passcode=Agus cuir isteach an paschód a léirítear san fheidhmchlár: +passcode_invalid=Tá an pascód mícheart. Bain triail as arís. +twofa_enrolled=Tá do chuntas cláraithe go rathúil. Stóráil d'eochair aisghabhála aonúsáide (%s) in áit shábháilte, mar ní thaispeánfar é arís. +twofa_failed_get_secret=Theip ar rún a fháil. + +webauthn_desc=Is feistí crua-earraí iad eochracha slándála ina bhfuil eochracha cripte Is féidir iad a úsáid le haghaidh fíordheimhniú dhá fhachtóir. Caithfidh eochracha slándála tacú le caigh <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">deán Fíordheimhnithe WebAuthn</a> +webauthn_register_key=Cuir Eochair Slándála +webauthn_nickname=Leasainm +webauthn_delete_key=Bain Eochair Slándála +webauthn_delete_key_desc=Má bhaineann tú eochair slándála ní féidir leat síniú leis a thuilleadh. Lean ar aghaidh? +webauthn_key_loss_warning=Má chailleann tú d'eochracha slándála, caillfidh tú rochtain ar do chuntas. +webauthn_alternative_tip=B'fhéidir gur mhaith leat modh fíordheimhnithe breise a chumrú. + +manage_account_links=Bainistigh Cuntais Nasctha +manage_account_links_desc=Tá na cuntais sheachtracha seo nasctha le do chuntas Gitea. +account_links_not_available=Níl aon chuntais sheachtracha nasctha le do chuntas Gitea faoi láthair. +link_account=Cuntas Nasc +remove_account_link=Bain Cuntas Nasctha +remove_account_link_desc=Ag baint cuntas nasctha, cuirfear a rochtain ar do chuntas Gitea a chúlghairm. Lean ar aghaidh? +remove_account_link_success=Tá an cuntas nasctha bainte amach. + +hooks.desc=Cuir cuaillí gréasáin leis a spreagfar do <strong>gach stór</strong> ar leatsa iad. + +orgs_none=Níl tú ina bhall d'aon eagraíochtaí. +repos_none=Níl aon stórais agat. + +delete_account=Scrios Do Cuntas +delete_prompt=Scriosfaidh an oibríocht seo do chuntas úsáideora go buan. <strong>NÍ FÉIDIR</strong> é a chealú. +delete_with_all_comments=Tá do chuntas níos óige ná %s. Chun tuairimí taibhse a sheachaint, scriosfar gach trácht saincheistea/PR leis. +confirm_delete_account=Deimhnigh scriosadh +delete_account_title=Scrios Cuntas Úsáide +delete_account_desc=An bhfuil tú cinnte gur mhaith leat an cuntas úsáideora seo a scriosadh go buan? + +email_notifications.enable=Cumasaigh Fógraí Ríomhphoist +email_notifications.onmention=Ríomhphost amháin ar luaigh +email_notifications.disable=Díchumasaigh Fógraí Ríomhphoist +email_notifications.submit=Socraigh rogha ríomhphoist +email_notifications.andyourown=Agus Do Fógraí Féin + +visibility=Infheictheacht úsáideora +visibility.public=Poiblí +visibility.public_tooltip=Infheicthe do gach duine +visibility.limited=Teoranta +visibility.limited_tooltip=Infheicthe ach d'úsáideoirí fíordheimhnithe +visibility.private=Príobháideach +visibility.private_tooltip=Ní fheictear ach do bhaill d'eagraíochtaí a chuaigh tú isteach + +[repo] +new_repo_helper=Tá gach comhad tionscadail i stór, lena n-áirítear stair athbhreithnithe. Ceana féin ag óstáil ceann in áit eile <a href="%s">Stórlann aistrithe.</a> +owner=Úinéir +owner_helper=B'fhéidir nach dtaispeánfar roinnt eagraíochtaí sa anuas mar gheall ar theorainn uasta comhaireamh stórais. +repo_name=Ainm Stórais +repo_name_helper=Úsáideann dea-ainmneacha stórtha eochairfhocail ghearr, i gcuimhne agus uathúla. +repo_size=Méid an Stóras +template=Teimpléad +template_select=Roghnaigh teimpléad. +template_helper=Déan teimpléad den stóras +template_description=Ligeann stórais teimpléid d'úsáideoirí stórais nua a ghiniúint leis an struchtúr eolaire céanna, comhaid agus socruithe roghnacha. +visibility=Infheictheacht +visibility_description=Ní bheidh ach an t-úinéir nó baill na heagraíochta má tá cearta acu in ann é a fheiceáil. +visibility_helper=Déan stóras príobháideach +visibility_helper_forced=Cuireann riarthóir do shuíomh iallach ar stórais nua a bheith príobháideach. +visibility_fork_helper=(Beidh tionchar ag athrú seo ar gach forc.) +clone_helper=Teastaíonn cabhair ó chlónáil? Tabhair cuairt <a target="_blank" rel="noopener noreferrer" href="%s">ar Cabhair</a>. +fork_repo=Stóras Forc +fork_from=Forc ó +already_forked=Tá tú tar éis %s a fhoirceann +fork_to_different_account=Forc chuig cuntas difriúil +fork_visibility_helper=Ní féidir infheictheacht stór forcailte a athrú. +fork_branch=Brainse le clónú chuig an bhforc +all_branches=Gach brainse +fork_no_valid_owners=Ní féidir an stór seo a fhorcáil toisc nach bhfuil úinéirí bailí ann. +fork.blocked_user=Ní féidir an stór a fhorcáil toisc go bhfuil úinéir an stórais bac ort. +use_template=Úsáid an teimpléad seo +open_with_editor=Oscail le %s +download_zip=Íoslódáil ZIP +download_tar=Íoslódáil TAR.GZ +download_bundle=Íoslódáil BUNDLE +generate_repo=Cruthaigh Stóras +generate_from=Gin Ó +repo_desc=Cur síos +repo_desc_helper=Cuir isteach tuairisc ghearr (roghnach) +repo_lang=Teanga +repo_gitignore_helper=Roghnaigh teimpléid .gitignore. +repo_gitignore_helper_desc=Roghnaigh na comhaid nach bhfuil le rianú ó liosta teimpléid do theangacha coitianta. Cuirtear déantáin tipiciúla a ghineann uirlisí tógála gach teanga san áireamh ar.gitignore de réir réamhshocraithe. +issue_labels=Lipéid Eisiúna +issue_labels_helper=Roghnaigh tacar lipéad eisiúna. +license=Ceadúnas +license_helper=Roghnaigh comhad ceadúnais. +license_helper_desc=Rialaíonn ceadúnas cad is féidir agus nach féidir le daoine eile a dhéanamh le do chód. Níl mé cinnte cé acu ceann atá ceart do do thionscadal? Féach <a target="_blank" rel="noopener noreferrer" href="%s">Roghnaigh ceadúnas.</a> +multiple_licenses=Ceadúnais Iolracha +object_format=Formáid Oibiacht +object_format_helper=Formáid oibiacht an stór. Ní féidir é a athrú níos déanaí. Is é SHA1 an comhoiriúnacht is fearr. +readme=README +readme_helper=Roghnaigh comhad teimpléad README. +readme_helper_desc=Seo an áit inar féidir leat cur síos iomlán a scríobh do thionscadal. +auto_init=Taisce a thionscnamh (Cuireann sé .gitignore, Ceadúnas agus README) +trust_model_helper=Roghnaigh múnla iontaobhais le haghaidh fíorú Is iad na roghanna féideartha: +trust_model_helper_collaborator=Comhoibritheoir: Sínithe muinín ag comhoibrithe +trust_model_helper_committer=Gealltóir: Iontaobhais sínithe a mheaitseálann na gealltoirí +trust_model_helper_collaborator_committer=Comhoibritheo+Coiteoir: Sínithe iontaobhais ag comhoibritheoirí a mheaitseann leis an gealltóir +trust_model_helper_default=Réamhshocrú: Úsáid an tsamhail iontaobhais réamhshocraithe don tsuiteáil seo +create_repo=Cruthaigh Stóras +default_branch=Branse Réamhshocraithe +default_branch_label=réamhshocraithe +default_branch_helper=Is é an brainse réamhshocraithe an bunbhrainse d'iarratais tarraingthe agus gealltanna cód. +mirror_prune=Prúnáil +mirror_prune_desc=Bain tagairtí cianrianaithe atá as feidhm +mirror_interval=Eatramh Scátháin (is iad aonaid ama bailí ná 'h', 'm', 's'). 0 chun sioncrónú tréimhsiúil a dhíchumasú. (Eatraimh íosta: %s) +mirror_interval_invalid=Níl an eatramh scátháin bailí. +mirror_sync=sioncronaithe +mirror_sync_on_commit=Sioncrónaigh nuair a bhrúitear geallúintí +mirror_address=Clón Ó URL +mirror_address_desc=Cuir aon dhintiúir riachtanacha sa chuid Údaraithe. +mirror_address_url_invalid=Tá an URL curtha ar fáil neamhbhailí. Caithfidh tú gach comhpháirt den url a éalú i gceart. +mirror_address_protocol_invalid=Tá an URL curtha ar fáil neamhbhailí. Ní féidir ach suíomhanna http (s)://nó git://a úsáid le haghaidh scátháin. +mirror_lfs=Stóráil Comhad Móra (LFS) +mirror_lfs_desc=Gníomhachtaigh scáthú sonraí LFS. +mirror_lfs_endpoint=Críochphointe LFS +mirror_lfs_endpoint_desc=Déanfaidh Sync iarracht an url clónála a úsáid chun <a target="_blank" rel="noopener noreferrer" href="%s">an freastalaí LFS a chinneadh</a>. Is féidir leat críochphointe saincheaptha a shonrú freisin má tá na sonraí LFS stórtha stóráilte áit éigin eile. +mirror_last_synced=Sincronaithe Deireanach +mirror_password_placeholder=(Gan athrú) +mirror_password_blank_placeholder=(Neamhshocraithe) +mirror_password_help=Athraigh ainm úsáideora chun pasfhocal stóráilte a scriosadh. +watchers=Breathnóirí +stargazers=Réalteoirí +stars_remove_warning=Bainfidh sé seo na réaltaí go léir ón stóras seo. +forks=Forcanna +stars=Réaltaí +reactions_more=agus %d níos mó +unit_disabled=Tá an chuid stórais seo díchumasaithe ag riarthóir an láithreáin. +language_other=Eile +adopt_search=Iontráil ainm úsáideora chun stórais neamhghlactha a chuardach... (fág bán chun gach rud a fháil) +adopt_preexisting_label=Glacadh le Comhaid +adopt_preexisting=Glac le comhaid atá ann cheana +adopt_preexisting_content=Cruthaigh stór ó %s +adopt_preexisting_success=Comhaid ghlacadh agus stór cruthaithe ó %s +delete_preexisting_label=Scrios +delete_preexisting=Scrios comhaid atá ann cheana +delete_preexisting_content=Scrios comhaid i %s +delete_preexisting_success=Scriosta comhaid neamhghlactha i %s +blame_prior=Féach ar an milleán roimh an athrú seo +blame.ignore_revs=Ag déanamh neamhairde de leasuithe i <a href="%s">.git-blame-ignore-revs</a>. Cliceáil <a href="%s">anseo chun seachaint</a> agus an gnáth-amharc milleán a fheiceáil. +blame.ignore_revs.failed=Theip ar neamhaird a dhéanamh ar leasuithe i <a href="%s">.git-blame-ignore-revs</a>. +author_search_tooltip=Taispeánann 30 úsáideoir ar a mhéad + +tree_path_not_found_commit=Níl cosán %[1]s ann i dtiomantas %[2]s +tree_path_not_found_branch=Níl cosán %[1]s ann i mbrainse %[2]s +tree_path_not_found_tag=Níl cosán %[1]s ann i gclib %[2]s + +transfer.accept=Glac le hAistriú +transfer.accept_desc=Aistriú chuig “%s” +transfer.reject=Diúltaigh aistriú +transfer.reject_desc=`Cealaigh aistriú chuig "%s"` +transfer.no_permission_to_accept=Níl cead agat glacadh leis an aistriú seo. +transfer.no_permission_to_reject=Níl cead agat an aistriú seo a dhiúltú. + +desc.private=Príobháideach +desc.public=Poiblí +desc.template=Teimpléad +desc.internal=Inmheánach +desc.archived=Cartlannaithe +desc.sha256=SHA256 + +template.items=Míreanna Teimpléad +template.git_content=Ábhar Git (Brainse Réamhshocraithe) +template.git_hooks=Crúcanna Git +template.git_hooks_tooltip=Faoi láthair ní féidir leat Git Hooks a mhodhnú nó a bhaint nuair a chuirtear leis. Roghnaigh é seo ach amháin má tá muinín agat as an stóras teimpléid. +template.webhooks=Crúcaí gréasáin +template.topics=Topaicí +template.avatar=Abhatár +template.issue_labels=Lipéid Eisiúna +template.one_item=Ní mór mír teimpléad amháin ar a laghad a roghnú +template.invalid=Ní mór stór teimpléad a roghnú + +archive.title=Tá an stóras seo i gcartlann. Is féidir leat comhaid a fheiceáil agus iad a chlónáil, ach ní féidir leat ceisteanna a bhrú ná a oscailt ná iarratais a tharraingt. +archive.title_date=Tá an stóras seo cartlannaithe ar %s. Is féidir leat comhaid a fheiceáil agus é a chlónú, ach ní féidir leat saincheisteanna a bhrú nó a oscailt ná iarratais a tharraingt. +archive.issue.nocomment=Tá an stóras seo i gcartlann. Ní féidir leat trácht a dhéanamh ar shaincheisteanna. +archive.pull.nocomment=Tá an stóras seo i gcartlann. Ní féidir leat trácht a dhéanamh ar iarratais tarraingthe. + +form.reach_limit_of_creation_1=Tá úinéir an stóras tar éis teorainn de %d stóras a bhaint amach cheana féin. +form.reach_limit_of_creation_n=Tá úinéir an stórais tar éis teorainn de %d stórtha a bhaint amach cheana féin. +form.name_reserved=Tá ainm an stór "%s" in áirithe. +form.name_pattern_not_allowed=Ní cheadaítear an patrún "%s" in ainm stór. + +need_auth=Údarú +migrate_options=Roghanna Imirce +migrate_service=Seirbhís Imirce +migrate_options_mirror_helper=Beidh an stóras seo ina scáthán +migrate_options_lfs=Aimirce comhaid LFS +migrate_options_lfs_endpoint.label=Críochphointe LFS +migrate_options_lfs_endpoint.description=Déanfaidh imirce iarracht do chianda Git a úsáid chun <a target="_blank" rel="noopener noreferrer" href="%s">freastalaí LFS a chinneadh</a>. Is féidir leat críochphointe saincheaptha a shonrú freisin má tá na sonraí LFS stórtha stóráilte áit éigin eile. +migrate_options_lfs_endpoint.description.local=Tacaítear le cosán freastalaí áitiúil freisin. +migrate_options_lfs_endpoint.placeholder=Má fhágtar bán, díorthófar an críochphointe ón URL clóin +migrate_items=Míreanna Imirce +migrate_items_wiki=Wiki +migrate_items_milestones=Clocha míle +migrate_items_labels=Lipéid +migrate_items_issues=Saincheisteanna +migrate_items_pullrequests=Iarrataí Tarraing +migrate_items_merge_requests=Iarrataí Cumaisc +migrate_items_releases=Eisiúintí +migrate_repo=Stóras Imirc +migrate.clone_address=Aimirce/ Clón Ó URL +migrate.clone_address_desc=An URL 'clón' HTTP(S) nó Git de stóras atá ann cheana +migrate.github_token_desc=Is féidir leat comhartha amháin nó níos mó a chur le camóg scartha anseo chun imirce a dhéanamh níos gasta mar gheall ar theorainn ráta API GitHub. RABHADH: D'fhéadfadh mí-úsáid na ngné seo beartas an sholáthraí seirbhíse a shárú agus blocáil cuntais a bheith mar thoradh air. +migrate.clone_local_path=nó cosán freastalaí áitiúil +migrate.permission_denied=Ní cheadaítear duit stórais áitiúla a iompórtáil. +migrate.permission_denied_blocked=Ní féidir leat allmhairiú ó óstaigh neamh-cheadaithe, iarr ar an riarachán socruithe ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS a sheiceáil le do thoil. +migrate.invalid_local_path=Tá an cosán áitiúil neamhbhailí. Níl sé ann nó ní eolaire é. +migrate.invalid_lfs_endpoint=Níl an críochphointe LFS bailí. +migrate.failed=Theip ar an imirce:% v +migrate.migrate_items_options=Teastaíonn Comhartha Rochtana chun míreanna breise a aistriú +migrated_from=Aistríodh ó <a href="%[1]s">%[2]s</a> +migrated_from_fake=Aistrithe ó %[1]s +migrate.migrate=Aistrigh Ó %s +migrate.migrating=Ag aistriú ó <b>%s</b> ... +migrate.migrating_failed=Theip ar aistriú ó <b>%s</b>. +migrate.migrating_failed.error=Theip ar aistriú: %s +migrate.migrating_failed_no_addr=Theip ar an imirce. +migrate.github.description=Aistrigh sonraí ó github.com nó ó chásanna GitHub eile. +migrate.git.description=Aistrigh stór amháin ó aon seirbhís Git. +migrate.gitlab.description=Aistrigh sonraí ó gitlab.com nó ó chásanna GitLab eile. +migrate.gitea.description=Aistrigh sonraí ó gitea.com nó ó chásanna Gitea eile. +migrate.gogs.description=Aistrigh sonraí ó notabug.org nó ó chásanna eile de chuid Gogs. +migrate.onedev.description=Aistrigh sonraí ó code.onedev.io nó ó chásanna OneDev eile. +migrate.codebase.description=Aistrigh sonraí ó codebasehq.com. +migrate.gitbucket.description=Aistrigh sonraí ó chásanna GitBucket. +migrate.codecommit.description=Aistrigh sonraí ó AWS CodeCommit. +migrate.codecommit.aws_access_key_id=ID Eochair Rochtana AWS +migrate.codecommit.aws_secret_access_key=Eochair Rochtana Rúnda AWS +migrate.codecommit.https_git_credentials_username=Ainm Úsáideora HTTPS Git Dintiúir +migrate.codecommit.https_git_credentials_password=Pasfhocal Dintiúir Git HTTPS +migrate.migrating_git=Sonraí Git a Aimirce +migrate.migrating_topics=Ábhair Imirce +migrate.migrating_milestones=Clocha Míle a Imirce +migrate.migrating_labels=Lipéid Imirce +migrate.migrating_releases=Eisiúintí Imirce +migrate.migrating_issues=Saincheisteanna Imirce +migrate.migrating_pulls=Iarratais Tarraingthe á n-Imirce +migrate.cancel_migrating_title=Cealaigh Imirce +migrate.cancel_migrating_confirm=Ar mhaith leat an imirce seo a chealú? + +mirror_from=scáthán de +forked_from=forcailte ó +generated_from=a ghintear ó +fork_from_self=Ní féidir leat stóras atá agat a fhorcáil. +fork_guest_user=Sínigh isteach chun an stóras seo a fhorc. +watch_guest_user=Sínigh isteach chun féachaint ar an stór seo. +star_guest_user=Sínigh isteach chun an stóras seo a réalú. +unwatch=Dífhéachaint +watch=Fhéachaint +unstar=Bain Réalta +star=Réalta +fork=Forc +action.blocked_user=Ní féidir gníomh a dhéanamh toisc go bhfuil úinéir an stórais bac ort. +download_archive=Íoslódáil Stóras +more_operations=Tuilleadh oibríochtaí + +quick_guide=Treoir Tapa +clone_this_repo=Clóin an stóras seo +cite_this_repo=Luaigh an stóras seo +create_new_repo_command=Stóras nua a chruthú ar an líne ordaithe +push_exist_repo=Stóras atá ann cheana a bhrú ón líne ordaithe +empty_message=Níl aon ábhar sa stóras seo. +broken_message=Ní féidir na sonraí Git atá mar bhunús leis an stóras seo a léamh. Déan teagmháil le riarthóir an chás seo nó scrios an stóras seo. + +code=Cód +code.desc=Rochtain ar chód foinse, comhaid, gealltanais agus brainsí. +branch=Brainse +tree=Crann +clear_ref=`Tagairt reatha soiléir` +filter_branch_and_tag=Scagaire brainse nó clib +find_tag=Aimsigh clib +branches=Brainsí +tags=Clibeanna +issues=Saincheisteanna +pulls=Iarratais Tarraingthe +projects=Tionscadail +packages=Pacáistí +actions=Gníomhartha +labels=Lipéid +org_labels_desc=Lipéid ar leibhéal eagraíochta is féidir a úsáid le <strong>gach stóras</strong> faoin eagraíocht seo +org_labels_desc_manage=bainistigh + +milestones=Clocha míle +commits=Tiomáintí +commit=Tiomantas + + + +commits.commits=Tiomáintí + + +commitstatus.error=Earráid + + +projects.new=Tionscadal Nua +projects.template.desc=Teimpléad +projects.column.new=Colún Nua + +issues.new.projects=Tionscadail +issues.new.milestone=Cloch Mhíle +issues.filter_label=Lipéad +issues.filter_milestone=Cloch Mhíle +issues.filter_project=Tionscadal +issues.filter_assignee=Sannaitheoir +issues.action_label=Lipéad +issues.action_milestone=Cloch Mhíle +issues.action_assignee=Sannaitheoir +issues.save=Sábháil + + +pulls.tab_commits=Tiomáintí + + + + + + + + + + + + + +wiki=Wiki +wiki.page=Leathanach +wiki.new_page=Leathanach + + +contributors.contribution_type.commits=Tiomáintí + +settings.githooks=Crúcanna Git + +settings.trust_model.collaborator.long=Comhoibritheoir: Sínithe muinín ag comhoibrithe +settings.trust_model.collaboratorcommitter.long=Comhoibritheo+Coiteoir: Sínithe iontaobhais ag comhoibritheoirí a mheaitseann leis an gealltóir +settings.event_fork=Forc +settings.event_wiki=Wiki + + + + + + + + + +[graphs] + +[org] + + + + + + + +[admin] + +dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[action] + +[tool] + +[dropzone] + +[notification] + +[gpg] + +[units] + +[packages] + +[secrets] + +[actions] + + + +runners.task_list.commit=Tiomantas + +runs.commit=Tiomantas + + + + +[projects] + +[git.filemode] +; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … + From 7e68bc88238104d2ee8b5a877fc1ad437f1778a4 Mon Sep 17 00:00:00 2001 From: Job <LordChunk@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:12:48 +0200 Subject: [PATCH 08/17] Fix PR creation on forked repositories (#31863) Resolves #20475 --- routers/api/v1/repo/pull.go | 17 ++++++++++++++--- tests/integration/pull_create_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 7eb4a8b8a2d4..4e3de77032fb 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1124,9 +1124,20 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) // Check if current user has fork of repository or in the same repository. headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID) if headRepo == nil && !isSameRepo { - log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) - ctx.NotFound("GetForkedRepo") - return nil, nil, nil, "", "" + err := baseRepo.GetBaseRepo(ctx) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err) + return nil, nil, nil, "", "" + } + + // Check if baseRepo's base repository is the same as headUser's repository. + if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID { + log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) + ctx.NotFound("GetBaseRepo") + return nil, nil, nil, "", "" + } + // Assign headRepo so it can be used below. + headRepo = baseRepo.BaseRepo } var headGitRepo *git.Repository diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 5a06a7817f66..9812d2073d1e 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -199,3 +199,30 @@ func TestPullBranchDelete(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) }) } + +/* +Setup: +The base repository is: user2/repo1 +Fork repository to: user1/repo1 +Push extra commit to: user2/repo1, which changes README.md +Create a PR on user1/repo1 + +Test checks: +Check if pull request can be created from base to the fork repository. +*/ +func TestPullCreatePrFromBaseToFork(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + sessionFork := loginUser(t, "user1") + testRepoFork(t, sessionFork, "user2", "repo1", "user1", "repo1", "") + + // Edit base repository + sessionBase := loginUser(t, "user2") + testEditFile(t, sessionBase, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + // Create a PR + resp := testPullCreateDirectly(t, sessionFork, "user1", "repo1", "master", "user2", "repo1", "master", "This is a pull title") + // check the redirected URL + url := test.RedirectURL(resp) + assert.Regexp(t, "^/user1/repo1/pulls/[0-9]*$", url) + }) +} From 5d6d025c9b8d2abca9ec2bfdc795d1f0c1c6592d Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Sat, 5 Oct 2024 02:45:06 +0900 Subject: [PATCH 09/17] Add support for searching users by email (#30908) Fix #30898 we have an option `SearchByEmail`, so enable it, then we can search user by email. Also added a test for it. --- models/user/search.go | 14 ++++++++- routers/api/v1/user/user.go | 11 +++---- tests/integration/api_user_search_test.go | 36 +++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/models/user/search.go b/models/user/search.go index 45b051187ea0..382b6fac2b08 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -65,7 +65,19 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess builder.Like{"LOWER(full_name)", lowerKeyword}, ) if opts.SearchByEmail { - keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword}) + var emailCond builder.Cond + emailCond = builder.Like{"LOWER(email)", lowerKeyword} + if opts.Actor == nil { + emailCond = emailCond.And(builder.Eq{"keep_email_private": false}) + } else if !opts.Actor.IsAdmin { + emailCond = emailCond.And( + builder.Or( + builder.Eq{"keep_email_private": false}, + builder.Eq{"id": opts.Actor.ID}, + ), + ) + } + keywordCond = keywordCond.Or(emailCond) } cond = cond.And(keywordCond) diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index fedad87fc4fa..2c277a18c739 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -68,11 +68,12 @@ func Search(ctx *context.APIContext) { users = []*user_model.User{user_model.NewActionsUser()} default: users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ - Actor: ctx.Doer, - Keyword: ctx.FormTrim("q"), - UID: uid, - Type: user_model.UserTypeIndividual, - ListOptions: listOptions, + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + UID: uid, + Type: user_model.UserTypeIndividual, + SearchByEmail: true, + ListOptions: listOptions, }) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]any{ diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index f776b3532576..ff4671c54e94 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -109,3 +109,39 @@ func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { DecodeJSON(t, resp, &results) assert.Empty(t, results.Data) } + +func TestAPIUserSearchByEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // admin can search user with private email + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2@example.com" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.Equal(t, 1, len(results.Data)) + assert.Equal(t, query, results.Data[0].Email) + + // no login user can not search user with private email + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.Empty(t, results.Data) + + // user can search self with private email + user2 := "user2" + session = loginUser(t, user2) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &results) + assert.Equal(t, 1, len(results.Data)) + assert.Equal(t, query, results.Data[0].Email) +} From 6a4eb126bd911e36489979954f0b3a3ebc1ae19f Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Sat, 5 Oct 2024 00:30:40 +0000 Subject: [PATCH 10/17] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.ini | 408 ++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 4f579c0dd74e..826ac0accef4 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -1238,29 +1238,425 @@ org_labels_desc_manage=bainistigh milestones=Clocha míle commits=Tiomáintí commit=Tiomantas +release=Scaoileadh +releases=Scaoileann +tag=Clib +released_this=scaoileadh seo +tagged_this=clib seo +file.title=%s ag %s +file_raw=Amh +file_history=Stair +file_view_source=Féach Foinse +file_view_rendered=Amharc Rindreáilte +file_view_raw=Amharc Amh +file_permalink=Buan-nasc +file_too_large=Tá an comhad ró-mhór le taispeáint. +file_is_empty=Tá an comhad folamh. +code_preview_line_from_to=Línte %[1]d go %[2]d i %[3]s +code_preview_line_in=Líne %[1]d i %[2]s +invisible_runes_header=`Tá carachtair Unicode dofheicthe sa chomhad seo ` +invisible_runes_description=`Tá carachtair dofheicthe Unicode sa chomhad seo nach féidir a idirdhealú do dhaoine ach d'fhéadfadh ríomhaire iad a phróiseáil ar bhealach difriúil. Má cheapann tú go bhfuil sé seo d'aon ghnó, is féidir leat neamhaird a dhéanamh go sábháilte don rabhadh seo Úsáid an cnaipe Escape chun iad a nochtadh. ` +ambiguous_runes_header=`Tá carachtair Unicode débhríoch sa chomhad seo ` +ambiguous_runes_description=`Tá carachtair Unicode sa chomhad seo a d'fhéadfadh a bheith mearbhall le carachtair eile. Má cheapann tú go bhfuil sé seo d'aon ghnó, is féidir leat neamhaird a dhéanamh go sábháilte don rabhadh seo Úsáid an cnaipe Escape chun iad a nochtadh. ` +invisible_runes_line=`Tá carachtair unicode dofheicthe ag an líne seo ` +ambiguous_runes_line=`Tá carachtair unicode débhríoch ag an líne seo ` +ambiguous_character=Is féidir `%[1]c [U+%04[1]X] a mheascadh le %[2]c [U+%04[2]X]` +escape_control_characters=Éalú +unescape_control_characters=Dí-Éalú +file_copy_permalink=Cóipeáil Buan-nasc +view_git_blame=Féach ar Git Blame +video_not_supported_in_browser=Ní thacaíonn do bhrabhsálaí leis an gclib 'video' HTML5. +audio_not_supported_in_browser=Ní thacaíonn do bhrabhsálaí leis an gclib 'audio' HTML5. +stored_lfs=Stóráilte le Git LFS +symbolic_link=Nasc siombalach +executable_file=Comhad Infheidhmithe +vendored=Díoltóra +generated=Gintear +commit_graph=Graf Tiomantas +commit_graph.select=Roghnaigh brainsí +commit_graph.hide_pr_refs=Folaigh Iarrataí Tarraing +commit_graph.monochrome=Mona +commit_graph.color=Dath +commit.contained_in=Tá an tiomantas seo le fáil i: +commit.contained_in_default_branch=Tá an tiomantas seo mar chuid den bhrainse réamhshocraithe +commit.load_referencing_branches_and_tags=Luchtaigh brainsí agus clibeanna a thagraíonn an tiomantas +blame=An milleán +download_file=Íoslódáil comhad +normal_view=Amharc Gnáth +line=líne +lines=línte +from_comment=(trácht) +editor.add_file=Cuir Comhad +editor.new_file=Comhad Nua +editor.upload_file=Uaslódáil Comhad +editor.edit_file=Cuir Comhad in eagar +editor.preview_changes=Athruithe Réamhamhar +editor.cannot_edit_lfs_files=Ní féidir comhaid LFS a chur in eagar sa chomhéadan gréasáin. +editor.cannot_edit_non_text_files=Ní féidir comhaid dhénártha a chur in eagar sa chomhéadan gréasáin. +editor.edit_this_file=Cuir Comhad in eagar +editor.this_file_locked=Tá an comhad faoi ghlas +editor.must_be_on_a_branch=Caithfidh tú a bheith ar bhrainse chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. +editor.fork_before_edit=Ní mór duit an stór seo a fhorcáil chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. +editor.delete_this_file=Scrios Comhad +editor.must_have_write_access=Caithfidh rochtain scríofa a bheith agat chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. +editor.file_delete_success=Tá an comhad "%s" scriosta. +editor.name_your_file=Ainmnigh do chomhad… +editor.filename_help=Cuir eolaire leis trína ainm a chlóscríobh ina dhiaidh sin le slash ('/'). Bain eolaire trí backspace a chlóscríobh ag tús an réimse ionchuir. +editor.or=nó +editor.cancel_lower=Cealaigh +editor.commit_signed_changes=Tiomantas Athruithe Sínithe +editor.commit_changes=Athruithe a Tiomantas +editor.add_tmpl=Cuir '{filename}' leis +editor.add=Cuir %s leis +editor.update=Nuashonraigh %s +editor.delete=Scrios %s +editor.patch=Cuir paiste i bhfeidh +editor.patching=Paisteáil: +editor.fail_to_apply_patch=Ní féidir paiste "%s" a chur i bhfeidhm +editor.new_patch=Paiste Nua +editor.commit_message_desc=Cuir cur síos leathnaithe roghnach leis… +editor.signoff_desc=Cuir leantóir sínithe ag an gcoiteoir ag deireadh na teachtaireachta logála tiomanta. +editor.commit_directly_to_this_branch=Tiomanta go díreach chuig an <strong class="branch-name">mbrainse %s</strong>. +editor.create_new_branch=Cruthaigh <strong>brainse nua</strong> don ghealltanas seo agus cuir tús le hiarratas tarraingthe. +editor.create_new_branch_np=Cruthaigh <strong>brainse nua</strong> don tiomantas seo. +editor.propose_file_change=Athrú comhad a mholadh +editor.new_branch_name=Ainmnigh an brainse nua don gealltanas seo +editor.new_branch_name_desc=Ainm brainse nua… +editor.cancel=Cealaigh +editor.filename_cannot_be_empty=Ní féidir ainm an chomhaid a bheith folamh. +editor.filename_is_invalid=Tá ainm an chomhaid neamhbhailí: "%s". +editor.branch_does_not_exist=Níl brainse "%s" ann sa stóras seo. +editor.branch_already_exists=Tá brainse "%s" ann cheana féin sa stóras seo. +editor.directory_is_a_file=Úsáidtear ainm eolaire "%s" cheana féin mar ainm comhaid sa stóras seo. +editor.file_is_a_symlink=Is nasc siombalach é `"%s". Ní féidir naisc shiombalacha a chur in eagar san eagarthóir gréasáin` +editor.filename_is_a_directory=Úsáidtear ainm comhaid "%s" cheana féin mar ainm eolaire sa stóras seo. +editor.file_editing_no_longer_exists=Níl an comhad atá á chur in eagar, "%s", ann sa stóras seo a thuilleadh. +editor.file_deleting_no_longer_exists=Níl an comhad atá á scriosadh, "%s", ann sa stóras seo a thuilleadh. +editor.file_changed_while_editing=Tá athrú tagtha ar ábhar an chomhad ó thosaigh tú ag eagarthóireacht <a target="_blank" rel="noopener noreferrer" href="%s">Cliceáil anseo</a> chun iad a fheiceáil nó Athru <strong>ithe a Tiomantas arís</strong> chun iad a fhorscríobh. +editor.file_already_exists=Tá comhad darb ainm "%s" ann cheana féin sa stóras seo. +editor.commit_id_not_matching=Ní mheaitseálann an ID Tiomanta leis an ID nuair a thosaigh tú ag eagarthóireacht. Tiomanta isteach i mbrainse paiste agus ansin cumaisc. +editor.push_out_of_date=Is cosúil go bhfuil an brú as dáta. +editor.commit_empty_file_header=Tiomantas comhad folamh +editor.commit_empty_file_text=Tá an comhad atá tú ar tí tiomantas folamh. Ar aghaidh? +editor.no_changes_to_show=Níl aon athruithe le taispeáint. +editor.fail_to_update_file=Theip ar nuashonrú/cruthú comhad "%s". +editor.fail_to_update_file_summary=Teachtaireacht Earráide: +editor.push_rejected_no_message=Dhiúltaigh an freastalaí an t-athrú gan teachtaireacht. Seiceáil Git Hooks le do thoil. +editor.push_rejected=Dhiúltaigh an freastalaí an t-athrú. Seiceáil Git Hooks le do thoil. +editor.push_rejected_summary=Teachtaireacht Diúltaithe Iomlán: +editor.add_subdir=Cuir eolaire leis… +editor.unable_to_upload_files=Theip ar uaslódáil comhaid go "%s" le hearráid: %v +editor.upload_file_is_locked=Tá comhad "%s" faoi ghlas ag %s. +editor.upload_files_to_dir=`Uaslódáil comhaid go "%s"` +editor.cannot_commit_to_protected_branch=Ní féidir gealltanas a thabhairt don bhrainse faoi chosaint "%s". +editor.no_commit_to_branch=Ní féidir tiomantas a thabhairt go díreach don bhrainse mar: +editor.user_no_push_to_branch=Ní féidir leis an úsáideoir brúigh go dtí an brainse +editor.require_signed_commit=Éilíonn an Brainse tiomantas sínithe +editor.cherry_pick=Roghnaigh silíní %s ar: +editor.revert=Fill %s ar: +commits.desc=Brabhsáil stair athraithe cód foinse. commits.commits=Tiomáintí +commits.no_commits=Níl aon ghealltanas i gcoiteann. Tá stair iomlán difriúil ag "%s" agus "%s". +commits.nothing_to_compare=Tá na brainsí seo cothrom. +commits.search.tooltip=Is féidir eochairfhocail a réamhfhostú le “údar:”, “committer:”, “after:”, nó “before:”, e.g. "fill an t-údar:Alice roimh: 2019-01-13". +commits.search_branch=An Brainse seo +commits.search_all=Gach Brainse +commits.author=Údar +commits.message=Teachtaireacht +commits.date=Dáta +commits.older=Níos sine +commits.newer=Níos nuaí +commits.signed_by=Sínithe ag +commits.signed_by_untrusted_user=Sínithe ag úsáideoir neamhiontaofa +commits.signed_by_untrusted_user_unmatched=Sínithe ag úsáideoir neamhiontaofa nach bhfuil ag teacht leis an gcoiste +commits.gpg_key_id=GPG Eochair ID +commits.ssh_key_fingerprint=Méarloirg Eochair SSH +commits.view_path=Féach ag an bpointe seo sa stair +commit.operations=Oibríochtaí +commit.revert=Téigh ar ais +commit.revert-header=Téigh ar ais: %s +commit.revert-content=Roghnaigh brainse chun filleadh ar: +commit.cherry-pick=Roghnaigh silíní +commit.cherry-pick-header=Roghnaigh silíní: %s +commit.cherry-pick-content=Roghnaigh brainse chun silíní a phiocadh air: commitstatus.error=Earráid +commitstatus.failure=Teip +commitstatus.pending=Ar feitheamh +commitstatus.success=Rath +ext_issues=Rochtain ar Saincheisteanna Seachtracha +ext_issues.desc=Nasc le rianaitheoir saincheisteanna seachtrach. +projects.desc=Saincheisteanna a bhainistiú agus tionscadail a tharraingt isteach. +projects.description=Cur síos (roghnach) +projects.description_placeholder=Cur síos +projects.create=Cruthaigh Tionscadal +projects.title=Teideal projects.new=Tionscadal Nua +projects.new_subheader=Déan do chuid oibre a chomhordú, a rianú agus a nuashonrú in aon áit amháin, ionas go bhfanann na tionscadail trédhearcach agus de réir sceidil. +projects.create_success=Tá an tionscadal "%s" cruthaithe. +projects.deletion=Scrios tionscadal +projects.deletion_desc=Má scriostar tionscadal, bainfear de gach saincheist a bhaineann leis é. Lean ort? +projects.deletion_success=Tá an tionscadal scriosta. +projects.edit=Cuir Tionscadal in Eagar +projects.edit_subheader=Eagraíonn tionscadail saincheisteanna agus rianaíonn siad dul chun cinn. +projects.modify=Cuir Tionscadal in Eagar +projects.edit_success=Tá an tionscadal "%s" nuashonraithe. +projects.type.none=Níl aon +projects.type.basic_kanban=Bunúsach Kanban +projects.type.bug_triage=Triáiseáil Fabht projects.template.desc=Teimpléad +projects.template.desc_helper=Roghnaigh teimpléad tionscadail chun tosú +projects.column.edit=Cuir Colún in eagar +projects.column.edit_title=Ainm +projects.column.new_title=Ainm +projects.column.new_submit=Cruthaigh Colún projects.column.new=Colún Nua +projects.column.set_default=Socraigh Réamhshocrú +projects.column.set_default_desc=Socraigh an colún seo mar réamhshocrú le haghaidh saincheisteanna agus tarraingtí gan chatagóir +projects.column.delete=Scrios Colún +projects.column.deletion_desc=Ag scriosadh colún tionscadail aistríonn gach saincheist ghaolmhar chuig an gcolún. Lean ar aghaidh? +projects.column.color=Dath +projects.open=Oscailte +projects.close=Dún +projects.column.assigned_to=Sannta do +projects.card_type.desc=Réamhamharcanna Cárta +projects.card_type.images_and_text=Íomhánna agus Téacs +projects.card_type.text_only=Téacs Amháin +issues.desc=Eagraigh tuarascálacha fabht, tascanna agus cloch mhíle. +issues.filter_assignees=Scagaire Sannaitheoir +issues.filter_milestones=Cloch Mhíle Scagaire +issues.filter_projects=Tionscadal Scagaire +issues.filter_labels=Lipéad Scagaire +issues.filter_reviewers=Athbhreithneoir Scagaire +issues.new=Eagrán Nua +issues.new.title_empty=Ní féidir leis an teideal a bheith folamh +issues.new.labels=Lipéid +issues.new.no_label=Gan Lipéad +issues.new.clear_labels=Lipéid shoiléir issues.new.projects=Tionscadail +issues.new.clear_projects=Tionscadail soiléire +issues.new.no_projects=Gan aon tionscadal +issues.new.open_projects=Tionscadail Oscailte +issues.new.closed_projects=Tionscadail Dúnta +issues.new.no_items=Gan aon earraí issues.new.milestone=Cloch Mhíle +issues.new.no_milestone=Gan Chloch Mhíle +issues.new.clear_milestone=Cloch Mhíle soiléir +issues.new.open_milestone=Clocha Míle Oscailte +issues.new.closed_milestone=Clocha Míle Dúnta +issues.new.assignees=Sannaitheoirí +issues.new.clear_assignees=Ceannaitheoirí soiléir +issues.new.no_assignees=Gan aon Sannaitheoirí +issues.new.no_reviewers=Gan athbhreithnithe +issues.new.blocked_user=Ní féidir saincheist a chruthú toisc go bhfuil úinéir an stórais bac ort. +issues.edit.already_changed=Ní féidir athruithe a shábháil ar an tsaincheist. Dealraíonn sé gur athraigh úsáideoir eile an t-ábhar cheana féin. Athnuachan an leathanach agus déan iarracht eagarthóireacht arís chun a gcuid athruithe a sheachaint +issues.edit.blocked_user=Ní féidir ábhar a chur in eagar toisc go bhfuil an póstaer nó úinéir an stórais bac ort. +issues.choose.get_started=Faigh Tosaigh +issues.choose.open_external_link=Oscailte +issues.choose.blank=Réamhshocrú +issues.choose.blank_about=Cruthaigh saincheist ó theimpléad réamhshocraithe. +issues.choose.ignore_invalid_templates=Rinneadh neamhaird ar theimpléid +issues.choose.invalid_templates=%v teimpléad neamhbhail(í) aimsíodh +issues.choose.invalid_config=Tá earráidí sa chumraíocht eisiúint: +issues.no_ref=Níl aon Brainse/Clib Sonraithe +issues.create=Cruthaigh Saincheist +issues.new_label=Lipéad Nua +issues.new_label_placeholder=Ainm lipéad +issues.new_label_desc_placeholder=Cur síos +issues.create_label=Cruthaigh Lipéad +issues.label_templates.title=Luchtaigh sraith réamhshainithe lipéid +issues.label_templates.info=Níl aon lipéid ann fós. Cruthaigh lipéad le 'Lipéad Nua' nó bain úsáid as tacar lipéad réamhshainithe: +issues.label_templates.helper=Roghnaigh tacar lipéad +issues.label_templates.use=Úsáid Sraith Lipéad +issues.label_templates.fail_to_load_file=Theip ar lódáil an chomhaid teimpléid lipéid "%s": %v +issues.add_label=cuireadh an lipéad %s %s leis +issues.add_labels=cuireadh na %s lipéid %s +issues.remove_label=bainte an %s lipéad %s +issues.remove_labels=bainte na %s lipéid %s +issues.add_remove_labels=chuir %s leis agus bhain %s lipéid %s +issues.add_milestone_at=`chuir seo leis an gcloch mhíle <b>%s</b> %s` +issues.add_project_at=`chuir seo leis an tionscadal <b>%s</b> %s` +issues.move_to_column_of_project=`aistrigh sé seo chuig %s i %s ar %s` +issues.change_milestone_at=`mionathraithe an chloch mhíle ó <b>%s</b> go <b>%s</b> %s` +issues.change_project_at=`mionathraithe an tionscadal ó <b>%s</b> go <b>%s</b> %s` +issues.remove_milestone_at=` bhain seo den <b>%s</b>chloch mhíle %s` +issues.remove_project_at=`bhain sé seo den <b>%s</b>an tionscadal %s` +issues.deleted_milestone=`(scriosta)` +issues.deleted_project=`(scriosta)` +issues.self_assign_at=`féin-shannta an %s seo` +issues.add_assignee_at=`a shannadh ag <b>%s</b> %s` +issues.remove_assignee_at=`a bhí gan shannadh ag <b>%s</b> %s` +issues.remove_self_assignment=`bhain siad a sannadh %s` +issues.change_title_at=`athraigh an teideal ó <b><strike>%s</strike></b> go <b>%s</b> %s` +issues.change_ref_at=`tagairt athraithe ó <b><strike>%s</strike></b> go <b>%s</b> %s` +issues.remove_ref_at=`bhaint an tagairt <b>%s</b> %s` +issues.add_ref_at=`Cuireadh an tagairt <b>%s</b> %s leis` +issues.delete_branch_at=`brainse scriosta <b>%s</b> %s` issues.filter_label=Lipéad +issues.filter_label_exclude=`Úsáid <code>alt</code> + <code>cliceáil/iontráil</code> chun lipéid a eisiamh` +issues.filter_label_no_select=Gach lipéad +issues.filter_label_select_no_label=Gan lipéad issues.filter_milestone=Cloch Mhíle +issues.filter_milestone_all=Gach cloch mhíle +issues.filter_milestone_none=Gan aon clocha mhíle +issues.filter_milestone_open=Clocha mhíle oscailte +issues.filter_milestone_closed=Clocha mhíle dúnta issues.filter_project=Tionscadal +issues.filter_project_all=Gach tionscadal +issues.filter_project_none=Gan aon tionscadal issues.filter_assignee=Sannaitheoir +issues.filter_assginee_no_select=Gach sannaithe +issues.filter_assginee_no_assignee=Gan sannaitheoir +issues.filter_poster=Údar +issues.filter_poster_no_select=Gach údair +issues.filter_type=Cineál +issues.filter_type.all_issues=Gach saincheist +issues.filter_type.assigned_to_you=Sannta duit +issues.filter_type.created_by_you=Cruthaithe agat +issues.filter_type.mentioning_you=Ag tagairt duit +issues.filter_type.review_requested=Athbhreithniú iarrtha +issues.filter_type.reviewed_by_you=Athbhreithnithe agat +issues.filter_sort=Sórtáil +issues.filter_sort.latest=Is nuaí +issues.filter_sort.oldest=Is sine +issues.filter_sort.recentupdate=Nuashonraithe le déanaí +issues.filter_sort.leastupdate=Is lú a nuashonraíodh le déanaí +issues.filter_sort.mostcomment=Is mó a bhfuil tráchtanna air +issues.filter_sort.leastcomment=Is lú a bhfuil tráchtanna air +issues.filter_sort.nearduedate=An dáta dlite is gaire +issues.filter_sort.farduedate=An dáta dlite is faide +issues.filter_sort.moststars=An líon réaltaí is mó +issues.filter_sort.feweststars=An líon réaltaí is lú +issues.filter_sort.mostforks=An líon forcanna is mó +issues.filter_sort.fewestforks=An líon forcanna is lú +issues.action_open=Oscailte +issues.action_close=Dún issues.action_label=Lipéad issues.action_milestone=Cloch Mhíle +issues.action_milestone_no_select=Gan Chloch Mhíle issues.action_assignee=Sannaitheoir +issues.action_assignee_no_select=Gan sannaitheoir +issues.action_check=Seiceáil/Dísheiceáil +issues.action_check_all=Seiceáil/Dísheiceáil gach mireanna +issues.opened_by=oscail %[1]s le <a href="%[2]s">%[3]s</a> +pulls.merged_by=le <a href="%[2]s">%[3]s</a> cumasc %[1]s +pulls.merged_by_fake=le %[2]s a chumasc %[1]s +issues.closed_by=le <a href="%[2]s">dúnadh %[3]s</a> %[1]s +issues.opened_by_fake=oscail %[1]s le %[2]s +issues.closed_by_fake=faoi %[2]s dúnadh %[1]s +issues.previous=Roimhe Seo +issues.next=Ar Aghaidh +issues.open_title=Oscailte +issues.closed_title=Dúnta +issues.draft_title=Dréacht +issues.num_comments_1=%d trácht +issues.num_comments=%d tráchtanna +issues.commented_at=`trácht <a href="#%s">%s</a> ` +issues.delete_comment_confirm=An bhfuil tú cinnte gur mhaith leat an trácht seo a scriosadh? +issues.context.copy_link=Cóipeáil Nasc +issues.context.quote_reply=Luaigh Freagra +issues.context.reference_issue=Tagairt in Eagrán Nua +issues.context.edit=Cuir in eagar +issues.context.delete=Scrios +issues.no_content=Níl aon tuairisc ar fáil. +issues.close=Dún Eagrán +issues.comment_pull_merged_at=cumasc tiomantas %[1]s le %[2]s %[3]s +issues.comment_manually_pull_merged_at=cumasc tiomantas %[1]s le %[2]s %[3]s +issues.close_comment_issue=Dún le trácht +issues.reopen_issue=Athoscail +issues.reopen_comment_issue=Athoscail le trácht +issues.create_comment=Trácht +issues.comment.blocked_user=Ní féidir trácht a chruthú nó a chur in eagar toisc go bhfuil an tráchtaire nó úinéir an stórais bac ort. +issues.closed_at=`dhún an cheist seo <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.reopened_at=`athoscail an t-eagrán seo <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.commit_ref_at=`rinne tagairt don cheist seo ó ghealltanas <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_issue_from=`<a href="%[3]s">rinne dagairt don cheist seo %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_pull_from=`<a href="%[3]s">rinne dagairt don iarratas tarraingthe seo %[4]s</a> <a id="%[1]s" href="#%[1]s">%[ 2]s</a>` +issues.ref_closing_from=`<a href="%[3]s">rinne dagairt d'iarratas tarraingthe %[4]s a dhúnfaidh an cheist seo</a> <a id="%[1]s" href="#%[1] s">%[2]s</a>` +issues.ref_reopening_from=`<a href="%[3]s">rinne dagairt d'iarratas tarraingthe %[4]s a athosclóidh an cheist seo</a> <a id="%[1]s" href="#%[1] s">%[2]s</a>` +issues.ref_closed_from=`<a href="%[3]s">dhún an cheist seo %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_reopened_from=`<a href="%[3]s">d'athoscail an eagrán seo %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_from=`ó %[1]s` +issues.author=Údar +issues.author_helper=Is é an t-úsáideoir seo an t-údar. +issues.role.owner=Úinéir +issues.role.owner_helper=Is é an t-úsáideoir seo úinéir an stór seo. +issues.role.member=Comhalta +issues.role.member_helper=Is ball den eagraíocht é an t-úsáideoir seo a bhfuil an stór seo ina úinéireacht. +issues.role.collaborator=Comhoibritheoir +issues.role.collaborator_helper=Tugadh cuireadh don úsáideoir seo comhoibriú ar an stóras. +issues.role.first_time_contributor=Cuiditheoir den chéad uair +issues.role.first_time_contributor_helper=Seo é an chéad uair a chuir an t-úsáideoir seo leis an stóras. +issues.role.contributor=Cuiditheoir +issues.role.contributor_helper=Tá an t-úsáideoir seo tiomanta don stóras roimhe seo. +issues.re_request_review=Athiarraigh athbhreithniú +issues.is_stale=Rinneadh athruithe ar an PR seo ón athbhreithniú seo +issues.remove_request_review=Bain iarratas athbhreithni +issues.remove_request_review_block=Ní féidir iarratas athbhreithnithe a bhaint +issues.dismiss_review=Díbhe Athbhreithnithe +issues.dismiss_review_warning=An bhfuil tú cinnte gur mhaith leat an athbhreithnithe seo a dhíbhe? +issues.sign_in_require_desc=<a href="%s">Sínigh isteach</a> chun dul isteach sa chomhrá seo. +issues.edit=Cuir in eagar +issues.cancel=Cealaigh issues.save=Sábháil +issues.label_title=Ainm +issues.label_description=Cur síos +issues.label_color=Dath +issues.label_exclusive=Eisiach +issues.label_archive=Lipéad Cartlann +issues.label_archived_filter=Taispeáin lipéid cartlainne +issues.label_archive_tooltip=Eisiatar lipéid chartlainne de réir réamhshocraithe ó na moltaí nuair a dhéantar cuardach de réir lipéid. +issues.label_exclusive_desc=Ainmnigh an lipéad <code>scope/item</code> chun é a dhéanamh comheisiatach le lipéid <code>scope/</code> eile. +issues.label_exclusive_warning=Bainfear aon lipéid scóipe contrártha le linn eagarthóireacht a dhéanamh ar lipéid iarratais eisiúna nó tarraingthe. +issues.label_count=%d lipéid +issues.label_open_issues=%d saincheisteanna oscailte/iarratais tarraing +issues.label_edit=Cuir in eagar +issues.label_delete=Scrios +issues.label_modify=Cuir Lipéad in Eagar +issues.label_deletion=Scrios Lipéad +issues.label_deletion_desc=Baineann lipéad a scriosadh é ó gach saincheist. Lean ar aghaidh? +issues.label_deletion_success=Tá an lipéad scriosta. +issues.label.filter_sort.alphabetically=Aibítreach +issues.label.filter_sort.reverse_alphabetically=Aisiompú in ord aibítre +issues.label.filter_sort.by_size=Méid is lú +issues.label.filter_sort.reverse_by_size=Méid is mó +issues.num_participants=%d Rannpháirtithe +issues.attachment.open_tab=`Cliceáil chun "%s" a fheiceáil i gcluaisín nua` +issues.attachment.download=`Cliceáil chun "%s" a íoslódáil +issues.subscribe=Liostáil +issues.unsubscribe=Díliostáil +issues.unpin_issue=Bain pionna an t-eagrán +issues.max_pinned=Ní féidir leat níos mó saincheisteanna a phionadh +issues.pin_comment=phionnáil an %s seo +issues.unpin_comment=bain pionna an %s seo +issues.lock=Cuir glas ar an gcomhrá +issues.unlock=Díghlasáil comhrá +issues.lock.unknown_reason=Ní féidir fadhb a ghlasáil le cúis anaithnid. +issues.lock_duplicate=Ní féidir saincheist a ghlasáil faoi dhó. +issues.unlock_error=Ní féidir saincheist nach bhfuil glasáilte a dhíghlasáil. +issues.lock_with_reason=curtha ar ceal mar <strong>%s</strong> agus comhrá teoranta do chomhoibrithe %s +issues.lock_no_reason=comhrá faoi ghlas agus teoranta do chomhoibrithe %s +issues.unlock_comment=an comhrá seo a dhíghlasáil %s +issues.lock_confirm=Glas +issues.unlock_confirm=Díghlasáil +issues.lock.notice_1=- Ní féidir le húsáideoirí eile tuairimí nua a chur leis an gceist seo. +issues.lock.notice_2=- Is féidir leatsa agus le comhoibrithe eile a bhfuil rochtain acu ar an stór seo fós tuairimí a fhágáil a fheiceann daoine eile. +issues.lock.notice_3=- Is féidir leat an tsaincheist seo a dhíghlasáil arís sa todhchaí. +issues.unlock.notice_1=- Bheadh gach duine in ann trácht a dhéanamh ar an gceist seo arís. +issues.unlock.notice_2=- Is féidir leat an tsaincheist seo a ghlasáil arís sa todhchaí i gcónaí. +issues.lock.reason=Cúis le glasáil +issues.lock.title=Glas comhrá ar an gceist seo. +issues.unlock.title=Díghlasáil comhrá ar an gceist seo. +issues.comment_on_locked=Ní féidir leat trácht a dhéanamh ar shaincheist faoi ghlas. +issues.delete=Scrios +issues.delete.title=Scrios an t-eagrán seo? pulls.tab_commits=Tiomáintí @@ -1274,6 +1670,8 @@ pulls.tab_commits=Tiomáintí +milestones.open=Oscailte +milestones.close=Dún @@ -1281,18 +1679,26 @@ wiki=Wiki wiki.page=Leathanach wiki.new_page=Leathanach +activity.closed_issue_label=Dúnta +activity.new_issues_count_1=Eagrán Nua +activity.unresolved_conv_label=Oscailte contributors.contribution_type.commits=Tiomáintí settings.githooks=Crúcanna Git +settings.projects_mode_all=Gach tionscadal +settings.trust_model.collaborator=Comhoibritheoir settings.trust_model.collaborator.long=Comhoibritheoir: Sínithe muinín ag comhoibrithe settings.trust_model.collaboratorcommitter.long=Comhoibritheo+Coiteoir: Sínithe iontaobhais ag comhoibritheoirí a mheaitseann leis an gealltóir +settings.slack_color=Dath settings.event_fork=Forc settings.event_wiki=Wiki +settings.event_release=Scaoileadh +branch.branch_already_exists=Tá brainse "%s" ann cheana féin sa stóras seo. @@ -1338,6 +1744,7 @@ dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo +notices.operations=Oibríochtaí [action] @@ -1371,4 +1778,5 @@ runs.commit=Tiomantas [git.filemode] ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +symbolic_link=Nasc siombalach From 66923e02d20e9d5b68ab20fbcdebd779eb4dbaf9 Mon Sep 17 00:00:00 2001 From: Zisu Zhang <thezzisu@gmail.com> Date: Sun, 6 Oct 2024 04:41:38 +0800 Subject: [PATCH 11/17] Enhance USER_DISABLED_FEATURES to allow disabling change username or full name (#31959) Fix #31958 Enhanced `USER_DISABLED_FEATURES`(also `EXTERNAL_USER_DISABLE_FEATURES`) option in `[admin]` section. Added following values: - `change_username`: Disable change username - `change_full_name`: Disable change full name --- Progress: - [x] Update code - [x] Update translations --- custom/conf/app.example.ini | 2 ++ modules/setting/admin.go | 2 ++ options/locale/locale_en-US.ini | 5 ++++- routers/web/user/setting/profile.go | 16 +++++++++++++++- templates/user/settings/profile.tmpl | 9 ++++++--- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 69d541ff8d40..7c7a43944f09 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1507,6 +1507,8 @@ LEVEL = Info ;; - manage_gpg_keys: a user cannot configure gpg keys ;; - manage_mfa: a user cannot configure mfa devices ;; - manage_credentials: a user cannot configure emails, passwords, or openid +;; - change_username: a user cannot change their username +;; - change_full_name: a user cannot change their full name ;;EXTERNAL_USER_DISABLE_FEATURES = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/setting/admin.go b/modules/setting/admin.go index ca4e9b1d5835..fde291ade94a 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -29,4 +29,6 @@ const ( UserFeatureManageGPGKeys = "manage_gpg_keys" UserFeatureManageMFA = "manage_mfa" UserFeatureManageCredentials = "manage_credentials" + UserFeatureChangeUsername = "change_username" + UserFeatureChangeFullName = "change_full_name" ) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e3b17f9a04f2..a02d939b79ed 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -580,6 +580,8 @@ lang_select_error = Select a language from the list. username_been_taken = The username is already taken. username_change_not_local_user = Non-local users are not allowed to change their username. +change_username_disabled = Changing username is disabled. +change_full_name_disabled = Changing full name is disabled. username_has_not_been_changed = Username has not been changed repo_name_been_taken = The repository name is already used. repository_force_private = Force Private is enabled: private repositories cannot be made public. @@ -705,7 +707,8 @@ public_profile = Public Profile biography_placeholder = Tell us a little bit about yourself! (You can use Markdown) location_placeholder = Share your approximate location with others profile_desc = Control how your profile is show to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations. -password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. +password_username_disabled = You are not allowed to change their username. Please contact your site administrator for more details. +password_full_name_disabled = You are not allowed to change their full name. Please contact your site administrator for more details. full_name = Full Name website = Website location = Location diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 554f6cd6ce3f..3b051c9b5f4b 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -69,6 +69,11 @@ func ProfilePost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.UpdateProfileForm) if form.Name != "" { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureChangeUsername) { + ctx.Flash.Error(ctx.Tr("user.form.change_username_disabled")) + ctx.Redirect(setting.AppSubURL + "/user/settings") + return + } if err := user_service.RenameUser(ctx, ctx.Doer, form.Name); err != nil { switch { case user_model.IsErrUserIsNotLocal(err): @@ -91,7 +96,6 @@ func ProfilePost(ctx *context.Context) { } opts := &user_service.UpdateOptions{ - FullName: optional.Some(form.FullName), KeepEmailPrivate: optional.Some(form.KeepEmailPrivate), Description: optional.Some(form.Description), Website: optional.Some(form.Website), @@ -99,6 +103,16 @@ func ProfilePost(ctx *context.Context) { Visibility: optional.Some(form.Visibility), KeepActivityPrivate: optional.Some(form.KeepActivityPrivate), } + + if form.FullName != "" { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureChangeFullName) { + ctx.Flash.Error(ctx.Tr("user.form.change_full_name_disabled")) + ctx.Redirect(setting.AppSubURL + "/user/settings") + return + } + opts.FullName = optional.Some(form.FullName) + } + if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil { ctx.ServerError("UpdateUser", err) return diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index aaaf8f30db6d..9c7e2de21832 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -12,14 +12,17 @@ <span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span> <span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span> </label> - <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40"> - {{if or (not .SignedUser.IsLocal) .IsReverseProxy}} + <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40"> + {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}} <p class="help text blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p> {{end}} </div> <div class="field {{if .Err_FullName}}error{{end}}"> <label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label> - <input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100"> + <input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" {{if ($.UserDisabledFeatures.Contains "change_full_name")}}disabled{{end}} maxlength="100"> + {{if ($.UserDisabledFeatures.Contains "change_full_name")}} + <p class="help text blue">{{ctx.Locale.Tr "settings.password_full_name_disabled"}}</p> + {{end}} </div> <div class="field {{if .Err_Email}}error{{end}}"> <label>{{ctx.Locale.Tr "email"}}</label> From 479c31bb4df2bad155035ad6706d830f874e533b Mon Sep 17 00:00:00 2001 From: Yarden Shoham <git@yardenshoham.com> Date: Sat, 5 Oct 2024 23:52:30 +0300 Subject: [PATCH 12/17] Upgrade htmx to 2.0.3 (#32192) Release notes: https://github.com/bigskysoftware/htmx/releases/tag/v2.0.3 Tested `Star`, `Watch`, and the admin dashboard page. All functionality remains unchanged. Signed-off-by: Yarden Shoham <git@yardenshoham.com> --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0e83b60ec24..ca001b293930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "esbuild-loader": "4.2.2", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "2.0.2", + "htmx.org": "2.0.3", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.11", @@ -10548,10 +10548,9 @@ } }, "node_modules/htmx.org": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.2.tgz", - "integrity": "sha512-eUPIpQaWKKstX393XNCRCMJTrqPzikh36Y9RceqsUZLTtlFjFaVDgwZLUsrFk8J2uzZxkkfiy0TE359j2eN6hA==", - "license": "0BSD" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz", + "integrity": "sha512-AeoJUAjkCVVajbfKX+3sVQBTCt8Ct4lif1T+z/tptTXo8+8yyq3QIMQQe/IT+R8ssfrO1I0DeX4CAronzCL6oA==" }, "node_modules/human-signals": { "version": "5.0.0", diff --git a/package.json b/package.json index d188e99a30d8..015dfc67a689 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "esbuild-loader": "4.2.2", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "2.0.2", + "htmx.org": "2.0.3", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.11", From e53056866674691864c4dfdddb3dfc629a06cbba Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Sun, 6 Oct 2024 00:34:29 +0000 Subject: [PATCH 13/17] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 - options/locale/locale_de-DE.ini | 1 - options/locale/locale_el-GR.ini | 1 - options/locale/locale_es-ES.ini | 1 - options/locale/locale_fa-IR.ini | 1 - options/locale/locale_fi-FI.ini | 1 - options/locale/locale_fr-FR.ini | 1 - options/locale/locale_ga-IE.ini | 446 +++++++++++++++++++++++++++++++- options/locale/locale_hu-HU.ini | 1 - options/locale/locale_id-ID.ini | 1 - options/locale/locale_is-IS.ini | 1 - options/locale/locale_it-IT.ini | 1 - options/locale/locale_ja-JP.ini | 3 +- options/locale/locale_ko-KR.ini | 1 - options/locale/locale_lv-LV.ini | 1 - options/locale/locale_nl-NL.ini | 1 - options/locale/locale_pl-PL.ini | 1 - options/locale/locale_pt-BR.ini | 1 - options/locale/locale_pt-PT.ini | 1 - options/locale/locale_ru-RU.ini | 1 - options/locale/locale_si-LK.ini | 1 - options/locale/locale_sk-SK.ini | 1 - options/locale/locale_sv-SE.ini | 1 - options/locale/locale_tr-TR.ini | 1 - options/locale/locale_uk-UA.ini | 1 - options/locale/locale_zh-CN.ini | 1 - options/locale/locale_zh-TW.ini | 1 - 27 files changed, 446 insertions(+), 28 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 07cbfe2c8f93..2bf2a968807b 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -689,7 +689,6 @@ public_profile=Veřejný profil biography_placeholder=Řekněte nám něco o sobě! (Můžete použít Markdown) location_placeholder=Sdílejte svou přibližnou polohu s ostatními profile_desc=Nastavte, jak bude váš profil zobrazen ostatním uživatelům. Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla a operace Git. -password_username_disabled=Externí uživatelé nemohou měnit svoje uživatelské jméno. Kontaktujte prosím svého administrátora pro více detailů. full_name=Celé jméno website=Web location=Místo diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 7512c5410152..4f011663e883 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -683,7 +683,6 @@ public_profile=Öffentliches Profil biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden) location_placeholder=Teile Deinen ungefähren Standort mit anderen profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet. -password_username_disabled=Benutzer, die nicht von Gitea verwaltet werden können ihren Benutzernamen nicht ändern. Bitte kontaktiere deinen Administrator für mehr Details. full_name=Vollständiger Name website=Webseite location=Standort diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 48610abb34e1..8a82d9353a30 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -620,7 +620,6 @@ public_profile=Δημόσιο Προφίλ biography_placeholder=Πείτε μας λίγο για τον εαυτό σας! (Μπορείτε να γράψετε με Markdown) location_placeholder=Μοιραστείτε την κατά προσέγγιση τοποθεσία σας με άλλους profile_desc=Ελέγξτε πώς εμφανίζεται το προφίλ σας σε άλλους χρήστες. Η κύρια διεύθυνση email σας θα χρησιμοποιηθεί για ειδοποιήσεις, ανάκτηση κωδικού πρόσβασης και λειτουργίες Git που βασίζονται στο web. -password_username_disabled=Οι μη τοπικοί χρήστες δεν επιτρέπεται να αλλάξουν το όνομα χρήστη τους. Επικοινωνήστε με το διαχειριστή σας για περισσότερες λεπτομέρειες. full_name=Πλήρες Όνομα website=Ιστοσελίδα location=Τοποθεσία diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 7d637f3d3ab3..423f74f8b206 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -617,7 +617,6 @@ public_profile=Perfil público biography_placeholder=¡Cuéntanos un poco sobre ti mismo! (Puedes usar Markdown) location_placeholder=Comparte tu ubicación aproximada con otros profile_desc=Controla cómo se muestra su perfil a otros usuarios. Tu dirección de correo electrónico principal se utilizará para notificaciones, recuperación de contraseña y operaciones de Git basadas en la web. -password_username_disabled=Usuarios no locales no tienen permitido cambiar su nombre de usuario. Por favor, contacta con el administrador del sistema para más detalles. full_name=Nombre completo website=Página web location=Localización diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 38fb70dae021..33d72ee7eb7f 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -491,7 +491,6 @@ account_link=حسابهای مرتبط organization=سازمان ها public_profile=نمایه عمومی -password_username_disabled=حسابهای غیر محلی مجاز به تغییر نام کاربری نیستند. لطفا با مدیر سایت در ارتباط باشید. full_name=نام کامل website=تارنما location=موقعیت مکانی diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 200e104f46b1..d16efb8834d3 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -451,7 +451,6 @@ account_link=Linkitetyt tilit organization=Organisaatiot public_profile=Julkinen profiili -password_username_disabled=Ei-paikalliset käyttäjät eivät voi muuttaa käyttäjätunnustaan. Ole hyvä ja ota yhteyttä sivuston ylläpitäjään saadaksesi lisätietoa. full_name=Kokonimi website=Nettisivut location=Sijainti diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 1ce04640e345..e64c85b7a4e5 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -705,7 +705,6 @@ public_profile=Profil public biography_placeholder=Parlez-nous un peu de vous ! (Vous pouvez utiliser Markdown) location_placeholder=Partagez votre position approximative avec d'autres personnes profile_desc=Contrôlez comment votre profil est affiché aux autres utilisateurs. Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et les opérations Git basées sur le Web. -password_username_disabled=Les utilisateurs externes ne sont pas autorisés à modifier leur nom d'utilisateur. Veuillez contacter l'administrateur de votre site pour plus de détails. full_name=Nom complet website=Site Web location=Localisation diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 826ac0accef4..23e0839530e0 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -705,7 +705,6 @@ public_profile=Próifíl Phoiblí biography_placeholder=Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid) location_placeholder=Comhroinn do shuíomh thart le daoine eile profile_desc=Rialú conas a thaispeánfar do phróifíl d'úsáideoirí eile. Úsáidfear do phríomhsheoladh ríomhphoist le haghaidh fógraí, aisghabháil pasfhocail agus oibríochtaí Git gréasán-bhunaithe. -password_username_disabled=Ní cheadaítear d'úsáideoirí neamháitiúla a n-ainm úsáideora a athrú. Déan teagmháil le do riarthóir láithreáin le haghaidh tuilleadh sonraí full_name=Ainm Iomlán website=Láithreán Gréasáin location=Suíomh @@ -1657,45 +1656,488 @@ issues.unlock.title=Díghlasáil comhrá ar an gceist seo. issues.comment_on_locked=Ní féidir leat trácht a dhéanamh ar shaincheist faoi ghlas. issues.delete=Scrios issues.delete.title=Scrios an t-eagrán seo? +issues.delete.text=An bhfuil tú cinnte gur mhaith leat an cheist seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann) +issues.tracker=Rianaitheoir Ama +issues.start_tracking_short=Tosaigh Uaineoir +issues.start_tracking=Rianú Am Tosaigh +issues.start_tracking_history=`thosaigh sé ag obair %s` +issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo +issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!` +issues.stop_tracking=Stop Uaineadóir +issues.stop_tracking_history=`stop sé ag obair %s` +issues.cancel_tracking=Caith amach +issues.cancel_tracking_history=`rianú ama curtha ar ceal %s` +issues.add_time=Láimh Cuir Am leis +issues.del_time=Scrios an log ama seo +issues.add_time_short=Cuir Am leis +issues.add_time_cancel=Cealaigh +issues.add_time_history=`am caite curtha leis %s` +issues.del_time_history=`an t-am caite scriosta %s` +issues.add_time_hours=Uaireanta +issues.add_time_minutes=Miontuairi +issues.add_time_sum_to_small=Níor iontráilíodh aon am. +issues.time_spent_total=An t-am iomlán a chaitear +issues.time_spent_from_all_authors=`Am Iomlán Caitear: %s` +issues.due_date=Dáta dlite +issues.invalid_due_date_format=Ní mór 'bbbb-mm-ll' a bheith i bhformáid an dáta dlite. +issues.error_modifying_due_date=Theip ar an dáta dlite a mhodhnú. +issues.error_removing_due_date=Theip ar an dáta dlite a bhaint. +issues.push_commit_1=Cuir %d gealltanas %s leis +issues.push_commits_n=cuireadh %d tiomantas %s leis +issues.force_push_codes=`bhrú i bhfeidhm %[1]s ó <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> go <a class=" ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s` +issues.force_push_compare=Déan comparáid +issues.due_date_form=bbbb-mm-ll +issues.due_date_form_add=Cuir dáta dlite leis +issues.due_date_form_edit=Cuir in eagar +issues.due_date_form_remove=Bain +issues.due_date_not_writer=Ní mór duit rochtain scríofa ar an stór seo d'fhonn dáta dlite eisiúna a nuashonrú. +issues.due_date_not_set=Níl aon dáta dlite socraithe. +issues.due_date_added=cuireadh an dáta dlite %s %s +issues.due_date_modified=d'athraigh an dáta dlite ó %[2]s go %[1]s %[3]s +issues.due_date_remove=bainte an dáta dlite %s %s +issues.due_date_overdue=Thar téarma +issues.due_date_invalid=Tá an dáta dlite neamhbhailí nó lasmuigh den raon. Úsáid an fhormáid 'bbbb-mm-ll' le do thoil. +issues.dependency.title=Spleithiúlachtaí +issues.dependency.issue_no_dependencies=Níl aon spleáchais leagtha síos. +issues.dependency.pr_no_dependencies=Níl aon spleáchais leagtha síos. +issues.dependency.no_permission_1=Níl cead agat spleáchas %d a léamh +issues.dependency.no_permission_n=Níl cead agat spleáchais %d a léamh +issues.dependency.no_permission.can_remove=Níl cead agat an spleáchas seo a léamh ach is féidir leis an spleáchas seo a bhaint +issues.dependency.add=Cuir spleáchas leis… +issues.dependency.cancel=Cealaigh +issues.dependency.remove=Bain +issues.dependency.remove_info=Bain an spleáchas seo +issues.dependency.added_dependency=`cuireadh spleáchas nua %s` +issues.dependency.removed_dependency=`bainte spleáchas %s` +issues.dependency.pr_closing_blockedby=Cuireann na saincheisteanna seo a leanas bac ar an iarratas tarraingte seo a dhúnadh +issues.dependency.issue_closing_blockedby=Tá na saincheisteanna seo a leanas bac ar dhúnadh an cheist seo +issues.dependency.issue_close_blocks=Cuireann an tsaincheist seo bac ar dhúnadh na saincheisteanna +issues.dependency.pr_close_blocks=Cuireann an iarratas tarraingthe seo bac ar dhúnadh na saincheisteanna +issues.dependency.issue_close_blocked=Ní mór duit gach saincheist a chuireann bac ar an gceist seo a dhúnadh sular féidir leat é a dhúnadh. +issues.dependency.issue_batch_close_blocked=Ní féidir saincheisteanna a roghnaíonn tú a dhúnadh, toisc go bhfuil spleáchais oscailte fós ag eisiúint #%d +issues.dependency.pr_close_blocked=Ní mór duit gach saincheist a bhlocálann an iarratas tarraingthe seo a dhúnadh sula féidir leat é a chumasc. +issues.dependency.blocks_short=Bloic +issues.dependency.blocked_by_short=Ag brath ar +issues.dependency.remove_header=Bain spleáchas +issues.dependency.issue_remove_text=Bainfidh sé seo an spleáchas ón gceist seo. Lean ar aghaidh? +issues.dependency.pr_remove_text=Bainfidh sé seo an spleáchas ón iarratas tarraingthe seo. Lean ar aghaidh? +issues.dependency.setting=Cumasaigh spleáchais le haghaidh Saincheisteanna agus Iarrataí Tar +issues.dependency.add_error_same_issue=Ní féidir leat ceist a dhéanamh ag brath air féin. +issues.dependency.add_error_dep_issue_not_exist=Níl saincheist spleách ann. +issues.dependency.add_error_dep_not_exist=Ní bhíonn spleáchas ann. +issues.dependency.add_error_dep_exists=Tá spleáchas ann cheana féin. +issues.dependency.add_error_cannot_create_circular=Ní féidir leat spleáchas a chruthú le dhá shaincheist a chuireann bac ar a chéile. +issues.dependency.add_error_dep_not_same_repo=Caithfidh an dá shaincheist a bheith sa stór céanna. +issues.review.self.approval=Ní féidir leat d'iarratas tarraingthe féin a cheadú. +issues.review.self.rejection=Ní féidir leat athruithe a iarraidh ar d'iarratas tarraingthe féin. +issues.review.approve=ceadaigh na hathruithe seo %s +issues.review.comment=athbhreithnithe %s +issues.review.dismissed=dhiúltaigh athbhreithniú %s ó %s +issues.review.dismissed_label=Dhiúltaigh +issues.review.left_comment=d'fhág trácht +issues.review.content.empty=Ní mór duit trácht a fhágáil a léiríonn an t-athrú (í) iarrtha. +issues.review.reject=athruithe iarrtha %s +issues.review.wait=iarradh athbhreithniú %s +issues.review.add_review_request=athbhreithniú iarrtha ó %s %s +issues.review.remove_review_request=iarratas athbhreithnithe bainte le haghaidh %s %s +issues.review.remove_review_request_self=dhiúltaigh %s a athbhreithniú +issues.review.pending=Ar feitheamh +issues.review.pending.tooltip=Níl an nóta tráchta seo le feiceáil ag úsáideoirí eile faoi láthair. Chun do thuairimí ar feitheamh a chur isteach, roghnaigh "%s" -> "%s/%s/%s" ag barr an leathanaigh. +issues.review.review=Léirmheas +issues.review.reviewers=Léirmheasóirí +issues.review.outdated=As dáta +issues.review.outdated_description=Tá athrú tagtha ar ábhar ó rinneadh an trácht seo +issues.review.option.show_outdated_comments=Taispeáin tráchtanna atá as dáta +issues.review.option.hide_outdated_comments=Folaigh tráchtanna atá as dáta +issues.review.show_outdated=Taispeáin as dáta +issues.review.hide_outdated=Folaigh as dáta +issues.review.show_resolved=Taispeáin réitithe +issues.review.hide_resolved=Folaigh réitithe +issues.review.resolve_conversation=Réitigh comhrá +issues.review.un_resolve_conversation=Comhrá gan réiteach +issues.review.resolved_by=mharcáil an comhrá seo mar réitigh +issues.review.commented=Trácht +issues.review.official=Ceadaithe +issues.review.requested=Athbhreithniú ar feitheamh +issues.review.rejected=Athruithe iarrtha +issues.review.stale=Nuashonraithe ó faomhadh +issues.review.unofficial=Ceadú gan áireamh +issues.assignee.error=Níor cuireadh gach sannaí leis mar gheall ar earráid gan choinne. +issues.reference_issue.body=Comhlacht +issues.content_history.deleted=scriosta +issues.content_history.edited=curtha in eagar +issues.content_history.created=cruthaithe +issues.content_history.delete_from_history=Scrios ón stair +issues.content_history.delete_from_history_confirm=Scrios ón stair? +issues.content_history.options=Roghanna +issues.reference_link=Tagairt: %s +compare.compare_base=bonn +compare.compare_head=déan comparáid +pulls.desc=Cumasaigh iarratais tarraingthe agus athbhreithnithe cód. +pulls.new=Iarratas Tarraingthe Nua +pulls.new.blocked_user=Ní féidir iarratas tarraingthe a chruthú toisc go bhfuil úinéir an stórais bac ort. +pulls.new.must_collaborator=Caithfidh tú a bheith ina chomhoibritheoir chun iarratas tarraingthe a chruthú. +pulls.edit.already_changed=Ní féidir athruithe a shábháil ar an iarratas tarraingthe. Dealraíonn sé gur athraigh úsáideoir eile an t-ábhar cheana féin. Athnuachan an leathanach agus déan iarracht eagarthóireacht arís chun a gcuid athruithe a sheachaint +pulls.view=Féach ar Iarratas Tarraing +pulls.compare_changes=Iarratas Tarraingthe Nua +pulls.allow_edits_from_maintainers=Ceadaigh eagarthóirí ó chothabhálaí +pulls.allow_edits_from_maintainers_desc=Is féidir le húsáideoirí a bhfuil rochtain scríofa acu ar an mbunbhrainse brú chuig an bhrainse +pulls.allow_edits_from_maintainers_err=Theip ar nuashonrú +pulls.compare_changes_desc=Roghnaigh an brainse le cumasc isteach agus an brainse le tarraingt uaidh. +pulls.has_viewed_file=Breathnaithe +pulls.has_changed_since_last_review=Athraithe ó d'athbhreithniú deire +pulls.viewed_files_label=Breathnaíodh ar %[1]d / %[2]d comhaid +pulls.expand_files=Leathnaigh gach comhaid +pulls.collapse_files=Laghdaigh gach comhaid +pulls.compare_base=cumaisc isteach +pulls.compare_compare=tarraing ó +pulls.switch_comparison_type=Athraigh cineál comparáide +pulls.switch_head_and_base=Athraigh ceann agus bonn +pulls.filter_branch=Brainse scagaire +pulls.show_all_commits=Taispeáin gach gealltanas +pulls.show_changes_since_your_last_review=Taispeáin athruithe ón léirmheas deiridh +pulls.showing_only_single_commit=Ag taispeáint athruithe tiomantais %[1]s amháin +pulls.showing_specified_commit_range=Ag taispeáint athruithe idir %[1]s..%[2]s +pulls.select_commit_hold_shift_for_range=Roghnaigh tiomantas. Coinnigh shift + cliceáil chun raon a roghnú +pulls.review_only_possible_for_full_diff=Ní féidir athbhreithniú a dhéanamh ach amháin nuair a bhreathnaítear ar an difríocht iomlán +pulls.filter_changes_by_commit=Scagaigh de réir tiomantas +pulls.nothing_to_compare=Tá na brainsí seo cothrom. Ní gá iarratas tarraingthe a chruthú. +pulls.nothing_to_compare_have_tag=Tá an brainse/clib roghnaithe cothrom. +pulls.nothing_to_compare_and_allow_empty_pr=Tá na brainsí seo cothrom. Beidh an PR seo folamh. +pulls.has_pull_request=`Tá iarratas tarraingthe idir na brainsí seo ann cheana: <a href="%[1]s">%[2]s#%[3]d</a>` +pulls.create=Cruthaigh Iarratas Tarraing +pulls.title_desc=ag iarraidh %[1]d gealltanas a chumasc ó <code>%[2]s</code> go <code id="branch_target">%[3]s</code> +pulls.merged_title_desc=cumasc %[1]d tiomantas ó <code>%[2]s</code> go <code>%[3]s</code> %[4]s +pulls.change_target_branch_at=`athraigh an spriocbhrainse ó <b>%s</b> go <b>%s</b> %s` +pulls.tab_conversation=Comhrá pulls.tab_commits=Tiomáintí +pulls.tab_files=Comhaid Athraithe +pulls.reopen_to_merge=Athoscail an t-iarratas tarraingthe seo le do thoil chun cumasc a dhéanamh. +pulls.cant_reopen_deleted_branch=Ní féidir an t-iarratas tarraingthe seo a athoscailt toisc gur scriosadh an brainse. +pulls.merged=Cumaiscthe +pulls.merged_success=D'éirigh leis an iarratas tarraingthe a chumasc agus a dhúnadh +pulls.closed=Iarratas tarraingthe dúnta +pulls.manually_merged=Cumaisc de láimh +pulls.merged_info_text=Is féidir an brainse %s a scriosadh anois. +pulls.is_closed=Tá an t-iarratas tarraingthe dúnta. +pulls.title_wip_desc=`<a href="#">Tosaigh an teideal le <strong>%s</strong></a> chun an t-iarratas tarraingthe a chosc ó chumasc de thaisme.` +pulls.cannot_merge_work_in_progress=Tá an t-iarratas tarraingthe seo marcáilte mar obair atá ar siúl. +pulls.still_in_progress=Fós ar siúl? +pulls.add_prefix=Cuir réimír <strong>%s</strong> leis +pulls.remove_prefix=Bain an réimír <strong>%s</strong> +pulls.data_broken=Tá an t-iarratas tarraingthe seo briste mar gheall ar fhaisnéis forc a bheith in easnamh. +pulls.files_conflicted=Tá athruithe ag an iarratas tarraingthe seo atá contrártha leis an spriocbhrainse. +pulls.is_checking=Tá seiceáil coinbhleachta cumaisc ar siúl. Bain triail eile as i gceann cúpla nóiméad. +pulls.is_ancestor=Tá an brainse seo san áireamh cheana féin sa spriocbhrainse. Níl aon rud le cumasc. +pulls.is_empty=Tá na hathruithe ar an mbrainse seo ar an spriocbhrainse cheana féin. Is tiomantas folamh é seo. +pulls.required_status_check_failed=Níor éirigh le roinnt seiceálacha riachtanacha. +pulls.required_status_check_missing=Tá roinnt seiceanna riachtanacha ar iarraidh. +pulls.required_status_check_administrator=Mar riarthóir, féadfaidh tú an t-iarratas tarraingthe seo a chumasc fós. +pulls.blocked_by_approvals=Níl go leor ceadaithe ag an iarraidh tarraingthe seo fós. Deonaíodh %d den fhaomhadh %d. +pulls.blocked_by_approvals_whitelisted=Níl go leor ceaduithe riachtanacha ag an iarratas tarraingte seo go fóill. %d de %d faomhadh tugtha ó úsáideoirí nó foirne ar an liosta ceadaithe. +pulls.blocked_by_rejection=Tá athruithe ag athbhreithneoir oifigiúil ag an iarratas tarraingthe seo. +pulls.blocked_by_official_review_requests=Tá iarratais ar athbhreithniú oifigiúil ag an iarratas tarraingte seo. +pulls.blocked_by_outdated_branch=Tá bac ar an iarratas tarraingte seo toisc go bhfuil sé as dáta. +pulls.blocked_by_changed_protected_files_1=Cuirtear bac ar an iarratas tarraingthe seo toisc go n-athraíonn sé comhad cosanta: +pulls.blocked_by_changed_protected_files_n=Tá bac ar an iarratas tarraingthe seo toisc go n-athraíonn sé comhaid chosanta: +pulls.can_auto_merge_desc=Is féidir an t-iarratas tarraingt seo a chumasc go huathoibríoch. +pulls.cannot_auto_merge_desc=Ní féidir an t-iarratas tarraingthe seo a chumasc go huathoibríoch mar gheall ar choinbhleachtaí. +pulls.cannot_auto_merge_helper=Cumaisc de láimh chun na coinbhleachtaí a réiteach. +pulls.num_conflicting_files_1=Comhad contrártha %d +pulls.num_conflicting_files_n=%d comhaid contrártha +pulls.approve_count_1=%d ceadú +pulls.approve_count_n=%d faomhadh +pulls.reject_count_1=%d iarratas athraithe +pulls.reject_count_n=%d iarratas ar athrú +pulls.waiting_count_1=%d athbhreithniú feithimh +pulls.waiting_count_n=%d athbhreithnithe feithimh +pulls.wrong_commit_id=caithfidh comhad id a bheith ina id tiomanta ar an spriocbhrainse +pulls.no_merge_desc=Ní féidir an t-iarratas tarraingthe seo a chumasc toisc go bhfuil gach rogha cumaisc stór díchumasaithe. +pulls.no_merge_helper=Cumasaigh roghanna cumaisc i socruithe an stór nó cumasc an t-iarratas tarraingthe de láimh. +pulls.no_merge_wip=Ní féidir an t-iarratas tarraingthe seo a chumasc toisc go bhfuil sé marcáilte mar obair atá ar siúl é. +pulls.no_merge_not_ready=Níl an t-iarratas tarraingthe seo réidh le cumasc, seiceáil stádas athbhreithnithe agus seiceálacha stádais. +pulls.no_merge_access=Níl tú údaraithe chun an t-iarratas tarraingthe seo a chumasc. +pulls.merge_pull_request=Cruthaigh tiomantas cumaisc +pulls.rebase_merge_pull_request=Athbhunaigh ansin go tapa ar aghaidh +pulls.rebase_merge_commit_pull_request=Rebase ansin cruthaigh tiomantas cumaisc +pulls.squash_merge_pull_request=Cruthaigh tiomantas scuaise +pulls.fast_forward_only_merge_pull_request=Go tapa ar aghaidh amháin +pulls.merge_manually=Cumaisc de láimh +pulls.merge_commit_id=ID an tiomantis cumaisc +pulls.require_signed_wont_sign=Éilíonn an bhrainse tiomáintí shínithe, ach ní shínífear an cumasc seo +pulls.invalid_merge_option=Ní féidir leat an rogha cumaisc seo a úsáid don iarratas tarraingthe seo. +pulls.merge_conflict=Theip ar Cumaisc: Bhí coinbhleacht ann agus é ag cumasc. Leid: Bain triail as straitéis dhifriúil +pulls.merge_conflict_summary=Teachtaireacht Earráide +pulls.rebase_conflict=Theip ar Chumasc: Bhí coinbhleacht ann agus tiomantas á athbhunú: %[1]s. Leid: Bain triail as straitéis eile +pulls.rebase_conflict_summary=Teachtaireacht Earráide +pulls.unrelated_histories=Theip ar Cumaisc: Ní roinneann an ceann cumaisc agus an bonn stair choiteann. Leid: Bain triail as straitéis dhifriúil +pulls.merge_out_of_date=Theip ar Cumaisc: Agus an cumaisc á ghiniúint, nuashonraíodh an bonn. Leid: Bain triail as arís. +pulls.head_out_of_date=Theip ar Cumaisc: Agus an cumaisc á ghiniúint, nuashonraíodh an ceann. Leid: Bain triail as arís. +pulls.has_merged=Theip ar: Cumaisíodh an t-iarratas tarraingthe, ní féidir leat a chumasc arís nó an spriocbhrainse a athrú. +pulls.push_rejected=Theip ar Brúigh: Diúltaíodh don bhrú. Déan athbhreithniú ar na Git Hooks don stór seo. +pulls.push_rejected_summary=Teachtaireacht Diúltaithe Iomlán +pulls.push_rejected_no_message=Theip ar Brúigh: Diúltaíodh don bhrú ach ní raibh aon teachtaireacht iargúlta ann. Déan athbhreithniú ar Git Hooks don stór seo +pulls.open_unmerged_pull_exists=`Ní féidir leat oibríocht athoscailte a dhéanamh toisc go bhfuil iarratas tarraingthe ar feitheamh (#%d) le hairíonna comhionanna. ` +pulls.status_checking=Tá roinnt seiceála ar feitheamh +pulls.status_checks_success=D'éirigh le gach seiceáil +pulls.status_checks_warning=Thuairiscigh roinnt seiceálacha rabhaidh +pulls.status_checks_failure=Theip ar roinnt seiceálacha +pulls.status_checks_error=Thug roinnt seiceálacha earráidí +pulls.status_checks_requested=Riachtanach +pulls.status_checks_details=Sonraí +pulls.status_checks_hide_all=Folaigh gach seiceáil +pulls.status_checks_show_all=Taispeáin gach seiceáil +pulls.update_branch=Nuashonrú brainse trí chumasc +pulls.update_branch_rebase=Nuashonraigh an bhrainse trí athbhunú +pulls.update_branch_success=Bhí nuashonrú brainse rathúil +pulls.update_not_allowed=Ní cheadaítear duit brainse a nuashonrú +pulls.outdated_with_base_branch=Tá an brainse seo as dáta leis an mbunbhrainse +pulls.close=Dún Iarratas Tarraing +pulls.closed_at=`dhún an t-iarratas tarraingthe seo <a id="%[1]s" href="#%[1]s">%[2]s</a>` +pulls.reopened_at=`athoscail an t-iarratas tarraingthe seo <a id="%[1]s" href="#%[1]s">%[2]s</a>` +pulls.cmd_instruction_hint=`Féach ar <a class="show-instruction">treoracha na líne ordaithe</a>.` +pulls.cmd_instruction_checkout_title=Seiceáil +pulls.cmd_instruction_checkout_desc=Ó stór tionscadail, seiceáil brainse nua agus déan tástáil ar na hathruithe. +pulls.cmd_instruction_merge_title=Cumaisc +pulls.cmd_instruction_merge_desc=Cumaisc na hathruithe agus nuashonrú ar Gitea. +pulls.cmd_instruction_merge_warning=Rabhadh: Ní féidir leis an oibríocht seo an t-iarratas tarraingthe a chumasc toisc nach raibh "cumasc láimhe uathoibríoch" cumasaithe +pulls.clear_merge_message=Glan an teachtaireacht chumaisc +pulls.clear_merge_message_hint=Má imrítear an teachtaireacht chumaisc ní bhainfear ach ábhar na teachtaireachta tiomanta agus coimeádfar leantóirí git ginte ar nós "Co-Authored-By …". +pulls.auto_merge_button_when_succeed=(Nuair a éiríonn le seiceálacha) +pulls.auto_merge_when_succeed=Cumaisc uathoibríoch nuair a éiríonn +pulls.auto_merge_newly_scheduled=Bhí an t-iarratas tarraingt sceidealta le cumasc nuair a éiríonn le gach seiceáil. +pulls.auto_merge_has_pending_schedule=Bhí an t-iarratas tarraingthe seo sceidealaithe ag %[1]s chun cumasc uathoibríoch a dhéanamh nuair a éiríonn le gach seiceáil %[2]s. +pulls.auto_merge_cancel_schedule=Cealaigh cumasc uathoibríoch +pulls.auto_merge_not_scheduled=Níl an t-iarratas tarraingthe seo sceidealaithe le cumasc go huathoibríoch. +pulls.auto_merge_canceled_schedule=Cealaíodh an cumaisc uathoibríoch don iarratas tarraingthe seo. +pulls.auto_merge_newly_scheduled_comment=`sceidealta an t-iarratas tarraingthe seo le cumasc uathoibrithe nuair a éiríonn le gach seiceáil %[1]s` +pulls.auto_merge_canceled_schedule_comment=`curtha ar ceal uathchumasc leis an iarratas tarraingthe seo nuair a éiríonn le gach seiceáil %[1]s` +pulls.delete.title=Scrios an t-iarratas tarraingthe seo? +pulls.delete.text=An bhfuil tú cinnte gur mhaith leat an t-iarratas tarraingthe seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann) +pulls.recently_pushed_new_branches=Bhrúigh tú ar bhrainse <strong>%[1]s</strong> %[2]s +pull.deleted_branch=(scriosta): %s +pull.agit_documentation=Déan athbhreithniú ar dhoiciméid faoi AGit +comments.edit.already_changed=Ní féidir athruithe a shábháil ar an trácht. Dealraíonn sé gur athraigh úsáideoir eile an t-ábhar cheana féin. Athnuachan an leathanach agus déan iarracht eagarthóireacht arís chun a gcuid athruithe a sheachaint +milestones.new=Cloch Mhíle Nua +milestones.closed=Dúnta %s +milestones.update_ago=Nuashonraithe %s +milestones.no_due_date=Gan dáta dlite milestones.open=Oscailte milestones.close=Dún +milestones.new_subheader=Is féidir le clocha míle cabhrú leat ceisteanna a eagrú agus a ndul chun cinn a rianú. +milestones.completeness=<strong>%d%%</strong> Críochnaithe +milestones.create=Cruthaigh Cloch Mhíle +milestones.title=Teideal +milestones.desc=Cur síos +milestones.due_date=Dáta dlite (roghnach) +milestones.clear=Glan +milestones.invalid_due_date_format=Caithfidh formáid dáta dlite a bheith 'bbbb-mm-ll'. +milestones.create_success=Cruthaíodh an chloch mhíle "%s". +milestones.edit=Cuir Cloch Mhíle in eagar +milestones.edit_subheader=Eagraíonn Garspriocanna saincheisteanna agus rianaítear dul chun cinn. +milestones.cancel=Cealaigh +milestones.modify=Nuashonraigh Cloch Mhíle +milestones.edit_success=Nuashonraíodh cloch mhíle "%s". +milestones.deletion=Scrios Cloch Mhíle +milestones.deletion_desc=Cuireann scriosadh cloch mhíle é as gach saincheist ghaolmhar. Lean ar aghaidh? +milestones.deletion_success=Tá an chloch mhíle scriosta. +milestones.filter_sort.name=Ainm +milestones.filter_sort.earliest_due_data=An dáta dlite is luaithe +milestones.filter_sort.latest_due_date=An dáta dlite is déanaí +milestones.filter_sort.least_complete=Is lú críochnaithe +milestones.filter_sort.most_complete=Is mó críochnaithe +milestones.filter_sort.most_issues=Saincheisteanna is mó +milestones.filter_sort.least_issues=Saincheisteanna is lú +signing.will_sign=Síneofar an gealltanas seo le heochair "%s". +signing.wont_sign.error=Bhí earráid ann agus tú ag seiceáil an féidir an tiomantas a shíniú. +signing.wont_sign.nokey=Níl aon eochair ar fáil chun an tiomantas seo a shíniú. +signing.wont_sign.never=Ní shínítear tiomáintí riamh. +signing.wont_sign.always=Sínítear tiomáintí i gcónaí. +signing.wont_sign.pubkey=Ní shíníofar an tiomantas toisc nach bhfuil eochair phoiblí agat a bhaineann le do chuntas. +signing.wont_sign.twofa=Caithfidh fíordheimhniú dhá-fhachtóir a bheith agat chun tiomáintí a shíniú. +signing.wont_sign.parentsigned=Ní shíníofar an tiomantas toisc nach bhfuil an tiomantas tuismitheora sínithe. +signing.wont_sign.basesigned=Ní shínífear an cumasc toisc nach bhfuil an tiomantas bunaithe sínithe. +signing.wont_sign.headsigned=Ní shínífear an cumasc toisc nach bhfuil an tiomantas ceann sínithe. +signing.wont_sign.commitssigned=Ní shínífear an cumasc toisc nach bhfuil na tiomáintí gaolmhara go léir sínithe. +signing.wont_sign.approved=Ní shíníofar an cumaisc toisc nach bhfuil an PR ceadaithe. +signing.wont_sign.not_signed_in=Níl tú sínithe isteach. +ext_wiki=Rochtain ar Vicí Seachtrach +ext_wiki.desc=Nasc le vicí seachtrach. -wiki=Wiki +wiki=Vicí +wiki.welcome=Fáilte go dtí an Vicí. +wiki.welcome_desc=Ligeann an vicí duit cáipéisíocht a scríobh agus a roinnt le comhoibrithe. +wiki.desc=Scríobh agus roinn cáipéisíocht le comhoibrithe. +wiki.create_first_page=Cruthaigh an Chéad Leathanach wiki.page=Leathanach +wiki.filter_page=Leathanach scagaire wiki.new_page=Leathanach +wiki.page_title=Teideal an leathanaigh +wiki.page_content=Ábhar an leathanaigh +wiki.default_commit_message=Scríobh nóta faoin nuashonrú leathanaigh seo (roghnach). +wiki.save_page=Sábháil Leathanach +wiki.last_commit_info=Cuireadh %s an leathanach seo in eagar %s +wiki.edit_page_button=Cuir in eagar +wiki.new_page_button=Leathanach Nua +wiki.file_revision=Athbhreithniú Leathanach +wiki.wiki_page_revisions=Athbhreithnithe Leathanach Vicí +wiki.back_to_wiki=Ar ais go leathanach vicí +wiki.delete_page_button=Scrios Leathanach +wiki.delete_page_notice_1=Ní féidir leathanach vicí "%s" a scriosadh. Lean ort? +wiki.page_already_exists=Tá leathanach vicí leis an ainm céanna ann cheana féin. +wiki.reserved_page=Tá an t-ainm leathanaigh vicí "%s" in áirithe. +wiki.pages=Leathanaigh +wiki.last_updated=Nuashonraithe deireanach %s +wiki.page_name_desc=Cuir isteach ainm don leathanach Vicí seo. Is iad roinnt ainmneacha speisialta: 'Baile', '_Sidebar' agus '_Footer'. +wiki.original_git_entry_tooltip=Féach ar an gcomhad bunaidh Git in ionad nasc cairdiúil a úsáid. +activity=Gníomhaíocht +activity.navbar.pulse=Cuisle +activity.navbar.code_frequency=Minicíocht Cód +activity.navbar.contributors=Rannpháirtithe +activity.navbar.recent_commits=Tiomáintí le déanaí +activity.period.filter_label=Tréimhse: +activity.period.daily=1 lá +activity.period.halfweekly=3 lá +activity.period.weekly=1 seachtain +activity.period.monthly=1 mhí +activity.period.quarterly=3 mhí +activity.period.semiyearly=6 mhí +activity.period.yearly=1 bhliain +activity.overview=Forbhreathnú +activity.active_prs_count_1=<strong>%d</strong> Iarratas Tarraingthe Gníomhach +activity.active_prs_count_n=<strong>%d</strong> Iarratais Tharraing Ghníomhach +activity.merged_prs_count_1=Iarratas Tarraing Cumaisc +activity.merged_prs_count_n=Iarratais Tharraing Chomhcheangail +activity.opened_prs_count_1=Iarratas Tarraing Beartaithe +activity.opened_prs_count_n=Iarratais Tarraing Beartaithe +activity.title.user_1=%d úsáideoir +activity.title.user_n=%d úsáideoirí +activity.title.prs_1=Iarratas tarraing %d +activity.title.prs_n=%d Iarratais Tarraing +activity.title.prs_merged_by=%s a chumasc ag %s +activity.title.prs_opened_by=%s arna mholadh ag %s +activity.merged_prs_label=Cumaiscthe +activity.opened_prs_label=Molta +activity.active_issues_count_1=<strong>%d</strong> Eagrán Gníomhach +activity.active_issues_count_n=<strong>%d</strong> Ceisteanna Gníomhacha +activity.closed_issues_count_1=Saincheist Dúnta +activity.closed_issues_count_n=Saincheisteanna Dúnta +activity.title.issues_1=Saincheist %d +activity.title.issues_n=Saincheisteanna %d +activity.title.issues_closed_from=%s dúnta ó %s +activity.title.issues_created_by=%s cruthaithe ag %s activity.closed_issue_label=Dúnta activity.new_issues_count_1=Eagrán Nua +activity.new_issues_count_n=Saincheisteanna Nua +activity.new_issue_label=Osclaíodh +activity.title.unresolved_conv_1=%d Comhrá Neamhréitithe +activity.title.unresolved_conv_n=%d Comhráite Neamhréitithe +activity.unresolved_conv_desc=Níor réitíodh na saincheisteanna agus na hiarratais tarraingthe seo le déanaí fós. activity.unresolved_conv_label=Oscailte +activity.title.releases_1=Scaoileadh %d +activity.title.releases_n=Eisiúintí %d +activity.title.releases_published_by=%s foilsithe ag %s +activity.published_release_label=Foilsithe +activity.no_git_activity=Níor rinneadh aon ghníomhaíocht tiomanta sa tréimhse seo. +activity.git_stats_exclude_merges=Gan cumaisc a áireamh, +activity.git_stats_author_1=%d údar +activity.git_stats_author_n=%d údair +activity.git_stats_pushed_1=tá sé brúite +activity.git_stats_pushed_n=tá brú orthu +activity.git_stats_commit_1=%d tiomantas +activity.git_stats_commit_n=%d tiomáintí +activity.git_stats_push_to_branch=chuig %s agus +activity.git_stats_push_to_all_branches=chuig gach brainse. +activity.git_stats_on_default_branch=Ar %s, +activity.git_stats_file_1=%d comhad +activity.git_stats_file_n=%d comhaid +activity.git_stats_files_changed_1=tá athrú tagtha +activity.git_stats_files_changed_n=tá athraithe +activity.git_stats_additions=agus tá ann +activity.git_stats_addition_1=%d breisiú +activity.git_stats_addition_n=%d breiseanna +activity.git_stats_and_deletions=agus +activity.git_stats_deletion_1=%d scriosadh +activity.git_stats_deletion_n=%d scriosta +contributors.contribution_type.filter_label=Cineál ranníocaíochta: contributors.contribution_type.commits=Tiomáintí +contributors.contribution_type.additions=Breiseanna +contributors.contribution_type.deletions=Scriosadh +settings=Socruithe +settings.desc=Is é socruithe an áit ar féidir leat na socruithe don stóras a bhainistiú +settings.options=Stóras +settings.collaboration=Comhoibritheoirí +settings.collaboration.admin=Riarthóir +settings.collaboration.write=Scríobh +settings.collaboration.read=Léigh +settings.collaboration.owner=Úinéir +settings.collaboration.undefined=Neamhshainithe +settings.hooks=Gníomhartha Gréasáin settings.githooks=Crúcanna Git +settings.basic_settings=Socruithe Bunúsacha +settings.mirror_settings=Socruithe Scáthán +settings.mirror_settings.docs=Cuir do stóras ar bun chun tiomáintí, clibeanna agus brainsí a shioncronú go huathoibríoch le stóras eile. +settings.mirror_settings.docs.disabled_pull_mirror.instructions=Socraigh do thionscadal chun tiomáintí, clibeanna agus brainsí a bhrú go huathoibríoch chuig stóras eile. Tá scátháin tarraingthe díchumasaithe ag riarthóir do shuíomh. +settings.mirror_settings.docs.disabled_push_mirror.instructions=Socraigh do thionscadal chun tiomáintí, clibeanna agus brainsí a tharraingt go huathoibríoch ó stóras eile. +settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Faoi láthair, ní féidir é seo a dhéanamh ach sa roghchlár "Imirce Nua". Le haghaidh tuilleadh eolais, téigh i gcomhairle le do thoil: +settings.mirror_settings.docs.disabled_push_mirror.info=Chuir riarthóir do shuíomh faoi dhíchumasú scátháin bhrú. +settings.mirror_settings.docs.no_new_mirrors=Tá do stóras ag teacht le hathruithe chuig nó ó stóras eile. Cuimhnigh le do thoil nach féidir leat scátháin nua a chruthú faoi láthair. +settings.mirror_settings.docs.can_still_use=Cé nach féidir leat scátháin atá ann cheana a mhodhnú nó cinn nua a chruthú, féadfaidh tú do scáthán atá ann cheana a úsáid fós. +settings.mirror_settings.docs.pull_mirror_instructions=Chun scáthán tarraingthe a shocrú, téigh i gcomhairle le do thoil: +settings.mirror_settings.docs.more_information_if_disabled=Is féidir leat tuilleadh eolais a fháil faoi scátháin bhrú agus tarraingthe anseo: +settings.mirror_settings.docs.doc_link_title=Conas is féidir liom na stórtha a scáthánú? +settings.mirror_settings.docs.doc_link_pull_section=an chuid "Ag tarraingt ó stóras" den doiciméadú. +settings.mirror_settings.docs.pulling_remote_title=Ag tarraingt ó stóras cianda +settings.mirror_settings.mirrored_repository=Stóras scátháin +settings.mirror_settings.pushed_repository=Stóras brúite +settings.mirror_settings.direction=Treo +settings.mirror_settings.direction.pull=Tarraingt +settings.mirror_settings.direction.push=Brúigh +settings.mirror_settings.last_update=Nuashonrú deireanach +settings.mirror_settings.push_mirror.none=Níl aon scátháin bhrú cumraithe +settings.mirror_settings.push_mirror.remote_url=URL Stóras Cianda Git +settings.mirror_settings.push_mirror.add=Cuir Scáthán Brúigh leis +settings.mirror_settings.push_mirror.edit_sync_time=Eagar eatramh sioncronaithe scátháin +settings.sync_mirror=Sioncronaigh Anois +settings.pull_mirror_sync_in_progress=Athruithe a tharraingt ón iargúlta %s i láthair na huaire. +settings.push_mirror_sync_in_progress=Athruithe a bhrú ar an iargúlta %s i láthair na huaire. +settings.site=Láithreán Gréasáin +settings.update_settings=Nuashonrú Socruithe +settings.update_mirror_settings=Nuashonraigh Socruithe Scátháin +settings.branches.switch_default_branch=Athraigh Brainse Réamhshocraithe +settings.branches.update_default_branch=An Brainse Réamhshocraithe a nuashonrú +settings.branches.add_new_rule=Cuir Riail Nua leis +settings.advanced_settings=Ardsocruithe settings.projects_mode_all=Gach tionscadal settings.trust_model.collaborator=Comhoibritheoir settings.trust_model.collaborator.long=Comhoibritheoir: Sínithe muinín ag comhoibrithe settings.trust_model.collaboratorcommitter.long=Comhoibritheo+Coiteoir: Sínithe iontaobhais ag comhoibritheoirí a mheaitseann leis an gealltóir +settings.webhook.body=Comhlacht settings.slack_color=Dath settings.event_fork=Forc settings.event_wiki=Wiki settings.event_release=Scaoileadh +settings.event_push=Brúigh +diff.review.comment=Trácht branch.branch_already_exists=Tá brainse "%s" ann cheana féin sa stóras seo. diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 28605d48cf68..69f0e6eab77b 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -397,7 +397,6 @@ account_link=Kapcsolt fiókok organization=Szervezetek public_profile=Nyilvános profil -password_username_disabled=A nem helyi felhasználóknak nem engedélyezett, hogy megváltoztassák a felhasználói nevüket. Kérjük lépjen kapcsolatba a helyi rendszergazdájával további információkért. full_name=Teljes név website=Webhely location=Hely diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 94c60979a8df..d1b4166e1628 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -317,7 +317,6 @@ account_link=Akun Tertaut organization=Organisasi public_profile=Profil Publik -password_username_disabled=Pengguna non-lokal tidak diizinkan untuk mengubah nama pengguna mereka. Silakan hubungi administrator sistem anda untuk lebih lanjut. full_name=Nama Lengkap website=Situs Web location=Lokasi diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 656b5e1c5650..0bd4ba68946d 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -428,7 +428,6 @@ account_link=Tengdir Reikningar organization=Stofnanir public_profile=Opinber Notandasíða -password_username_disabled=Notendum utan staðarins er ekki heimilt að breyta notendanafni sínu. Vinsamlegast hafðu samband við síðustjórann þinn til að fá frekari upplýsingar. full_name=Fullt Nafn website=Vefsíða location=Staðsetning diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 3ddd2bbddfef..d82215622fdb 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -516,7 +516,6 @@ account_link=Account collegati organization=Organizzazioni public_profile=Profilo pubblico -password_username_disabled=Gli utenti non locali non hanno il permesso di cambiare il proprio nome utente. per maggiori dettagli si prega di contattare l'amministratore del sito. full_name=Nome Completo website=Sito web location=Posizione diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 2e861df207ba..973c1b3761c3 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -705,7 +705,6 @@ public_profile=公開プロフィール biography_placeholder=自己紹介してください!(Markdownを使うことができます) location_placeholder=おおよその場所を他の人と共有 profile_desc=あなたのプロフィールが他のユーザーにどのように表示されるかを制御します。あなたのプライマリメールアドレスは、通知、パスワードの回復、WebベースのGit操作に使用されます。 -password_username_disabled=非ローカルユーザーのユーザー名は変更できません。詳細はサイト管理者にお問い合わせください。 full_name=フルネーム website=Webサイト location=場所 @@ -1040,6 +1039,7 @@ issue_labels_helper=イシューのラベルセットを選択 license=ライセンス license_helper=ライセンス ファイルを選択してください。 license_helper_desc=ライセンスにより、他人があなたのコードに対して何ができて何ができないのかを規定します。 どれがプロジェクトにふさわしいか迷っていますか? <a target="_blank" rel="noopener noreferrer" href="%s">ライセンス選択サイト</a> も確認してみてください。 +multiple_licenses=複数のライセンス object_format=オブジェクトのフォーマット object_format_helper=リポジトリのオブジェクトフォーマット。後で変更することはできません。SHA1 は最も互換性があります。 readme=README @@ -2941,6 +2941,7 @@ dashboard.start_schedule_tasks=Actionsスケジュールタスクを開始 dashboard.sync_branch.started=ブランチの同期を開始しました dashboard.sync_tag.started=タグの同期を開始しました dashboard.rebuild_issue_indexer=イシューインデクサーの再構築 +dashboard.sync_repo_licenses=リポジトリライセンスの同期 users.user_manage_panel=ユーザーアカウント管理 users.new_account=ユーザーアカウントを作成 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index dc122ec4c352..91c5b24ab560 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -375,7 +375,6 @@ account_link=연결된 계정 organization=조직 public_profile=공개 프로필 -password_username_disabled=로컬 사용자가 아닌 경우 사용자 이름 변경을 할 수 없습니다. 자세한 내용은 관리자에게 문의해주세요. full_name=성명 website=웹 사이트 location=위치 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index d8ab21e1703a..ee6f3911b0d9 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -623,7 +623,6 @@ public_profile=Publiskais profils biography_placeholder=Pastāsti mums mazliet par sevi! (Var izmantot Markdown) location_placeholder=Kopīgot savu aptuveno atrašanās vietu ar citiem profile_desc=Norādīt, kā profils tiek attēlots citiem lietotājiem. Primārā e-pasta adrese tiks izmantota paziņojumiem, paroles atjaunošanai un Git tīmekļa darbībām. -password_username_disabled=Ne-lokāliem lietotājiem nav atļauts mainīt savu lietotāja vārdu. Sazinieties ar sistēmas administratoru, lai uzzinātu sīkāk. full_name=Pilns vārds website=Mājas lapa location=Atrašanās vieta diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 5ed3417b4a7c..1540cf095b07 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -515,7 +515,6 @@ account_link=Gekoppelde Accounts organization=Organisaties public_profile=Openbaar profiel -password_username_disabled=Niet-lokale gebruikers kunnen hun gebruikersnaam niet veranderen. Neem contact op met de sitebeheerder voor meer details. full_name=Volledige naam website=Website location=Locatie diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 911a4b9e0ef5..22bdf1bbacb0 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -500,7 +500,6 @@ account_link=Powiązane Konta organization=Organizacje public_profile=Profil publiczny -password_username_disabled=Użytkownicy nielokalni nie mogą zmieniać swoich nazw. Aby uzyskać więcej informacji, skontaktuj się z administratorem strony. full_name=Imię i nazwisko website=Strona location=Lokalizacja diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 6f4f710d7f05..bb185d3de6dd 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -622,7 +622,6 @@ public_profile=Perfil público biography_placeholder=Conte-nos um pouco sobre você! (Você pode usar Markdown) location_placeholder=Compartilhe sua localização aproximada com outras pessoas profile_desc=Controle como o seu perfil é exibido para outros usuários. Seu endereço de e-mail principal será usado para notificações, recuperação de senha e operações do Git baseadas na Web. -password_username_disabled=Usuários não-locais não podem alterar seus nomes de usuário. Por favor contate o administrador do site para mais informações. full_name=Nome completo website=Site location=Localização diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index cb521fe58c99..6f788737ff5b 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -705,7 +705,6 @@ public_profile=Perfil público biography_placeholder=Conte-nos um pouco sobre si! (Pode usar Markdown) location_placeholder=Partilhe a sua localização aproximada com outros profile_desc=Controle como o seu perfil é apresentado aos outros utilizadores. O seu endereço de email principal será usado para notificações, recuperação de senha e operações Git baseadas na web. -password_username_disabled=Utilizadores não-locais não podem mudar os seus nomes de utilizador. Entre em contacto com o administrador do sítio saber para mais detalhes. full_name=Nome completo website=Sítio web location=Localização diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 03c6e2d073f9..7fd73f9d830c 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -618,7 +618,6 @@ public_profile=Открытый профиль biography_placeholder=Расскажите немного о себе! (Можно использовать Markdown) location_placeholder=Поделитесь своим приблизительным местоположением с другими profile_desc=Контролируйте, как ваш профиль будет отображаться другим пользователям. Ваш основной адрес электронной почты будет использоваться для уведомлений, восстановления пароля и веб-операций Git. -password_username_disabled=Нелокальным пользователям запрещено изменение их имени пользователя. Для получения более подробной информации обратитесь к администратору сайта. full_name=Имя и фамилия website=Веб-сайт location=Местоположение diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index c9521d80f886..4d64c46e6ee2 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -480,7 +480,6 @@ account_link=සම්බන්ධිත ගිණුම් organization=සංවිධාන public_profile=ප්රසිද්ධ පැතිකඩ -password_username_disabled=දේශීය නොවන පරිශීලකයින්ට ඔවුන්ගේ පරිශීලක නාමය වෙනස් කිරීමට අවසර නැත. වැඩි විස්තර සඳහා කරුණාකර ඔබේ වෙබ් අඩවිය පරිපාලක අමතන්න. full_name=සම්පූර්ණ නම website=වියමන අඩවිය location=ස්ථානය diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 484fa320fa9f..a964b526afa1 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -583,7 +583,6 @@ account_link=Prepojené účty organization=Organizácie public_profile=Verejný profil -password_username_disabled=Externí používatelia nemôžu meniť svoje používateľské meno. Kontaktujte, prosím, svojho administrátora kvôli detailom. full_name=Celé meno website=Webová stránka location=Miesto diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 459b7045067b..2993828c22fd 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -418,7 +418,6 @@ account_link=Länkade Konton organization=Organisationer public_profile=Offentlig profil -password_username_disabled=Externa användare kan inte ändra sitt användarnamn. Kontakta din webbadministratör för mera information. full_name=Fullständigt namn website=Webbplats location=Plats diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 7ef6c1d35e15..3c719f84bafc 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -694,7 +694,6 @@ public_profile=Herkese Açık Profil biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz) location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır. -password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz. full_name=Ad Soyad website=Web Sitesi location=Konum diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 129ab1b7f5c2..d94d11207b48 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -494,7 +494,6 @@ account_link=Прив'язані облікові записи organization=Організації public_profile=Загальнодоступний профіль -password_username_disabled=Нелокальним користувачам заборонено змінювати ім'я користувача. Щоб отримати докладнішу інформацію, зв'яжіться з адміністратором сайту. full_name=Повне ім'я website=Веб-сайт location=Місцезнаходження diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 2827a8cd35b9..3be7b044e903 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -686,7 +686,6 @@ public_profile=公开信息 biography_placeholder=告诉我们一点您自己! (您可以使用Markdown) location_placeholder=与他人分享你的大概位置 profile_desc=控制您的个人资料对其他用户的显示方式。您的主要电子邮件地址将用于通知、密码恢复和基于网页界面的 Git 操作 -password_username_disabled=不允许非本地用户更改他们的用户名。更多详情请联系您的系统管理员。 full_name=自定义名称 website=个人网站 location=所在地区 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 9406419e6bb1..d755f64dcd5f 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -583,7 +583,6 @@ account_link=已連結帳號 organization=組織 public_profile=公開的個人資料 -password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。 full_name=全名 website=個人網站 location=所在地區 From 6551847aa8a7b2d363e9fbfe7509b679be5c0a71 Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Mon, 7 Oct 2024 00:32:39 +0000 Subject: [PATCH 14/17] [skip ci] Updated licenses and gitignores --- options/gitignore/KiCad | 2 + options/license/Sendmail-Open-Source-1.1 | 75 ++++++++++++++++++++++++ options/license/harbour-exception | 23 ++++++++ 3 files changed, 100 insertions(+) create mode 100644 options/license/Sendmail-Open-Source-1.1 create mode 100644 options/license/harbour-exception diff --git a/options/gitignore/KiCad b/options/gitignore/KiCad index a63bc0e7f7e0..59fde34c0e55 100644 --- a/options/gitignore/KiCad +++ b/options/gitignore/KiCad @@ -16,6 +16,8 @@ _autosave-* *-save.pro *-save.kicad_pcb fp-info-cache +~*.lck +\#auto_saved_files# # Netlist files (exported from Eeschema) *.net diff --git a/options/license/Sendmail-Open-Source-1.1 b/options/license/Sendmail-Open-Source-1.1 new file mode 100644 index 000000000000..054f719ee594 --- /dev/null +++ b/options/license/Sendmail-Open-Source-1.1 @@ -0,0 +1,75 @@ +SENDMAIL OPEN SOURCE LICENSE + +The following license terms and conditions apply to this open source +software ("Software"), unless a different license is obtained directly +from Sendmail, Inc. ("Sendmail") located at 6475 Christie Ave, Suite 350, +Emeryville, CA 94608, USA. + +Use, modification and redistribution (including distribution of any +modified or derived work) of the Software in source and binary forms is +permitted only if each of the following conditions of 1-6 are met: + +1. Redistributions of the Software qualify as "freeware" or "open + source software" under one of the following terms: + + (a) Redistributions are made at no charge beyond the reasonable + cost of materials and delivery; or + + (b) Redistributions are accompanied by a copy of the modified + Source Code (on an acceptable machine-readable medium) or by an + irrevocable offer to provide a copy of the modified Source Code + (on an acceptable machine-readable medium) for up to three years + at the cost of materials and delivery. Such redistributions must + allow further use, modification, and redistribution of the Source + Code under substantially the same terms as this license. For + the purposes of redistribution "Source Code" means the complete + human-readable, compilable, linkable, and operational source + code of the redistributed module(s) including all modifications. + +2. Redistributions of the Software Source Code must retain the + copyright notices as they appear in each Source Code file, these + license terms and conditions, and the disclaimer/limitation of + liability set forth in paragraph 6 below. Redistributions of the + Software Source Code must also comply with the copyright notices + and/or license terms and conditions imposed by contributors on + embedded code. The contributors' license terms and conditions + and/or copyright notices are contained in the Source Code + distribution. + +3. Redistributions of the Software in binary form must reproduce the + Copyright Notice described below, these license terms and conditions, + and the disclaimer/limitation of liability set forth in paragraph + 6 below, in the documentation and/or other materials provided with + the binary distribution. For the purposes of binary distribution, + "Copyright Notice" refers to the following language: "Copyright (c) + 1998-2009 Sendmail, Inc. All rights reserved." + +4. Neither the name, trademark or logo of Sendmail, Inc. (including + without limitation its subsidiaries or affiliates) or its contributors + may be used to endorse or promote products, or software or services + derived from this Software without specific prior written permission. + The name "sendmail" is a registered trademark and service mark of + Sendmail, Inc. + +5. We reserve the right to cancel this license if you do not comply with + the terms. This license is governed by California law and both of us + agree that for any dispute arising out of or relating to this Software, + that jurisdiction and venue is proper in San Francisco or Alameda + counties. These license terms and conditions reflect the complete + agreement for the license of the Software (which means this supercedes + prior or contemporaneous agreements or representations). If any term + or condition under this license is found to be invalid, the remaining + terms and conditions still apply. + +6. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY + SENDMAIL AND ITS CONTRIBUTORS "AS IS" WITHOUT WARRANTY OF ANY KIND + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT AND FITNESS FOR A + PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. IN NO EVENT SHALL SENDMAIL + OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + WITHOUT LIMITATION NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/options/license/harbour-exception b/options/license/harbour-exception new file mode 100644 index 000000000000..25d75e9fc766 --- /dev/null +++ b/options/license/harbour-exception @@ -0,0 +1,23 @@ +As a special exception, the Harbour Project gives permission for +additional uses of the text contained in its release of Harbour. + +The exception is that, if you link the Harbour libraries with other +files to produce an executable, this does not by itself cause the +resulting executable to be covered by the GNU General Public License. +Your use of that executable is in no way restricted on account of +linking the Harbour library code into it. + +This exception does not however invalidate any other reasons why +the executable file might be covered by the GNU General Public License. + +This exception applies only to the code released by the Harbour +Project under the name Harbour. If you copy code from other +Harbour Project or Free Software Foundation releases into a copy of +Harbour, as the General Public License permits, the exception does +not apply to the code that you add in this way. To avoid misleading +anyone as to the status of such modified files, you must delete +this exception notice from them. + +If you write modifications of your own for Harbour, it is your choice +whether to permit this exception to apply to your modifications. +If you do not wish that, delete this exception notice. From fa35ace9fb0383cee78e1717bcb6eab1224fb4f1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Mon, 7 Oct 2024 11:50:38 +0800 Subject: [PATCH 15/17] Fix bug when there are multiple triggers with workflow dispatch (#32200) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8dd694792528..d85553ac9f26 100644 --- a/go.mod +++ b/go.mod @@ -331,7 +331,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.259.1 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.3 replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0 diff --git a/go.sum b/go.sum index aa592053b53e..bb185e20c16d 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw= -gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8= +gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4= +gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40= gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= From bdd655f2bde5facada4394f36fe54e364787de7a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 7 Oct 2024 23:21:07 +0200 Subject: [PATCH 16/17] Allow filtering PRs by poster in the ListPullRequests API (#32209) as title --- *Sponsored by Kithara Software GmbH* --- models/issues/pull_list.go | 5 ++++ routers/api/v1/repo/pull.go | 42 ++++++++++++++++++++++++++-------- templates/swagger/v1_json.tmpl | 25 +++++++++++++++----- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index f80a2284f096..9155ea083468 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -26,6 +26,7 @@ type PullRequestsOptions struct { SortType string Labels []int64 MilestoneID int64 + PosterID int64 } func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { @@ -46,6 +47,10 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR sess.And("issue.milestone_id=?", opts.MilestoneID) } + if opts.PosterID > 0 { + sess.And("issue.poster_id=?", opts.PosterID) + } + return sess } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 4e3de77032fb..34ebcb42d5ae 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -52,56 +52,79 @@ func ListPullRequests(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: owner of the repo + // description: Owner of the repo // type: string // required: true // - name: repo // in: path - // description: name of the repo + // description: Name of the repo // type: string // required: true // - name: state // in: query - // description: "State of pull request: open or closed (optional)" + // description: State of pull request // type: string - // enum: [closed, open, all] + // enum: [open, closed, all] + // default: open // - name: sort // in: query - // description: "Type of sort" + // description: Type of sort // type: string // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority] // - name: milestone // in: query - // description: "ID of the milestone" + // description: ID of the milestone // type: integer // format: int64 // - name: labels // in: query - // description: "Label IDs" + // description: Label IDs // type: array // collectionFormat: multi // items: // type: integer // format: int64 + // - name: poster + // in: query + // description: Filter by pull request author + // type: string // - name: page // in: query - // description: page number of results to return (1-based) + // description: Page number of results to return (1-based) // type: integer + // minimum: 1 + // default: 1 // - name: limit // in: query - // description: page size of results + // description: Page size of results // type: integer + // minimum: 0 // responses: // "200": // "$ref": "#/responses/PullRequestList" // "404": // "$ref": "#/responses/notFound" + // "500": + // "$ref": "#/responses/error" labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels")) if err != nil { ctx.Error(http.StatusInternalServerError, "PullRequests", err) return } + var posterID int64 + if posterStr := ctx.FormString("poster"); posterStr != "" { + poster, err := user_model.GetUserByName(ctx, posterStr) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Error(http.StatusBadRequest, "Poster not found", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + } + return + } + posterID = poster.ID + } listOptions := utils.GetListOptions(ctx) prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{ ListOptions: listOptions, @@ -109,6 +132,7 @@ func ListPullRequests(ctx *context.APIContext) { SortType: ctx.FormTrim("sort"), Labels: labelIDs, MilestoneID: ctx.FormInt64("milestone"), + PosterID: posterID, }) if err != nil { ctx.Error(http.StatusInternalServerError, "PullRequests", err) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index bac918ac3899..2cbd8782d841 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -11209,26 +11209,27 @@ "parameters": [ { "type": "string", - "description": "owner of the repo", + "description": "Owner of the repo", "name": "owner", "in": "path", "required": true }, { "type": "string", - "description": "name of the repo", + "description": "Name of the repo", "name": "repo", "in": "path", "required": true }, { "enum": [ - "closed", "open", + "closed", "all" ], "type": "string", - "description": "State of pull request: open or closed (optional)", + "default": "open", + "description": "State of pull request", "name": "state", "in": "query" }, @@ -11265,14 +11266,23 @@ "in": "query" }, { + "type": "string", + "description": "Filter by pull request author", + "name": "poster", + "in": "query" + }, + { + "minimum": 1, "type": "integer", - "description": "page number of results to return (1-based)", + "default": 1, + "description": "Page number of results to return (1-based)", "name": "page", "in": "query" }, { + "minimum": 0, "type": "integer", - "description": "page size of results", + "description": "Page size of results", "name": "limit", "in": "query" } @@ -11283,6 +11293,9 @@ }, "404": { "$ref": "#/responses/notFound" + }, + "500": { + "$ref": "#/responses/error" } } }, From d3ada91ea41b2f2eb58d637ab4c0a2dde07f20ce Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Tue, 8 Oct 2024 00:30:42 +0000 Subject: [PATCH 17/17] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.ini | 804 +++++++++++++++++++++++++++++++- options/locale/locale_pt-PT.ini | 4 + 2 files changed, 790 insertions(+), 18 deletions(-) diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 23e0839530e0..82209b1b1180 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -580,6 +580,8 @@ lang_select_error=Roghnaigh teanga ón liosta. username_been_taken=Tá an t-ainm úsáideora tógtha cheana féin. username_change_not_local_user=Ní cheadaítear d'úsáideoirí neamháitiúla a n-ainm úsáideora a athrú. +change_username_disabled=Tá athrú an ainm úsáideora díchumasaithe. +change_full_name_disabled=Tá athrú an ainm iomlán díchumasaithe. username_has_not_been_changed=Níor athraíodh ainm úsáideora repo_name_been_taken=Úsáidtear ainm an stór cheana féin. repository_force_private=Tá Force Private cumasaithe: ní féidir stórais phríobháideacha a dhéanamh poiblí. @@ -705,6 +707,8 @@ public_profile=Próifíl Phoiblí biography_placeholder=Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid) location_placeholder=Comhroinn do shuíomh thart le daoine eile profile_desc=Rialú conas a thaispeánfar do phróifíl d'úsáideoirí eile. Úsáidfear do phríomhsheoladh ríomhphoist le haghaidh fógraí, aisghabháil pasfhocail agus oibríochtaí Git gréasán-bhunaithe. +password_username_disabled=Níl cead agat a n-ainm úsáideora a athrú. Déan teagmháil le do riarthóir suímh le haghaidh tuilleadh sonraí. +password_full_name_disabled=Níl cead agat a n-ainm iomlán a athrú. Déan teagmháil le do riarthóir suímh le haghaidh tuilleadh sonraí. full_name=Ainm Iomlán website=Láithreán Gréasáin location=Suíomh @@ -970,7 +974,7 @@ remove_account_link=Bain Cuntas Nasctha remove_account_link_desc=Ag baint cuntas nasctha, cuirfear a rochtain ar do chuntas Gitea a chúlghairm. Lean ar aghaidh? remove_account_link_success=Tá an cuntas nasctha bainte amach. -hooks.desc=Cuir cuaillí gréasáin leis a spreagfar do <strong>gach stór</strong> ar leatsa iad. +hooks.desc=Cuir crúcaí gréasán leis a spreagfar do <strong>gach stór</strong> ar leatsa iad. orgs_none=Níl tú ina bhall d'aon eagraíochtaí. repos_none=Níl aon stórais agat. @@ -2089,7 +2093,7 @@ settings.collaboration.write=Scríobh settings.collaboration.read=Léigh settings.collaboration.owner=Úinéir settings.collaboration.undefined=Neamhshainithe -settings.hooks=Gníomhartha Gréasáin +settings.hooks=Crúcaí Gréasán settings.githooks=Crúcanna Git settings.basic_settings=Socruithe Bunúsacha settings.mirror_settings=Socruithe Scáthán @@ -2126,41 +2130,797 @@ settings.branches.switch_default_branch=Athraigh Brainse Réamhshocraithe settings.branches.update_default_branch=An Brainse Réamhshocraithe a nuashonrú settings.branches.add_new_rule=Cuir Riail Nua leis settings.advanced_settings=Ardsocruithe +settings.wiki_desc=Cumasaigh Stór Vicí +settings.use_internal_wiki=Úsáid Vicí Insuite +settings.default_wiki_branch_name=Ainm Brainse Réamhshocraithe Vicí +settings.default_wiki_everyone_access=Cead Rochtana Réamhshocraithe d'úsáideoirí sínithe isteach: +settings.failed_to_change_default_wiki_branch=Theip ar an brainse réamhshocraithe vicí a athrú. +settings.use_external_wiki=Úsáid Vicí Seachtrach +settings.external_wiki_url=URL Vicí Seachtrach +settings.external_wiki_url_error=Ní URL bailí é URL seachtrach vicí. +settings.external_wiki_url_desc=Atreoraítear cuairteoirí chuig an URL wiki seachtrach agus iad ag cliceáil ar an gcluaisín wiki. +settings.issues_desc=Cumasaigh Rianóir Saincheist Stórais +settings.use_internal_issue_tracker=Úsáid Rianóir Saincheist Ionsuite +settings.use_external_issue_tracker=Úsáid Rianaire Eisiúint Sheachtrach +settings.external_tracker_url=URL Rianaithe Saincheisteanna Seachtrach +settings.external_tracker_url_error=Ní URL bailí é an URL rianaitheora saincheisteanna seachtrach. +settings.external_tracker_url_desc=Déantar cuairteoirí a atreorú chuig an URL rianaithe eisiúintí seachtracha nuair a chliceálann siad ar an táb saincheisteanna. +settings.tracker_url_format=Formáid URL Rianaithe Saincheist Seachtrach +settings.tracker_url_format_error=Ní URL bailí é an fhormáid URL rianaitheora saincheisteanna seachtrach. +settings.tracker_issue_style=Formáid Uimhir Rianaithe Saincheisteanna +settings.tracker_issue_style.numeric=Uimhriúil +settings.tracker_issue_style.alphanumeric=Alfauméireacha +settings.tracker_issue_style.regexp=Léiriú Rialta +settings.tracker_issue_style.regexp_pattern=Patrún Léirithe Rialta +settings.tracker_issue_style.regexp_pattern_desc=Úsáidfear an chéad ghrúpa a gabhadh in ionad <code>{index}</code>. +settings.tracker_url_format_desc=Úsáid na sealbhóirí áite <code>{user}</code>, <code>{repo}</code> agus <code>{index}</code> le haghaidh an ainm úsáideora, an t-ainm stórtha agus an t-innéacs eisiúna. +settings.enable_timetracker=Cumasaigh Rianú Ama +settings.allow_only_contributors_to_track_time=Lig do Rannpháirtithe Amach Am a Rianú +settings.pulls_desc=Cumasaigh Iarratais Tarraingthe Stóras +settings.pulls.ignore_whitespace=Déan neamhaird de spás bán le haghaidh coinbhleachtaí +settings.pulls.enable_autodetect_manual_merge=Cumasaigh cumasc láimhe autodetector (Nóta: I roinnt cásanna speisialta, is féidir míbhreithiúnais tarlú) +settings.pulls.allow_rebase_update=Cumasaigh brainse iarratais tarraingthe a nuashonrú trí athbhunú +settings.pulls.default_delete_branch_after_merge=Scrios brainse an iarratais tarraingthe tar éis cumasc de réir réamhshocraithe +settings.pulls.default_allow_edits_from_maintainers=Ceadaigh eagarthóirí ó chothabhálaí de réir réamhshocraithe +settings.releases_desc=Cumasaigh Eisiúintí Stórais +settings.packages_desc=Cumasaigh Clárlann na bPacáistí Taisclainne +settings.projects_desc=Cumasaigh Tionscadail +settings.projects_mode_desc=Mód Tionscadail (cé na cineálacha tionscadail le taispeáint) +settings.projects_mode_repo=Tionscadail stórais amháin +settings.projects_mode_owner=Tionscadail úsáideora nó org amháin settings.projects_mode_all=Gach tionscadal +settings.actions_desc=Cumasaigh Gníomhartha Taiscthe +settings.admin_settings=Socruithe Riarthóra +settings.admin_enable_health_check=Cumasaigh Seiceálacha Sláinte Stórais (git fsck) +settings.admin_code_indexer=Innéacsaitheoir Cód +settings.admin_stats_indexer=Innéacsóir Staitisticí Cód +settings.admin_indexer_commit_sha=SHA Innéacsaithe Deiridh +settings.admin_indexer_unindexed=Neamh-innéacsaithe +settings.reindex_button=Cuir le Scuaine Reindex +settings.reindex_requested=Athinnéacsú Iarrtha +settings.admin_enable_close_issues_via_commit_in_any_branch=Saincheist a dhúnadh trí ghealltanas a rinneadh i mbrainse neamh-mhainneachtana +settings.danger_zone=Crios Contúirte +settings.new_owner_has_same_repo=Tá stóras leis an ainm céanna ag an úinéir nua cheana féin. Roghnaigh ainm eile le do thoil. +settings.convert=Tiontaigh go Stóras Rialta +settings.convert_desc=Is féidir leat an scáthán seo a thiontú ina stór rialta. Ní féidir é seo a chur ar ais. +settings.convert_notices_1=Déanfaidh an oibríocht seo an scáthán a thiontú ina stóras rialta agus ní féidir é a chur ar ais. +settings.convert_confirm=Tiontaigh Stóras +settings.convert_succeed=Tá an scáthán tiontaithe ina stóras rialta. +settings.convert_fork=Tiontaigh go Stóras Rialta +settings.convert_fork_desc=Is féidir leat an forc seo a thiontú ina stóras rialta. Ní féidir é seo a chur ar ais. +settings.convert_fork_notices_1=Déanfaidh an oibríocht seo an forc a thiontú ina stóras rialta agus ní féidir é a chur ar ais. +settings.convert_fork_confirm=Tiontaigh Stóras +settings.convert_fork_succeed=Tá an forc tiontaithe ina stóras rialta. +settings.transfer=Úinéireacht Aistrithe +settings.transfer.rejected=Diúltaíodh d'aistriú stóras. +settings.transfer.success=D'éirigh le haistriú stóras. +settings.transfer.blocked_user=Ní féidir an stóras a aistriú toisc go bhfuil bac á chur ort ag an úinéir nua. +settings.transfer_abort=Cealaigh aistriú +settings.transfer_abort_invalid=Ní féidir leat aistriú stóras nach bhfuil ann a chealú. +settings.transfer_abort_success=Cuireadh an t-aistriú stóras chuig %s ar ceal go rathúil. +settings.transfer_desc=Aistrigh an stóras seo chuig úsáideoir nó chuig eagraíocht a bhfuil cearta riarthóra agat ina leith. +settings.transfer_form_title=Cuir isteach ainm an stóras mar dhearbhú: +settings.transfer_in_progress=Tá aistriú leanúnach ann faoi láthair. Cealaigh é más mian leat an stóras seo a aistriú chuig úsáideoir eile. +settings.transfer_notices_1=- Caillfidh tú rochtain ar an stóras má aistríonn tú é chuig úsáideoir aonair. +settings.transfer_notices_2=- Coimeádfaidh tú rochtain ar an stóras má aistríonn tú é chuig eagraíocht a bhfuil (comh)úinéir agat. +settings.transfer_notices_3=- Má tá an stóras príobháideach agus má aistrítear é chuig úsáideoir aonair, cinnteoidh an gníomh seo go bhfuil ar a laghad cead léite ag an úsáideoir (agus athraíonn sé ceadanna más gá). +settings.transfer_notices_4=- Más le heagraíocht an stóras, agus má aistríonn tú chuig eagraíocht nó duine aonair eile é, caillfidh tú na naisc idir saincheisteanna an taisclainne agus bord tionscadail na heagraíochta. +settings.transfer_owner=Úinéir nua +settings.transfer_perform=Déan Aistriú +settings.transfer_started=`Tá an stóras seo marcáilte le haistriú agus tá sé ag fanacht le deimhniú ó "%s"` +settings.transfer_succeed=Tá an stóras aistrithe. +settings.signing_settings=Socruithe Fíoraithe Sínithe +settings.trust_model=Samhail Iontaobhas Sínithe +settings.trust_model.default=Múnla Iontaobhais Réamhshocraithe +settings.trust_model.default.desc=Úsáid an tsamhail iontaobhais stórais réamhshocraithe don suiteáil settings.trust_model.collaborator=Comhoibritheoir settings.trust_model.collaborator.long=Comhoibritheoir: Sínithe muinín ag comhoibrithe -settings.trust_model.collaboratorcommitter.long=Comhoibritheo+Coiteoir: Sínithe iontaobhais ag comhoibritheoirí a mheaitseann leis an gealltóir +settings.trust_model.collaborator.desc=Déanfar sínithe bailí ó chomhoibritheoirí an stóras seo a mharcáil mar 'iontaofa' – (cibé acu a mheaitseálann siad an tiomnóir nó nach bhfuil). Seachas sin, marcálfar sínithe bailí mar 'neamhiontaofa' má mheaitseálann an síniú an tiomnóir agus mar 'neamh-mheaitseáilte' mura bhfuil. +settings.trust_model.committer=Coimisitheoir +settings.trust_model.committer.long=Gealltóir: Sínithe iontaobhais a mheaitseálann na coimitheoirí (Meaitseálann sé seo le GitHub agus cuirfidh sé iallach ar Gitea gealltanais sínithe Gitea a bheith mar an tiomnóir) +settings.trust_model.committer.desc=Ní mharcálfar "muinín" ar shínithe bailí ach amháin má mheaitseálann siad leis an gcoiste, nó déanfar iad a mharcáil "gan mheaitseáil". Cuireann sé seo iachall ar Gitea a bheith mar an tiomnóir ar ghealltanais sínithe agus an fíor-chimisteoir marcáilte mar Comhúdar: agus Co-tiomanta ag: leantóir sa chimiú. Caithfidh an eochair réamhshocraithe Gitea a bheith ag teacht le hÚsáideoir sa bhunachar sonraí. +settings.trust_model.collaboratorcommitter=Comhoibritheo+Coimiteoir +settings.trust_model.collaboratorcommitter.long=Comhoibrí+Coiste: sínithe muiníne ó chomhoibrithe a mheaitseálann an tiomnóir +settings.trust_model.collaboratorcommitter.desc=Déanfar sínithe bailí ó chomhoibritheoirí ar an stór seo a mharcáil "muinín" má mheaitseálann siad leis an gcoiste. Seachas sin, marcálfar "neamhiontaofa" ar shínithe bailí má mheaitseálann an síniú leis an gcoiste agus "gan mheaitseáil" ar shlí eile. Cuirfidh sé seo iallach ar Gitea a mharcáil mar an tiomnóir ar ghealltanais shínithe agus an fíor-choiste a bheith marcáilte mar Comhúdaraithe Ag: agus Comhthiomanta Ag: leantóir sa ghealltanas. Caithfidh an eochair réamhshocraithe Gitea a bheith ag teacht le hÚsáideoir sa bhunachar sonraí. +settings.wiki_delete=Scrios Sonraí Vicí +settings.wiki_delete_desc=Tá sonraí wiki stóras a scriosadh buan agus ní féidir iad a chur ar ais. +settings.wiki_delete_notices_1=- Scriosfaidh agus díchumasóidh sé seo an stóras vicí do %s go buan. +settings.confirm_wiki_delete=Scrios Sonraí Vicí +settings.wiki_deletion_success=Scriosadh sonraí vicí an stórais. +settings.delete=Scrios an Stóras seo +settings.delete_desc=Tá scriosadh stóras buan agus ní féidir é a chealú. +settings.delete_notices_1=- <strong>NÍ FÉIDIR</strong> an oibríocht seo a chealú. +settings.delete_notices_2=- Scriosfaidh an oibríocht seo stór <strong>%s</strong> go buan lena n-áirítear cód, ceisteanna, nótaí tráchta, sonraí vicí agus socruithe comhoibrithe. +settings.delete_notices_fork_1=- Beidh forcanna den stóras seo neamhspleách tar éis iad a scriosadh. +settings.deletion_success=Tá an stóras scriosta. +settings.update_settings_success=Nuashonraíodh na socruithe stóras. +settings.update_settings_no_unit=Ba cheart go gceadódh an stóras idirghníomhú de chineál éigin ar a laghad. +settings.confirm_delete=Scrios Stóras +settings.add_collaborator=Cuir Comhoibritheoir leis +settings.add_collaborator_success=Cuireadh an comhoibritheoir leis. +settings.add_collaborator_inactive_user=Ní féidir úsáideoir neamhghníomhach a chur mar chomhoibritheoir. +settings.add_collaborator_owner=Ní féidir úinéir a chur leis mar chomhoibritheoir. +settings.add_collaborator_duplicate=Tá an comhoibrí curtha leis an stóras seo cheana féin. +settings.add_collaborator.blocked_user=Cuireann úinéir an stóras bac ar an gcomhoibritheoir nó a mhalairt. +settings.delete_collaborator=Bain +settings.collaborator_deletion=Bain Comhoibritheoir +settings.collaborator_deletion_desc=Má dhéantar comhoibrí a bhaint, déanfar a rochtain ar an stóras seo a chúlghairm. Lean ort? +settings.remove_collaborator_success=Tá an comhoibritheoir bainte. +settings.org_not_allowed_to_be_collaborator=Ní féidir eagraíochtaí a chur leis mar chomhoibritheoir. +settings.change_team_access_not_allowed=Tá rochtain foirne a athrú don stóras teoranta d'úinéir eagraíochta +settings.team_not_in_organization=Níl an fhoireann san eagraíocht chéanna leis an stóras +settings.teams=Foirne +settings.add_team=Cuir Foireann leis +settings.add_team_duplicate=Tá an stóras ag an bhfoireann cheana féin +settings.add_team_success=Tá rochtain ag an bhfoireann anois ar an stóras. +settings.change_team_permission_tip=Tá cead na foirne socraithe ar leathanach socraithe foirne agus ní féidir é a athrú in aghaidh an stóras +settings.delete_team_tip=Tá rochtain ag an bhfoireann seo ar gach stórais agus ní féidir í a bhaint +settings.remove_team_success=Tá rochtain na foirne ar an stóras bainte amach. +settings.add_webhook=Cuir Crúca Gréasán leis +settings.add_webhook.invalid_channel_name=Ní féidir ainm cainéal Crúca Gréasán a bheith folamh agus ní féidir ach carachtar # a bheith ann. +settings.hooks_desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibríoch chuig freastalaí nuair a chuireann imeachtaí áirithe Gitea tús. Léigh tuilleadh sa <a target="_blank" rel="noopener noreferrer" href="%s">treoirleabhair gréasáin</a>. +settings.webhook_deletion=Bain Crúca Gréasán +settings.webhook_deletion_desc=Scriostar a shocruithe agus a stair seachadta a bhaineann le Crúca Gréasán a bhaint. Lean ar aghaidh? +settings.webhook_deletion_success=Tá an Crúca Gréasán bainte amach. +settings.webhook.test_delivery=Seachadadh Tástála +settings.webhook.test_delivery_desc=Déan tástáil ar an Crúca Gréasán seo le himeacht bhréige. +settings.webhook.test_delivery_desc_disabled=Chun an Crúca Gréasán seo a thástáil le himeacht bhréige, gníomhachtaigh é. +settings.webhook.request=Iarratas +settings.webhook.response=Freagra +settings.webhook.headers=Ceanntásca +settings.webhook.payload=Ábhar settings.webhook.body=Comhlacht +settings.webhook.replay.description=Seinn an Crúca Gréasán seo arís. +settings.webhook.replay.description_disabled=Chun an Crúca Gréasán seo a athsheinm, gníomhachtaigh é. +settings.webhook.delivery.success=Cuireadh imeacht leis an scuaine seachadta. D'fhéadfadh sé cúpla soicind a thógáil sula dtaispeántar sé sa stair seachadta. +settings.githooks_desc=Tá Git Crúcaí faoi thiomáint ag Git féin. Is féidir leat comhaid crúca a chur in eagar thíos chun oibríochtaí saincheaptha a shocrú. +settings.githook_edit_desc=Mura bhfuil an hook neamhghníomhach, cuirfear ábhar samplach i láthair. Má fhágann tú ábhar go luach folamh díchumasófar an crúca seo. +settings.githook_name=Ainm Crúca +settings.githook_content=Ábhar Crúca +settings.update_githook=Nuashonraigh Crúca +settings.add_webhook_desc=Seolfaidh Gitea iarratais <code>POST</code> le cineál ábhar sonraithe chuig an spriocURL. Léigh tuilleadh sa <a target="_blank" rel="noopener noreferrer" href="%s">treoir Crúcaí Gréasán</a>. +settings.payload_url=URL spriocdhírithe +settings.http_method=Modh HTTP +settings.content_type=Cineál Ábhar POST +settings.secret=Rúnda +settings.slack_username=Ainm úsáideora +settings.slack_icon_url=URL deilbhín settings.slack_color=Dath +settings.discord_username=Ainm úsáideora +settings.discord_icon_url=URL deilbhín +settings.event_desc=Truicear Ar: +settings.event_push_only=Imeachtaí Brúigh +settings.event_send_everything=Gach Imeacht +settings.event_choose=Imeachtaí Saincheaptha… +settings.event_header_repository=Imeachtaí Stóras +settings.event_create=Cruthaigh +settings.event_create_desc=Cruthaíodh brainse nó clib. +settings.event_delete=Scrios +settings.event_delete_desc=Brainse nó clib scriosta. settings.event_fork=Forc -settings.event_wiki=Wiki +settings.event_fork_desc=Forcadh stóras. +settings.event_wiki=Vicí +settings.event_wiki_desc=Leathanach Vicí cruthaithe, athainmnithe, curtha in eagar nó scriosta. settings.event_release=Scaoileadh +settings.event_release_desc=Scaoileadh foilsithe, nuashonraithe nó scriosta i stóras. settings.event_push=Brúigh +settings.event_force_push=Fórsa Brúigh +settings.event_push_desc=Brúigh Git chuig stóras. +settings.event_repository=Stóras +settings.event_repository_desc=Stóras a cruthaíodh nó a scriosadh. +settings.event_header_issue=Imeachtaí Eisiúint +settings.event_issues=Saincheisteanna +settings.event_issues_desc=Osclaíodh, dúnadh, athosclaíodh nó cuireadh an cheist in eagar. +settings.event_issue_assign=Saincheist Sannaithe +settings.event_issue_assign_desc=Eisiúint sannta nó neamhshannta. +settings.event_issue_label=Eisiúint Lipéadaithe +settings.event_issue_label_desc=Lipéid eisiúna nuashonraithe nó glanta. +settings.event_issue_milestone=Clocha Míle Saincheiste +settings.event_issue_milestone_desc=Clocha Míle Saincheiste nó Clocha Míle de-Saincheiste. +settings.event_issue_comment=Trácht Eisiúna +settings.event_issue_comment_desc=Trácht eisiúna cruthaithe, curtha in eagar nó a scriosadh. +settings.event_header_pull_request=Tarraingt Imeachtaí Iarratas +settings.event_pull_request=Iarratas Tarraingthe +settings.event_pull_request_desc=Iarratas tarraingthe oscailte, dúnta, athoscailte nó curtha in eagar. +settings.event_pull_request_assign=Iarratas Tarraingthe Sannta +settings.event_pull_request_assign_desc=Iarratas tarraingthe sannta nó neamhshannta. +settings.event_pull_request_label=Iarratas Tarraingthe Lipéadaithe +settings.event_pull_request_label_desc=Tarraing lipéid iarratais nuashonraithe nó glanta. +settings.event_pull_request_milestone=Iarratas Tarraing Cloch Mhíle +settings.event_pull_request_milestone_desc=Iarratas tarraing clocha míle nó dí-chlocha míle. +settings.event_pull_request_comment=Trácht ar Iarratas Tarraingthe +settings.event_pull_request_comment_desc=Trácht ar iarratas tarraingthe cruthaithe, curtha in eagar, nó scriosta. +settings.event_pull_request_review=Iarratas Tarraingthe Athbhreithnithe +settings.event_pull_request_review_desc=Tarraing iarratas ceadaithe, diúltaithe nó trácht athbhreithnithe. +settings.event_pull_request_sync=Iarratas Tarraing Sincronaithe +settings.event_pull_request_sync_desc=Tarraing iarratas sioncrónaithe. +settings.event_pull_request_review_request=Iarratas ar Athbhreithniú Tarraingthe Iarrtha +settings.event_pull_request_review_request_desc=Tarraing athbhreithniú iarratais iarrtha nó baineadh iarratas athbhreithnithe. +settings.event_pull_request_approvals=Ceaduithe Iarratais Tarraing +settings.event_pull_request_merge=Cumaisc Iarratas Tarraing +settings.event_package=Pacáiste +settings.event_package_desc=Pacáiste a cruthaíodh nó a scriosadh i stóras. +settings.branch_filter=Scagaire brainse +settings.branch_filter_desc=Liosta bán brainse le haghaidh brú, cruthú brainse agus imeachtaí scriosta brainse, sonraithe mar phatrún glob. Má tá sé folamh nó <code>*</code>, tuairiscítear imeachtaí do gach brainse. Féach <a href="%[1]s">%[2]s</a> doiciméadú le haghaidh comhréire. Samplaí: <code>máistir</code>, <code>{master,release*}</code>. +settings.authorization_header=Ceanntásc Údaraithe +settings.authorization_header_desc=Cuirfear san áireamh mar cheanntásc údaraithe d'iarratais nuair a bheidh ann Samplaí: %s. +settings.active=Gníomhach +settings.active_helper=Seolfar faisnéis faoi imeachtaí spreagtha chuig an URL Crúca Gréasán seo. +settings.add_hook_success=Cuireadh an Crúca Gréasán leis. +settings.update_webhook=Nuashonraigh Crúca Gréasán +settings.update_hook_success=Nuashonraíodh an Crúca Gréasán. +settings.delete_webhook=Bain Crúca Gréasán +settings.recent_deliveries=Seachadtaí le déana +settings.hook_type=Cineál Crúca +settings.slack_token=Comhartha +settings.slack_domain=Fearann +settings.slack_channel=Cainéal +settings.add_web_hook_desc=Comhtháthaigh <a target="_blank" rel="noreferrer" href="%s">%s</a> isteach i do stóras. +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_telegram=Teileagram +settings.web_hook_name_matrix=Maitrís +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite +settings.web_hook_name_feishu=Feishu +settings.web_hook_name_larksuite=Lark Suite +settings.web_hook_name_wechatwork=WeCom (Wechat Work) +settings.web_hook_name_packagist=Packagist +settings.packagist_username=Ainm úsáideora Pacagist +settings.packagist_api_token=Comhartha API +settings.packagist_package_url=URL pacáiste Packagist +settings.deploy_keys=Eochracha a imscaradh +settings.add_deploy_key=Cuir Eochair Imscartha leis +settings.deploy_key_desc=Tá rochtain tarraingthe léite amháin ag eochracha imscartha ar an stóras. +settings.is_writable=Cumasaigh Rochtain Scríobh +settings.is_writable_info=Lig don eochair imlonnaithe seo <strong>a bhrú</strong> chuig an stóras. +settings.no_deploy_keys=Níl aon eochracha imscartha ann fós. +settings.title=Teideal +settings.deploy_key_content=Ábhar +settings.key_been_used=Tá eochair imscartha le hábhar comhionann in úsáid cheana féin. +settings.key_name_used=Tá eochair imscartha leis an ainm céanna ann cheana féin. +settings.add_key_success=Tá an eochair imlonnaithe "%s" curtha leis. +settings.deploy_key_deletion=Bain Eochair Imlonnaithe +settings.deploy_key_deletion_desc=Ag baint eochair imscartha, cuirfear a rochtain ar an stóras seo a chúlghairm. Lean ar aghaidh? +settings.deploy_key_deletion_success=Tá an eochair imscartha bainte amach. +settings.branches=Brainsí +settings.protected_branch=Cosaint Brainse +settings.protected_branch.save_rule=Sábháil Riail +settings.protected_branch.delete_rule=Scrios Riail +settings.protected_branch_can_push=Ceadaigh bhrú? +settings.protected_branch_can_push_yes=Is féidir leat a bhrú +settings.protected_branch_can_push_no=Ní féidir leat a bhrú +settings.branch_protection=Rialacha Cosanta Brainse do Bhrainse '<b>%s</b>' +settings.protect_this_branch=Cumasaigh Cosaint Brainse +settings.protect_this_branch_desc=Cosc ar scriosadh agus cuireann sé srian le Git a bhrú agus a chumasc go dtí an brainse. +settings.protect_disable_push=Díchumasaigh Brúigh +settings.protect_disable_push_desc=Ní cheadfar aon bhrú chuig an mbrainse seo. +settings.protect_disable_force_push=Díchumasaigh Fórsa Brú +settings.protect_disable_force_push_desc=Ní cheadfar aon fhórsa a bhrú chuig an mbrainse seo. +settings.protect_enable_push=Cumasaigh Brúigh +settings.protect_enable_push_desc=Beidh cead ag aon duine a bhfuil rochtain scríofa aige/aici brú chuig an mbrainse seo (ach gan brú a bhrú). +settings.protect_enable_force_push_all=Cumasaigh Fórsa Brúigh +settings.protect_enable_force_push_all_desc=Beidh cead ag duine ar bith a bhfuil rochtain brú aige brú a chur ar an mbrainse seo. +settings.protect_enable_force_push_allowlist=Brú Fórsa Srianta ón Liosta Ceadaithe +settings.protect_enable_force_push_allowlist_desc=Ní cheadófar ach d’úsáideoirí liostaithe nó foirne a bhfuil rochtain bhrú acu brú a chur ar an mbrainse seo. +settings.protect_enable_merge=Cumasaigh Cumaisc +settings.protect_enable_merge_desc=Beidh cead ag aon duine a bhfuil rochtain scríofa aige na hiarratais tarraingte a chumasc leis an mbrainse seo. +settings.protect_whitelist_committers=Brú Srianta ón Liosta Ceadaithe +settings.protect_whitelist_committers_desc=Ní bheidh cead ach úsáideoirí nó foirne liostaithe ceadaithe brú chuig an mbrainse seo (ach gan brú a chur i bhfeidhm). +settings.protect_whitelist_deploy_keys=Eochracha imscartha ón Liosta Ceadaithe le rochtain scríofa chun brú. +settings.protect_whitelist_users=Úsáideoirí ar an Liosta Ceadaithe chun brú a dhéanamh: +settings.protect_whitelist_teams=Foirne ar an Liosta Ceadaithe chun brú a dhéanamh: +settings.protect_force_push_allowlist_users=Úsáideoirí ar an Liosta Ceadaithe le haghaidh brú fórsa a dhéanamh: +settings.protect_force_push_allowlist_teams=Foirne ar an Liosta Ceadaithe le haghaidh brú fórsa a dhéanamh: +settings.protect_force_push_allowlist_deploy_keys=Eochracha imscaradh le rochtain brú ar an Liosta Ceadaithe le haghaidh brú fórsa a dhéanamh. +settings.protect_merge_whitelist_committers=Cumasaigh Liosta Ceadaithe Cumaisc +settings.protect_merge_whitelist_committers_desc=Ní lig ach d'úsáideoirí nó d'fhoirne liostaithe iarratais tarraingthe isteach sa bhrainse seo a chumasc. +settings.protect_merge_whitelist_users=Úsáideoirí ar an Liosta Ceadaithe le haghaidh cumasc: +settings.protect_merge_whitelist_teams=Foirne ar an Liosta Ceadaithe le haghaidh cumasc: +settings.protect_check_status_contexts=Cumasaigh Seiceáil Stádas +settings.protect_status_check_patterns=Patrúin seiceála stádais: +settings.protect_status_check_patterns_desc=Iontráil patrúin chun a shonrú cé na seiceálacha stádais a chaithfidh pas a fháil sular féidir brainsí a chumasc le brainse a chomhoibríonn leis an riail seo. Sonraíonn gach líne patrún. Ní féidir patrúin a bheith folamh. +settings.protect_check_status_contexts_desc=A cheangal ar sheiceálacha stádais pas a fháil roimh chumasc. Nuair a bheidh sé cumasaithe, ní mór gealltanais a bhrú ar dtús chuig brainse eile, ansin iad a chumasc nó a bhrú go díreach chuig brainse a thagann leis an riail seo tar éis do sheiceálacha stádais a bheith caite. Mura ndéantar comhthéacs ar bith a mheaitseáil, ní mór go n-éireodh leis an ngealltanas deiridh beag beann ar an gcomhthéacs. +settings.protect_check_status_contexts_list=Seiceálacha stádais a fuarthas sa tseachtain seo caite don stóras seo +settings.protect_status_check_matched=Comhoiriúnach +settings.protect_invalid_status_check_pattern=Patrún seiceála stádais neamhbhailí: "%s". +settings.protect_no_valid_status_check_patterns=Gan aon phatrúin seiceála stádais bailí. +settings.protect_required_approvals=Ceaduithe riachtanacha: +settings.protect_required_approvals_desc=Ná lig ach iarratas tarraingthe a chumasc le go leor ceaduithe riachtanacha. Tá ceaduithe riachtanacha ó úsáideoirí nó foirne atá ar an liosta ceadaithe nó ó aon duine a bhfuil rochtain scríofa acu. +settings.protect_approvals_whitelist_enabled=Ceaduithe a theorannú le húsáideoirí nó foirne liostaithe +settings.protect_approvals_whitelist_enabled_desc=Ní dhéanfar ach léirmheasanna ó úsáideoirí nó foirne ceadaithe a áireamh chuig na formheasanna riachtanacha. Gan liosta ceadaithe formheasa, áireofar léirmheasanna ó aon duine a bhfuil rochtain scríofa acu ar na ceaduithe riachtanacha. +settings.protect_approvals_whitelist_users=Léirmheastóirí ón Liosta Ceadaithe: +settings.protect_approvals_whitelist_teams=Foirne ar an Liosta Ceadaithe le haghaidh athbhreithnithe: +settings.dismiss_stale_approvals=Déan seancheaduithe a dhíbhe +settings.dismiss_stale_approvals_desc=Nuair a bhrúitear gealltanais nua a athraíonn ábhar an iarratais tarraingthe chuig an mbrainse, déanfar sean-cheaduithe a dhíchur. +settings.ignore_stale_approvals=Déan neamhaird de sheancheaduithe +settings.ignore_stale_approvals_desc=Ná cuir faomhadh a rinneadh ar ghealltanais níos sine (athbhreithnithe seanchaite) san áireamh i dtreo cé mhéad faomhadh atá ag an PR. Ní bhaineann le hábhar má dhéantar athbhreithnithe seanchaite a dhíbhe cheana féin. +settings.require_signed_commits=Ceangaltais Sínithe a cheangal +settings.require_signed_commits_desc=Diúltaigh brú chuig an mbrainse seo má tá siad neamhshínithe nó neamh-fhíoraithe. +settings.protect_branch_name_pattern=Patrún Ainm Brainse Cosanta +settings.protect_branch_name_pattern_desc=Patrúin ainmneacha brainse faoi chosaint. Féach <a href="%s">an cháipéisíocht</a> le haghaidh comhréire patrún. Samplaí: príomh, scaoileadh/** +settings.protect_patterns=Patrúin +settings.protect_protected_file_patterns=Patrúin comhaid faoi chosaint (scartha ag baint úsáide as leathchóilín ';'): +settings.protect_protected_file_patterns_desc=Ní cheadaítear comhaid chosanta a athrú go díreach fiú má tá cearta ag an úsáideoir comhaid sa bhrainse seo a chur leis, a chur in eagar nó a scriosadh. Is féidir patrúin iolracha a dheighilt trí úsáid a bhaint as leathstad (';'). Féach ar <a href='%[1]s'>%[2]s</a> do chomhréir phatrúin. Samplaí: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. +settings.protect_unprotected_file_patterns=Patrúin comhaid gan chosaint (scartha ag baint úsáide as leathchóilín ';'): +settings.protect_unprotected_file_patterns_desc=Comhaid gan chosaint a cheadaítear a athrú go díreach má tá rochtain scríofa ag an úsáideoir, ag seachaint srianadh brú. Is féidir patrúin iolracha a dheighilt trí úsáid a bhaint as leathstad (';'). Féach ar <a href='%[1]s'>%[2]s</a> do chomhréir phatrúin. Samplaí: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. +settings.add_protected_branch=Cumasaigh cosaint +settings.delete_protected_branch=Díchumasaigh cosaint +settings.update_protect_branch_success=Tá cosaint brainse don riail "%s" nuashonraithe. +settings.remove_protected_branch_success=Baineadh cosaint brainse don riail "%s". +settings.remove_protected_branch_failed=Theip ar riail cosanta brainse "%s" a bhaint. +settings.protected_branch_deletion=Scrios Cosaint Brainse +settings.protected_branch_deletion_desc=Ligeann cosaint brainse a dhíchumasú d'úsáideoirí a bhfuil cead scríofa acu brú chuig an mbrainse. Lean ar aghaidh? +settings.block_rejected_reviews=Cuir bac ar chumasc ar léirmheasanna diúltaithe +settings.block_rejected_reviews_desc=Ní bheidh cumasc indéanta nuair a iarrann athbhreithnithe oifigiúla athruithe, fiú má tá go leor ceadaithe ann. +settings.block_on_official_review_requests=Cuir bac ar chumasc ar iarratais ar athbhreithniú oifigiúil +settings.block_on_official_review_requests_desc=Ní bheidh sé indéanta cumasc nuair a bhíonn iarratais oifigiúla ar athbhreithniú aige, fiú má tá go leor ceadaithe ann. +settings.block_outdated_branch=Cuir bac ar chumasc má tá an t-iarratas tarraingthe as dáta +settings.block_outdated_branch_desc=Ní bheidh cumasc indéanta nuair a bhíonn ceannbhrainse taobh thiar de bhronnbhrainse. +settings.default_branch_desc=Roghnaigh brainse stóras réamhshocraithe le haghaidh iarratas tarraingte agus geallann an cód: +settings.merge_style_desc=Stíleanna Cumaisc +settings.default_merge_style_desc=Stíl Cumaisc Réamhshocraithe +settings.choose_branch=Roghnaigh brainse… +settings.no_protected_branch=Níl aon bhrainsí cosanta ann. +settings.edit_protected_branch=Cuir in eagar +settings.protected_branch_required_rule_name=Ainm riail riachtanach +settings.protected_branch_duplicate_rule_name=Ainm riail dúblach +settings.protected_branch_required_approvals_min=Ní féidir ceaduithe riachtanacha a bheith diúltach. +settings.tags=Clibeanna +settings.tags.protection=Cosaint Clib +settings.tags.protection.pattern=Patrún Clib +settings.tags.protection.allowed=Ceadaithe +settings.tags.protection.allowed.users=Úsáideoirí ceadaithe +settings.tags.protection.allowed.teams=Foirne ceadaithe +settings.tags.protection.allowed.noone=Níl aon duine +settings.tags.protection.create=Clib a chosaint +settings.tags.protection.none=Níl aon chlibeanna cosanta ann. +settings.tags.protection.pattern.description=Is féidir leat ainm amháin nó patrún glob nó slonn rialta a úsáid chun clibeanna iolracha a mheaitseáil. Léigh tuilleadh sa <a target="_blank" rel="noopener" href="%s">treoir na gclibeanna cosanta</a>. +settings.bot_token=Comhartha Bota +settings.chat_id=ID Comhrá +settings.thread_id=ID Snáithe +settings.matrix.homeserver_url=URL sheirbhíse baile +settings.matrix.room_id=ID seomra +settings.matrix.message_type=Cineál teachtaireachta +settings.visibility.private.button=Déan Príobháideach +settings.visibility.private.text=Ní amháin go gcuirfidh an infheictheacht a athrú go príobháideach an repo infheicthe amháin do bhaill cheadaithe ach féadfaidh sé an gaol idir é agus forcanna, féachadóirí agus réaltaí a bhaint. +settings.visibility.private.bullet_title=<strong>An infheictheacht a athrú go toil phríobháide</strong> +settings.visibility.private.bullet_one=Déan an stóras infheicthe do chomhaltaí ceadaithe amháin. +settings.visibility.private.bullet_two=B’fhéidir go mbainfear an gaol idir é agus <strong>forcanna</strong>, <strong>faireoirí</strong>, agus <strong>réaltaí</strong>. +settings.visibility.public.button=Déan Poiblí +settings.visibility.public.text=Má athraíonn an infheictheacht don phobal, beidh an stóras le feiceáil do dhuine ar bith. +settings.visibility.public.bullet_title=<strong>Athróidh an infheictheacht go poiblí:</strong> +settings.visibility.public.bullet_one=Déan an repo le feiceáil do dhuine ar bith. +settings.visibility.success=Athraigh infheictheacht stóras. +settings.visibility.error=Tharla earráid agus tú ag iarraidh infheictheacht an stóras a athrú. +settings.visibility.fork_error=Ní féidir infheictheacht stóras forcáilte a athrú. +settings.archive.button=Cartlann Stóras +settings.archive.header=Cartlann an Stóras seo +settings.archive.text=Má dhéantar an stóras a chartlannú, beidh sé léite go hiomlán amháin. Beidh sé i bhfolach ón bpainéal. Aon duine (ní fiú tú!) beidh siad in ann tiomantas nua a dhéanamh, nó aon saincheisteanna nó iarratais a tharraingt a oscailt. +settings.archive.success=Rinneadh an stóras a chartlannú go rathúil. +settings.archive.error=Tharla earráid agus tú ag iarraidh an stóras a chartlannú. Féach an logáil le haghaidh tuilleadh sonraí. +settings.archive.error_ismirror=Ní féidir leat stóras scátháin a chartlannú. +settings.archive.branchsettings_unavailable=Níl socruithe brainse ar fáil má tá an stóras i gcartlann. +settings.archive.tagsettings_unavailable=Níl socruithe clibeanna ar fáil má tá an stóras i gcartlann. +settings.archive.mirrors_unavailable=Níl scátháin ar fáil má tá an stóras i gcartlann. +settings.unarchive.button=Stóras gan cartlann +settings.unarchive.header=Díchartlannaigh an stóras seo +settings.unarchive.text=Beidh an stóras a dhícheangal ag athghairm a chumas chun tiomanta agus brúigh a fháil, chomh maith le fadhbanna nua agus iarratais tarraing. +settings.unarchive.success=Rinneadh an stóras a dhíchartlann go rathúil. +settings.unarchive.error=Tharla earráid agus tú ag iarraidh an stóras a dhíchartlannú. Féach an logáil le haghaidh tuilleadh sonraí. +settings.update_avatar_success=Nuashonraíodh avatar an stóras. +settings.lfs=LFS +settings.lfs_filelist=Comhaid LFS a stóráiltear sa stóras seo +settings.lfs_no_lfs_files=Níl aon chomhaid LFS stóráilte sa stóras seo +settings.lfs_findcommits=Aimsigh gealltanais +settings.lfs_lfs_file_no_commits=Níor aimsíodh aon ghealltanais don chomhad LFS seo +settings.lfs_noattribute=Níl an tréith inghlasáilte sa bhrainse réamhshocraithe ag an gcosán seo +settings.lfs_delete=Scrios comhad LFS le OID %s +settings.lfs_delete_warning=D'fhéadfadh earráidí 'níl réad ann 'ar an tseiceáil a bheith ina chúis le comhad LFS a scriosadh. An bhfuil tú cinnte? +settings.lfs_findpointerfiles=Faigh comhaid pointeora +settings.lfs_locks=Glais +settings.lfs_invalid_locking_path=Cosan neamhbhailí: %s +settings.lfs_invalid_lock_directory=Ní féidir eolaire a ghlasáil: %s +settings.lfs_lock_already_exists=Tá an glas ann cheana féin: %s +settings.lfs_lock=Glas +settings.lfs_lock_path=Cosán comhad le haghaidh glasáil... +settings.lfs_locks_no_locks=Gan Glais +settings.lfs_lock_file_no_exist=Níl an comhad faoi ghlas sa bhrainse réamhshocraithe +settings.lfs_force_unlock=Díghlasáil Fórsa +settings.lfs_pointers.found=Aimsíodh %d pointeoir(í) blob - %d bainteach, %d neamhghaolmhar (%d in easnamh ón siopa) +settings.lfs_pointers.sha=SHA Blob +settings.lfs_pointers.oid=OID +settings.lfs_pointers.inRepo=I Stóras +settings.lfs_pointers.exists=Ann sa siopa +settings.lfs_pointers.accessible=Inrochtana don Úsáideoir +settings.lfs_pointers.associateAccessible=Comhlach %d OID inrochtana +settings.rename_branch_failed_exist=Ní féidir brainse a athainmniú toisc go bhfuil spriocbhrainse %s ann. +settings.rename_branch_failed_not_exist=Ní féidir brainse %s a athainmniú toisc nach bhfuil sé ann. +settings.rename_branch_success=Ainmníodh brainse %s go rathúil go %s. +settings.rename_branch_from=sean-ainm brainse +settings.rename_branch_to=ainm brainse nua +settings.rename_branch=Athainmnigh brainse +diff.browse_source=Brabhsáil Foinse +diff.parent=tuismitheoir +diff.commit=tiomantas +diff.git-notes=Nótaí +diff.data_not_available=Níl Ábhar Difríochtaí Ar Fáil +diff.options_button=Roghanna Diff +diff.show_diff_stats=Taispeáin Staitisticí +diff.download_patch=Íoslódáil an comhad paiste +diff.download_diff=Íoslódáil Comhad Diff +diff.show_split_view=Amharc Scoilt +diff.show_unified_view=Amharc Aontaithe +diff.whitespace_button=Spás bán +diff.whitespace_show_everything=Taispeáin gach athrú +diff.whitespace_ignore_all_whitespace=Déan neamhaird de spás bán nuair a dhéantar comparáid idir línte +diff.whitespace_ignore_amount_changes=Déan neamhaird de athruithe ar an méid spás bán +diff.whitespace_ignore_at_eol=Déan neamhaird ar athruithe ar spás bán ag EOL +diff.stats_desc=D'athraigh <strong> %d comhad</strong> le <strong>%d breiseanna</strong> agus <strong>%d scriosta</strong> +diff.stats_desc_file=%d athruithe: %d breiseanna agus scriosadh %d +diff.bin=BRUSCAIR +diff.bin_not_shown=Ní thaispeántar comhad dénártha. +diff.view_file=Féach ar an gComhad +diff.file_before=Roimhe +diff.file_after=Tar éis +diff.file_image_width=Leithead +diff.file_image_height=Airde +diff.file_byte_size=Méid +diff.file_suppressed=Tá difríocht comhad cosc orthu toisc go bhfuil sé ró-mhór +diff.file_suppressed_line_too_long=Cuirtear difríocht comhad faoi chois toisc go bhfuil líne amháin nó níos mó rófhada +diff.too_many_files=Níor taispeánadh roinnt comhad mar go bhfuil an iomarca comhad athraithe sa difríocht seo +diff.show_more=Taispeáin Tuilleadh +diff.load=Difríocht Luchtaigh +diff.generated=a ghintear +diff.vendored=curtha ar fáil +diff.comment.add_line_comment=Cuir trácht líne leis +diff.comment.placeholder=Fág trácht +diff.comment.markdown_info=Tacaítear le stíliú le marcáil. +diff.comment.add_single_comment=Cuir trácht aonair leis +diff.comment.add_review_comment=Cuir trácht leis +diff.comment.start_review=Tosaigh athbhreithniú +diff.comment.reply=Freagra +diff.review=Léirmheas +diff.review.header=Cuir isteach léirmheas +diff.review.placeholder=Trácht athbhreithnithe diff.review.comment=Trácht +diff.review.approve=Ceadú +diff.review.self_reject=Ní féidir le húdair iarratais tarraing athruithe a iarraidh ar a n-iarratas tarraingthe +diff.review.reject=Iarr athruithe +diff.review.self_approve=Ní féidir le húdair iarratais tarraing a n-iarratas tarraingthe féin a chead +diff.committed_by=tiomanta ag +diff.protected=Cosanta +diff.image.side_by_side=Taobh le Taobh +diff.image.swipe=Scaoil +diff.image.overlay=Forleagan +diff.has_escaped=Tá carachtair Unicode i bhfolach ag an líne seo +diff.show_file_tree=Taispeáin crann comhad +diff.hide_file_tree=Folaigh crann comhad +releases.desc=Rian leaganacha tionscadal agus íoslódálacha. +release.releases=Eisiúintí +release.detail=Sonraí eisithe +release.tags=Clibeanna +release.new_release=Scaoileadh Nua +release.draft=Dréacht +release.prerelease=Réamh-eisiúint +release.stable=Cobhsaí +release.compare=Déan comparáid +release.edit=cuir in eagar +release.ahead.commits=Geallann <strong>%d</strong> +release.ahead.target=go %s ón scaoileadh seo +tag.ahead.target=chuig %s ón gclib seo +release.source_code=Cód Foinse +release.new_subheader=Eagraíonn eiseachtaí leaganacha tionscadail +release.edit_subheader=Eagraíonn eisiúintí leaganacha tionscadal. +release.tag_name=Ainm chlib +release.target=Sprioc +release.tag_helper=Roghnaigh clib atá ann cheana nó cruthaigh clib nua. +release.tag_helper_new=Clib nua. Cruthófar an chlib seo ón sprioc. +release.tag_helper_existing=Clib atá ann cheana. +release.title=Teideal scaoileadh +release.title_empty=Ní féidir leis an teideal a bheith folamh. +release.message=Déan cur síos ar an eisiúint seo +release.prerelease_desc=Marcáil mar Réamh-eisiúint +release.prerelease_helper=Marcáil an scaoileadh seo mí-oiriúnach le húsáid táirgeachta. +release.cancel=Cealaigh +release.publish=Foilsigh Eisiúint +release.save_draft=Sábháil Dréacht +release.edit_release=Eisiúint Nuashonraithe +release.delete_release=Scrios Scaoilte +release.delete_tag=Scrios Clib +release.deletion=Scrios Scaoilte +release.deletion_desc=Ní bhíonn scaoileadh ag scriosadh ach é ó Gitea. Ní dhéanfaidh sé difear do chlib Git, ar ábhar do stóras nó ar a stair. Lean ar aghaidh? +release.deletion_success=Tá an scaoileadh scriosta. +release.deletion_tag_desc=Scriosfar an chlib seo ón stóras. Ní athraítear inneachar agus stair na stórtha. Lean ort? +release.deletion_tag_success=Tá an chlib scriosta. +release.tag_name_already_exist=Tá eisiúint leis an ainm clib seo ann cheana féin. +release.tag_name_invalid=Níl ainm an chlib bailí. +release.tag_name_protected=Tá ainm an chlib cosanta. +release.tag_already_exist=Tá an t-ainm clib seo ann cheana féin. +release.downloads=Íoslódálacha +release.download_count=Íoslódálacha: %s +release.add_tag_msg=Úsáid teideal agus ábhar an eisiúna mar theachtaireacht chlibe. +release.add_tag=Cruthaigh Clib Amháin +release.releases_for=Eisiúintí do %s +release.tags_for=Clibeanna do %s -branch.branch_already_exists=Tá brainse "%s" ann cheana féin sa stóras seo. +branch.name=Ainm Brainse +branch.already_exists=Tá brainse leis an ainm "%s" ann cheana féin. +branch.delete_head=Scrios +branch.delete=`Scrios Brainse "%s"` +branch.delete_html=Scrios Brainse +branch.delete_desc=Tá brainse a scriosadh buan. Cé go bhféadfadh an brainse scriosta leanúint ar aghaidh ag bheith ann ar feadh tréimhse ghearr sula mbaintear í i ndáiríre, NÍ FÉIDIR é a dhíchur i bhformhór Lean ar aghaidh? +branch.deletion_success=Tá brainse "%s" scriosta. +branch.deletion_failed=Theip ar scriosadh brainse "%s". +branch.delete_branch_has_new_commits=Ní féidir brainse “%s” a scriosadh toisc go bhfuil tiomáintí nua curtha leis tar éis a chumasc. +branch.create_branch=Cruthaigh brainse %s +branch.create_from=`ó "%s"` +branch.create_success=Tá brainse "%s" cruthaithe. +branch.branch_already_exists=Tá brainse "%s" sa stóras seo cheana. +branch.branch_name_conflict=Tagann an t-ainm brainse "%s" leis an mbrainse "%s" atá ann cheana féin. +branch.tag_collision=Ní féidir brainse "%s" a chruthú mar tá clib leis an ainm céanna sa stóras cheana féin. +branch.deleted_by=Scriosta ag %s +branch.restore_success=Tá brainse "%s" curtha ar ais. +branch.restore_failed=Theip ar chur ar ais brainse "%s". +branch.protected_deletion_failed=Tá brainse "%s" cosanta. Ní féidir é a scriosadh. +branch.default_deletion_failed=Is é brainse "%s" an brainse réamhshocraithe. Ní féidir é a scriosadh. +branch.restore=`Athchóirigh Brainse "%s"` +branch.download=`Brainse Íosluchtaithe "%s"` +branch.rename=`Athainmnigh Brainse "%s"` +branch.included_desc=Tá an brainse seo mar chuid den bhrainse réamhshocraithe +branch.included=San áireamh +branch.create_new_branch=Cruthaigh brainse ón mbrainse: +branch.confirm_create_branch=Cruthaigh brainse +branch.warning_rename_default_branch=Tá tú ag athainmniú an bhrainse réamhshocraithe. +branch.rename_branch_to=Athainmnigh "%s" go: +branch.confirm_rename_branch=Athainmnigh brainse +branch.create_branch_operation=Cruthaigh brainse +branch.new_branch=Cruthaigh brainse nua +branch.new_branch_from=`Cruthaigh brainse nua ó "%s"` +branch.renamed=Ainmníodh brainse %s go %s. +tag.create_tag=Cruthaigh clib %s +tag.create_tag_operation=Cruthaigh clib +tag.confirm_create_tag=Cruthaigh clib +tag.create_tag_from=`Cruthaigh clib nua ó "%s"` +tag.create_success=Tá clib "%s" cruthaithe. +topic.manage_topics=Bainistigh topaicí +topic.done=Déanta +topic.count_prompt=Ní féidir leat níos mó ná 25 topaicí a roghnú +topic.format_prompt=Ní mór do thopaicí tosú le litir nó uimhir, is féidir daiseanna ('-') agus poncanna ('.') a áireamh, a bheith suas le 35 carachtar ar fad. Ní mór litreacha a bheith i litreacha beaga. +find_file.go_to_file=Téigh go dtí an comhad +find_file.no_matching=Níl aon chomhad meaitseála le fáil +error.csv.too_large=Ní féidir an comhad seo a rinneadh toisc go bhfuil sé ró-mhór. +error.csv.unexpected=Ní féidir an comhad seo a rindreáil toisc go bhfuil carachtar ann gan súil leis i líne %d agus i gcolún %d. +error.csv.invalid_field_count=Ní féidir an comhad seo a rindreáil toisc go bhfuil líon mícheart réimsí i líne %d. +error.broken_git_hook=Is cosúil go bhfuil crúcaí git den stór seo briste. Lean an <a target="_blank" rel="noreferrer" href="%s">doiciméadúchán</a> chun iad a cheartú, ansin brúigh roinnt gealltanas chun an stádas a athnuachan. [graphs] +component_loading=Á lódáil %s... +component_loading_failed=Ní fhéadfaí %s a luchtú +component_loading_info=Seans go dtógfaidh sé seo beagán… +component_failed_to_load=Tharla earráid gan choinne. +code_frequency.what=minicíocht cód +contributors.what=ranníocaíochtaí +recent_commits.what=tiomantáin le déanaí [org] +org_name_holder=Ainm na hEagraíochta +org_full_name_holder=Ainm iomlán na hEagraíochta +org_name_helper=Ba cheart go mbeadh ainmneacha eagraíochta gearr agus i gcuimhne. +create_org=Cruthaigh Eagraíocht +repo_updated=Nuashonraithe +members=Comhaltaí +teams=Foirne +code=Cód +lower_members=comhaltaí +lower_repositories=stórais +create_new_team=Foireann Nua +create_team=Cruthaigh Foireann +org_desc=Cur síos +team_name=Ainm Foirne +team_desc=Cur síos +team_name_helper=Ba chóir go mbeadh ainmneacha foirne gearr agus i gcuimhne. +team_desc_helper=Déan cur síos ar chuspóir nó ról na foirne. +team_access_desc=Rochtain stórais +team_permission_desc=Cead +team_unit_desc=Ceadaigh Rochtain ar Rannóga Stóras +team_unit_disabled=(Díchumasaithe) +form.name_reserved=Tá an t-ainm eagraíochta "%s" curtha in áirithe. +form.name_pattern_not_allowed=Ní cheadaítear an patrún "%s" in ainm eagraíochta. +form.create_org_not_allowed=Níl cead agat eagraíocht a chruthú. +settings=Socruithe +settings.options=Eagraíocht +settings.full_name=Ainm Iomlán +settings.email=Ríomhphost Teagmhála +settings.website=Láithreán Gréasáin +settings.location=Suíomh +settings.permission=Ceadanna +settings.repoadminchangeteam=Is féidir le riarthóir an stórais rochtain d'fhoirne a chur leis agus a bhaint +settings.visibility=Infheictheacht +settings.visibility.public=Poiblí +settings.visibility.limited=Teoranta (Infheicthe d'úsáideoirí fíordheimhnithe amháin) +settings.visibility.limited_shortname=Teoranta +settings.visibility.private=Príobháideach (Infheicthe amháin do bhaill eagraíochta) +settings.visibility.private_shortname=Príobháideach +settings.update_settings=Nuashonrú Socruithe +settings.update_setting_success=Nuashonraíodh socruithe eagraíochta. +settings.change_orgname_prompt=Nóta: Athróidh ainm na heagraíochta ag athrú URL d'eagraíochta agus saorfar an sean-ainm. +settings.change_orgname_redirect_prompt=Déanfaidh an sean-ainm a atreorú go dtí go n-éilítear é. +settings.update_avatar_success=Nuashonraíodh avatar na heagraíochta. +settings.delete=Scrios Eagraíocht +settings.delete_account=Scrios an Eagraíocht seo +settings.delete_prompt=Bainfear an eagraíocht go buan. <strong>NÍ FÉIDIR</strong> é seo a chealú! +settings.confirm_delete_account=Deimhnigh scriosadh +settings.delete_org_title=Scrios Eagraíocht +settings.delete_org_desc=Scriosfar an eagraíocht seo go buan. Lean ar aghaidh? +settings.hooks_desc=Cuir crúcaí gréasán in leis a spreagfar do <strong>gach stóras</strong> faoin eagraíocht seo. +settings.labels_desc=Cuir lipéid leis ar féidir iad a úsáid ar shaincheisteanna do <strong>gach stóras</strong> faoin eagraíocht seo. +members.membership_visibility=Infheictheacht Ballraíochta: +members.public=Infheicthe +members.public_helper=dhéanamh i bhfolach +members.private=I bhfolach +members.private_helper=a dhéanamh le feiceáil +members.member_role=Ról Comhalta: +members.owner=Úinéir +members.member=Comhalta +members.remove=Bain +members.remove.detail=Bain %[1]s de %[2]s? +members.leave=Fágáil +members.leave.detail=Fág %s? +members.invite_desc=Cuir ball nua le %s: +members.invite_now=Tabhair cuireadh Anois +teams.join=Bígí +teams.leave=Fág +teams.leave.detail=Fág %s? +teams.can_create_org_repo=Cruthaigh stórais +teams.can_create_org_repo_helper=Is féidir le baill stóras nua a chruthú san eagraíocht. Gheobhaidh an cruthaitheoir rochtain riarthóra ar an stóras nua. +teams.none_access=Gan Rochtain +teams.none_access_helper=Ní féidir le baill aon ghníomhaíocht eile a fheiceáil nó a dhéanamh ar an aonad seo. Níl aon éifeacht aige ar stórais phoiblí. +teams.general_access=Rochtain Ginearálta +teams.general_access_helper=Déanfar ceadanna baill a chinneadh ag an tábla ceadanna thíos. +teams.read_access=Léigh +teams.read_access_helper=Is féidir le baill stórais foirne a fheiceáil agus a chlónáil. +teams.write_access=Scríobh +teams.write_access_helper=Is féidir le baill léamh agus brú chuig stórais foirne. +teams.admin_access=Rochtain Riarthóra +teams.admin_access_helper=Is féidir le baill tarraingt agus brú chuig stórais foirne agus comhoibritheoirí a chur leo. +teams.no_desc=Níl aon tuairisc ag an bhfoireann seo +teams.settings=Socruithe +teams.owners_permission_desc=Tá rochtain iomlán ag úinéirí ar <strong>gach stórais</strong> agus tá <strong>rochtain ag an riarthóir</strong> ar an eagraíocht. +teams.members=Baill Foirne +teams.update_settings=Nuashonrú Socruithe +teams.delete_team=Scrios Foireann +teams.add_team_member=Cuir Comhalta Foirne leis +teams.invite_team_member=Tabhair cuireadh chuig %s +teams.invite_team_member.list=Cuirí ar Feitheamh +teams.delete_team_title=Scrios Foireann +teams.delete_team_desc=Cúlghairtear rochtain stórais óna baill a scriosadh foirne. Lean ar aghaidh? +teams.delete_team_success=Tá an fhoireann scriosta. +teams.read_permission_desc=Deonaíonn an fhoireann seo rochtain <strong>Léamh</strong>: is féidir le baill stórtha foirne a fheiceáil agus a chlónáil. +teams.write_permission_desc=Tugann an fhoireann seo rochtain do <strong>Scríobh</strong>: is féidir le baill léamh ó stórtha foirne agus iad a bhrú chucu. +teams.admin_permission_desc=Tugann an fhoireann seo rochtain do <strong>Riarachán</strong>: is féidir le baill léamh ó stórtha foirne, brú chucu agus cur leo. +teams.create_repo_permission_desc=Ina theannta sin, tugann an fhoireann seo cead <strong>Cruthaigh Stóras</strong>: is féidir le baill stórtha nua a chruthú san eagraíocht. +teams.repositories=Stórais Foirne +teams.remove_all_repos_title=Bain gach stórais foirne +teams.remove_all_repos_desc=Bainfidh sé seo gach stórais ón bhfoireann. +teams.add_all_repos_title=Cuir gach stórais leis +teams.add_all_repos_desc=Cuirfidh sé seo stórais uile na heagraíochta leis an bhfoireann. +teams.add_nonexistent_repo=Níl an stóras atá tú ag iarraidh a chur leis ann, cruthaigh é ar dtús. +teams.add_duplicate_users=Is ball foirne é an úsáideoir cheana féin. +teams.repos.none=Ní raibh rochtain ag an bhfoireann seo ar aon stóras. +teams.members.none=Níl aon bhaill ar an bhfoireann seo. +teams.members.blocked_user=Ní féidir an t-úsáideoir a chur leis toisc go bhfuil an eagraíocht bac air. +teams.specific_repositories=Stórais Sonrach +teams.specific_repositories_helper=Ní bheidh rochtain ag comhaltaí ach ar stórtha a cuireadh leis an bhfoireann go sainráite. <strong>Ní bhainfear</strong> na stórtha a cuireadh leis cheana le <i>Gach stóras</i> go huathoibríoch trí é seo a roghnú. +teams.all_repositories=Gach stórais +teams.all_repositories_helper=Tá rochtain ag an bhfoireann ar gach stórais. Má roghnaíonn sé seo, <strong>cuirfear na stórais go léir atá</strong> ann cheana leis an bhfoireann. +teams.all_repositories_read_permission_desc=Tugann an fhoireann seo rochtain do <strong>Léamh</strong> ar <strong>gach stórais</strong>: is féidir le baill amharc ar stórais agus iad a chlónáil. +teams.all_repositories_write_permission_desc=Tugann an fhoireann seo rochtain do <strong>Scríobh</strong> ar <strong>gach stórais</strong>: is féidir le baill léamh ó stórais agus iad a bhrú chucu. +teams.all_repositories_admin_permission_desc=Tugann an fhoireann seo rochtain <strong>Riarthóra</strong> ar <strong>gach stóras</strong>: is féidir le comhaltaí léamh, brú a dhéanamh agus comhoibritheoirí a chur le stórtha. +teams.invite.title=Tugadh cuireadh duit dul isteach i bhfoireann <strong>%s</strong> san eagraíocht <strong>%s</strong>. +teams.invite.by=Ar cuireadh ó %s +teams.invite.description=Cliceáil ar an gcnaipe thíos le do thoil chun dul isteach san fhoireann. [admin] +maintenance=Cothabháil +dashboard=Deais +self_check=Féin-sheiceáil +identity_access=Féiniúlacht & Rochtain +users=Cuntais Úsáideora +organizations=Eagraíochtaí +assets=Sócmhainní Cód +repositories=Stórais +hooks=Crúcaí Gréasán +integrations=Comhtháthaithe +authentication=Foinsí Fíordheimhnithe +emails=Ríomhphoist Úsáideoirí +config=Cumraíocht +config_summary=Achoimre +config_settings=Socruithe +notices=Fógraí Córais +monitor=Monatóireacht +first_page=Ar dtús +last_page=Deiridh +total=Iomlán: %d +settings=Socruithe Riaracháin +dashboard.new_version_hint=Tá Gitea %s ar fáil anois, tá %s á rith agat. Seiceáil <a target="_blank" rel="noreferrer" href="%s">an blag</a> le haghaidh tuilleadh sonraí. +dashboard.statistic=Achoimre +dashboard.maintenance_operations=Oibríochtaí Cothabháil +dashboard.system_status=Stádas an Chórais +dashboard.operation_name=Ainm Oibríochta +dashboard.operation_switch=Athraigh +dashboard.operation_run=Rith +dashboard.clean_unbind_oauth=Glan naisc OAuth neamhcheangailte +dashboard.clean_unbind_oauth_success=Scriosadh gach nasc OAuth neamhcheangailte. +dashboard.task.started=Tasc Tosaigh: %[1]s +dashboard.task.process=Tasc: %[1]s +dashboard.task.cancelled=Tasc: %[1]s cealaithe: %[3]s +dashboard.task.error=Earráid sa Tasc: %[1]s: %[3]s +dashboard.task.finished=Tasc: Tá %[1]s tosaithe ag %[2]s críochnaithe +dashboard.task.unknown=Tasc anaithnid: %[1]s +dashboard.cron.started=Cron tosaithe: %[1]s +dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %[1]s cealaithe: %[3]s +dashboard.cron.error=Earráid i gCron: %s: %[3]s +dashboard.cron.finished=Cron: %[1]s críochnaithe +dashboard.delete_inactive_accounts=Scrios gach cuntas neamhghníomhach +dashboard.delete_inactive_accounts.started=Tasc scriostha gach cuntas neamhghníomhachtaithe tosaithe. +dashboard.delete_repo_archives=Scrios gach cartlann stórais (ZIP, TAR.GZ, srl.) +dashboard.delete_repo_archives.started=Scrios gach tasc cartlann stórais a thosaigh. +dashboard.delete_missing_repos=Scrios gach stóras atá in easnamh ar a gcuid comhad Git +dashboard.delete_missing_repos.started=Scrios gach stóras atá in easnamh ar a dtasc comhaid Git a thosaigh. +dashboard.delete_generated_repository_avatars=Scrios abhatáranna stórtha ginte +dashboard.sync_repo_branches=Sync brainsí caillte ó shonraí git go bunachair sonraí +dashboard.sync_repo_tags=Clibeanna sioncraigh ó shonraí git go bunachar sonraí +dashboard.update_mirrors=Scátháin a nuashonrú dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo +users.full_name=Ainm Iomlán +users.list_status_filter.is_active=Gníomhach + + +orgs.teams=Foirne + +repos.owner=Úinéir + +packages.owner=Úinéir + +defaulthooks=Réamhshocraithe Crúcaí Gréasán +defaulthooks.desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibríoch chuig freastalaí nuair a chuireann imeachtaí áirithe Gitea tús. Is mainneachtainí iad na cuacha gréasáin a shainítear anseo agus déanfar iad a chóipeáil isteach i ngach stórais nua. Léigh tuilleadh sa <a target="_blank" rel="noopener" href="%s">treoir chúca Crúcaí Gréasán</a>. +defaulthooks.add_webhook=Cuir Crúca Gréasán Réamhshocraithe leis +defaulthooks.update_webhook=Nuashonraigh Réamhshocrú Crúca Gréasán + +systemhooks=Córas Crúcaí Gréasán +systemhooks.desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibríoch chuig freastalaí nuair a chuireann imeachtaí áirithe Gitea tús. Gníomhóidh na Crúcaí Gréasán atá sainithe anseo ar gach stóras ar an gcóras, mar sin déan machnamh ar aon impleachtaí feidhmíochta a d’fhéadfadh a bheith aige seo. Léigh tuilleadh sa <a target="_blank" rel="noopener" href="%s">treoir chúca gréasáin</a>. +systemhooks.add_webhook=Cuir Crúca Gréasán Córas leis +systemhooks.update_webhook=Nuashonraigh Córas Crúca Gréasán + +auths.updated=Nuashonraithe +auths.domain=Fearann + + + + + + +config.webhook_config=Cumraíocht Crúca Gréasán @@ -2171,22 +2931,13 @@ dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo +monitor.desc=Cur síos +monitor.queue.settings.submit=Nuashonrú Socruithe - - - - - - - - - - - - - +notices.system_notice_list=Fógraí Córais notices.operations=Oibríochtaí +notices.desc=Cur síos [action] @@ -2202,6 +2953,8 @@ notices.operations=Oibríochtaí [units] [packages] +alpine.repository.branches=Brainsí +alpine.repository.repositories=Stórais [secrets] @@ -2209,16 +2962,31 @@ notices.operations=Oibríochtaí +runners.description=Cur síos +runners.task_list.run=Rith runners.task_list.commit=Tiomantas +runners.status.active=Gníomhach +runners.reset_registration_token=Athshocraigh comhartha clár +runners.reset_registration_token_success=D'éirigh le hathshocrú comhartha clárúcháin an dara háit +runs.all_workflows=Gach Sreafaí Oibre runs.commit=Tiomantas +runs.scheduled=Sceidealaithe +runs.pushed_by=bhrú ag +runs.invalid_workflow_helper=Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s [projects] +type-3.display_name=Tionscadal Eagrúcháin [git.filemode] +changed_filemode=%[1]s → %[2]s ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +directory=Eolaire +normal_file=Comhad gnáth +executable_file=Comhad infheidhmithe symbolic_link=Nasc siombalach +submodule=Fo-mhodúl diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 6f788737ff5b..41531f7b3de7 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -580,6 +580,8 @@ lang_select_error=Escolha um idioma da lista. username_been_taken=O nome de utilizador já foi tomado. username_change_not_local_user=Utilizadores que não são locais não têm permissão para mudar o nome de utilizador. +change_username_disabled=Alterar o nome de utilizador está desabilitado. +change_full_name_disabled=Alterar o nome completo está desabilitado. username_has_not_been_changed=O nome de utilizador não foi modificado repo_name_been_taken=O nome do repositório já foi usado. repository_force_private=Forçar Privado está habilitado: repositórios privados não podem ser tornados públicos. @@ -705,6 +707,8 @@ public_profile=Perfil público biography_placeholder=Conte-nos um pouco sobre si! (Pode usar Markdown) location_placeholder=Partilhe a sua localização aproximada com outros profile_desc=Controle como o seu perfil é apresentado aos outros utilizadores. O seu endereço de email principal será usado para notificações, recuperação de senha e operações Git baseadas na web. +password_username_disabled=Não tem permissão para alterar os nomes de utilizador deles/delas. Entre em contacto com o administrador para saber mais detalhes. +password_full_name_disabled=Não tem permissão para alterar o nome completo deles/delas. Entre em contacto com o administrador para saber mais detalhes. full_name=Nome completo website=Sítio web location=Localização