diff --git a/.deadcode-out b/.deadcode-out index 9641f272b3..e63e4a3dc3 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -87,12 +87,24 @@ forgejo.org/modules/eventsource Event.String forgejo.org/modules/forgefed + NewForgeFollowFromAp + NewForgeFollow + ForgeFollow.MarshalJSON + ForgeFollow.UnmarshalJSON + ForgeFollow.Validate NewForgeUndoLike ForgeUndoLike.UnmarshalJSON ForgeUndoLike.Validate + NewForgeUserActivityFromAp + NewForgeUserActivity + ForgeUserActivity.Validate + NewPersonIDFromModel GetItemByType JSONUnmarshalerFn NotEmpty + NewForgeUserActivityNoteFromAp + newNote + ForgeUserActivityNote.Validate ToRepository OnRepository @@ -204,6 +216,7 @@ forgejo.org/modules/util/filebuffer forgejo.org/modules/validation IsErrNotValid + ValidateIDExists forgejo.org/modules/web RouteMock diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 0035ad9b2f..cddda0fa09 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -28,7 +28,7 @@ jobs: runs-on: docker container: - image: data.forgejo.org/renovate/renovate:40.11.19 + image: data.forgejo.org/renovate/renovate:40.40.0 steps: - name: Load renovate repo cache diff --git a/Makefile b/Makefile index 9cbf0f9e1c..759443456d 100644 --- a/Makefile +++ b/Makefile @@ -37,19 +37,18 @@ endif XGO_VERSION := go-1.21.x AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go -MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go -DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # renovate: datasource=go -GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.1 # renovate: datasource=go +DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go +GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@40.11.19 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +RENOVATE_NPM_PACKAGE ?= renovate@40.40.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... @@ -94,7 +93,7 @@ else # drop the "g" prefix prepended by git describe to the commit hash FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always 2>/dev/null | sed 's/^v//' | sed 's/\-g/-/') ifneq ($(FORGEJO_VERSION),) - ifneq ($(GITEA_COMPATIBILITY),) + ifeq ($(findstring $(GITEA_COMPATIBILITY),$(FORGEJO_VERSION)),) FORGEJO_VERSION := $(FORGEJO_VERSION)+$(GITEA_COMPATIBILITY) endif endif @@ -557,7 +556,7 @@ test-check: .PHONY: test\#% test\#%: - @echo "Running go test with -tags '$(TEST_TAGS)'..." + @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) .PHONY: coverage @@ -924,7 +923,6 @@ deps-tools: $(GO) install $(GOFUMPT_PACKAGE) $(GO) install $(GOLANGCI_LINT_PACKAGE) $(GO) install $(GXZ_PACKAGE) - $(GO) install $(MISSPELL_PACKAGE) $(GO) install $(SWAGGER_PACKAGE) $(GO) install $(XGO_PACKAGE) $(GO) install $(GO_LICENSES_PACKAGE) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index e222089dc5..d177bc5653 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -19,6 +19,11 @@ "path": "code.forgejo.org/forgejo-contrib/go-libravatar/LICENSE", "licenseText": "Copyright (c) 2016 Sandro Santilli \u003cstrk@kbt.io\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, + { + "name": "code.forgejo.org/forgejo/go-rpmutils", + "path": "code.forgejo.org/forgejo/go-rpmutils/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": "code.forgejo.org/forgejo/levelqueue", "path": "code.forgejo.org/forgejo/levelqueue/LICENSE", @@ -99,11 +104,6 @@ "path": "github.com/Azure/go-ntlmssp/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Microsoft\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, - { - "name": "github.com/DataDog/zstd", - "path": "github.com/DataDog/zstd/LICENSE", - "licenseText": "Simplified BSD License\n\nCopyright (c) 2016, Datadog \u003cinfo@datadoghq.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the copyright holder nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" - }, { "name": "github.com/ProtonMail/go-crypto", "path": "github.com/ProtonMail/go-crypto/LICENSE", @@ -894,11 +894,6 @@ "path": "github.com/santhosh-tekuri/jsonschema/v6/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." }, - { - "name": "github.com/sassoftware/go-rpmutils", - "path": "github.com/sassoftware/go-rpmutils/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/sergi/go-diff/diffmatchpatch", "path": "github.com/sergi/go-diff/diffmatchpatch/LICENSE", @@ -939,6 +934,11 @@ "path": "github.com/urfave/cli/v2/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "github.com/urfave/cli/v3", + "path": "github.com/urfave/cli/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + }, { "name": "github.com/valyala/fastjson", "path": "github.com/valyala/fastjson/LICENSE", diff --git a/build/lint-locale/lint-locale.go b/build/lint-locale/lint-locale.go index 0d80ffa4b0..dc4088c73c 100644 --- a/build/lint-locale/lint-locale.go +++ b/build/lint-locale/lint-locale.go @@ -52,7 +52,7 @@ func initBlueMondayPolicy() { policy.AllowAttrs("id").Matching(positionalPlaceholderRe).OnElements("code") // Allowed elements with no attributes. Must be a recognized tagname. - policy.AllowElements("strong", "br", "b", "strike", "code", "i") + policy.AllowElements("strong", "br", "b", "strike", "code", "i", "kbd") // TODO: Remove in `actions.workflow.dispatch.trigger_found`. policy.AllowNoAttrs().OnElements("c") diff --git a/cmd/actions.go b/cmd/actions.go index 54b5154cfa..12af2c8e86 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -4,25 +4,28 @@ package cmd import ( + "context" "fmt" "forgejo.org/modules/private" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - // CmdActions represents the available actions sub-commands. - CmdActions = &cli.Command{ +// CmdActions represents the available actions sub-commands. +func cmdActions() *cli.Command { + return &cli.Command{ Name: "actions", Usage: "Manage Forgejo Actions", - Subcommands: []*cli.Command{ - subcmdActionsGenRunnerToken, + Commands: []*cli.Command{ + subcmdActionsGenRunnerToken(), }, } +} - subcmdActionsGenRunnerToken = &cli.Command{ +func subcmdActionsGenRunnerToken() *cli.Command { + return &cli.Command{ Name: "generate-runner-token", Usage: "Generate a new token for a runner to use to register with the server", Action: runGenerateActionsRunnerToken, @@ -36,10 +39,10 @@ var ( }, }, } -) +} -func runGenerateActionsRunnerToken(c *cli.Context) error { - ctx, cancel := installSignals() +func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setting.MustInstalled() diff --git a/cmd/admin.go b/cmd/admin.go index e04a5bc530..7e06a99cda 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -15,56 +15,64 @@ import ( "forgejo.org/modules/log" repo_module "forgejo.org/modules/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - // CmdAdmin represents the available admin sub-command. - CmdAdmin = &cli.Command{ +// CmdAdmin represents the available admin sub-command. +func cmdAdmin() *cli.Command { + return &cli.Command{ Name: "admin", Usage: "Perform common administrative operations", - Subcommands: []*cli.Command{ - subcmdUser, - subcmdRepoSyncReleases, - subcmdRegenerate, - subcmdAuth, - subcmdSendMail, + Commands: []*cli.Command{ + subcmdUser(), + subcmdRepoSyncReleases(), + subcmdRegenerate(), + subcmdAuth(), + subcmdSendMail(), }, } +} - subcmdRepoSyncReleases = &cli.Command{ +func subcmdRepoSyncReleases() *cli.Command { + return &cli.Command{ Name: "repo-sync-releases", Usage: "Synchronize repository releases with tags", Action: runRepoSyncReleases, } +} - subcmdRegenerate = &cli.Command{ +func subcmdRegenerate() *cli.Command { + return &cli.Command{ Name: "regenerate", Usage: "Regenerate specific files", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdRegenHooks, microcmdRegenKeys, }, } +} - subcmdAuth = &cli.Command{ +func subcmdAuth() *cli.Command { + return &cli.Command{ Name: "auth", Usage: "Modify external auth providers", - Subcommands: []*cli.Command{ - microcmdAuthAddOauth, - microcmdAuthUpdateOauth, - microcmdAuthAddLdapBindDn, - microcmdAuthUpdateLdapBindDn, - microcmdAuthAddLdapSimpleAuth, - microcmdAuthUpdateLdapSimpleAuth, - microcmdAuthAddSMTP, - microcmdAuthUpdateSMTP, - microcmdAuthList, - microcmdAuthDelete, + Commands: []*cli.Command{ + microcmdAuthAddOauth(), + microcmdAuthUpdateOauth(), + microcmdAuthAddLdapBindDn(), + microcmdAuthUpdateLdapBindDn(), + microcmdAuthAddLdapSimpleAuth(), + microcmdAuthUpdateLdapSimpleAuth(), + microcmdAuthAddSMTP(), + microcmdAuthUpdateSMTP(), + microcmdAuthList(), + microcmdAuthDelete(), }, } +} - subcmdSendMail = &cli.Command{ +func subcmdSendMail() *cli.Command { + return &cli.Command{ Name: "sendmail", Usage: "Send a message to all users", Action: runSendMail, @@ -86,15 +94,17 @@ var ( }, }, } +} - idFlag = &cli.Int64Flag{ +func idFlag() *cli.Int64Flag { + return &cli.Int64Flag{ Name: "id", Usage: "ID of authentication source", } -) +} -func runRepoSyncReleases(_ *cli.Context) error { - ctx, cancel := installSignals() +func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index b5e0212df7..cb95b3b3c8 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -13,17 +14,20 @@ import ( "forgejo.org/models/db" auth_service "forgejo.org/services/auth" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - microcmdAuthDelete = &cli.Command{ +func microcmdAuthDelete() *cli.Command { + return &cli.Command{ Name: "delete", Usage: "Delete specific auth source", - Flags: []cli.Flag{idFlag}, + Flags: []cli.Flag{idFlag()}, Action: runDeleteAuth, } - microcmdAuthList = &cli.Command{ +} + +func microcmdAuthList() *cli.Command { + return &cli.Command{ Name: "list", Usage: "List auth sources", Action: runListAuth, @@ -54,10 +58,10 @@ var ( }, }, } -) +} -func runListAuth(c *cli.Context) error { - ctx, cancel := installSignals() +func runListAuth(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { @@ -81,7 +85,7 @@ func runListAuth(c *cli.Context) error { // loop through each source and print w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) - fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") + fmt.Fprint(w, "ID\tName\tType\tEnabled\n") for _, source := range authSources { fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) } @@ -90,12 +94,12 @@ func runListAuth(c *cli.Context) error { return nil } -func runDeleteAuth(c *cli.Context) error { +func runDeleteAuth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 637769b153..997d6b3a16 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -11,7 +11,7 @@ import ( "forgejo.org/models/auth" "forgejo.org/services/auth/source/ldap" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type ( @@ -23,8 +23,8 @@ type ( } ) -var ( - commonLdapCLIFlags = []cli.Flag{ +func commonLdapCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Usage: "Authentication name.", @@ -102,8 +102,10 @@ var ( Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", }, } +} - ldapBindDnCLIFlags = append(commonLdapCLIFlags, +func ldapBindDnCLIFlags() []cli.Flag { + return append(commonLdapCLIFlags(), &cli.StringFlag{ Name: "bind-dn", Usage: "The DN to bind to the LDAP server with when searching for the user.", @@ -128,49 +130,59 @@ var ( Name: "page-size", Usage: "Search page size.", }) +} - ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, +func ldapSimpleAuthCLIFlags() []cli.Flag { + return append(commonLdapCLIFlags(), &cli.StringFlag{ Name: "user-dn", Usage: "The user's DN.", }) +} - microcmdAuthAddLdapBindDn = &cli.Command{ +func microcmdAuthAddLdapBindDn() *cli.Command { + return &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapBindDn(c) + Action: func(ctx context.Context, cli *cli.Command) error { + return newAuthService().addLdapBindDn(ctx, cli) }, - Flags: ldapBindDnCLIFlags, + Flags: ldapBindDnCLIFlags(), } +} - microcmdAuthUpdateLdapBindDn = &cli.Command{ +func microcmdAuthUpdateLdapBindDn() *cli.Command { + return &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapBindDn(c) + Action: func(ctx context.Context, cli *cli.Command) error { + return newAuthService().updateLdapBindDn(ctx, cli) }, - Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), + Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...), } +} - microcmdAuthAddLdapSimpleAuth = &cli.Command{ +func microcmdAuthAddLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapSimpleAuth(c) + Action: func(ctx context.Context, cli *cli.Command) error { + return newAuthService().addLdapSimpleAuth(ctx, cli) }, - Flags: ldapSimpleAuthCLIFlags, + Flags: ldapSimpleAuthCLIFlags(), } +} - microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ +func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapSimpleAuth(c) + Action: func(ctx context.Context, cli *cli.Command) error { + return newAuthService().updateLdapSimpleAuth(ctx, cli) }, - Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), + Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...), } -) +} // newAuthService creates a service with default functions. func newAuthService() *authService { @@ -183,7 +195,7 @@ func newAuthService() *authService { } // parseAuthSource assigns values on authSource according to command line flags. -func parseAuthSource(c *cli.Context, authSource *auth.Source) { +func parseAuthSource(c *cli.Command, authSource *auth.Source) { if c.IsSet("name") { authSource.Name = c.String("name") } @@ -202,7 +214,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) { } // parseLdapConfig assigns values on config according to command line flags. -func parseLdapConfig(c *cli.Context, config *ldap.Source) error { +func parseLdapConfig(c *cli.Command, config *ldap.Source) error { if c.IsSet("name") { config.Name = c.String("name") } @@ -289,7 +301,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getAuthSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. -func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) { +func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { if err := argsSet(c, "id"); err != nil { return nil, err } @@ -307,12 +319,12 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp } // addLdapBindDn adds a new LDAP via Bind DN authentication source. -func (a *authService) addLdapBindDn(c *cli.Context) error { +func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { return err } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := a.initDB(ctx); err != nil { @@ -336,8 +348,8 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. -func (a *authService) updateLdapBindDn(c *cli.Context) error { - ctx, cancel := installSignals() +func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := a.initDB(ctx); err != nil { @@ -358,12 +370,12 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. -func (a *authService) addLdapSimpleAuth(c *cli.Context) error { +func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { return err } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := a.initDB(ctx); err != nil { @@ -387,8 +399,8 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { } // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. -func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { - ctx, cancel := installSignals() +func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := a.initDB(ctx); err != nil { diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 7ca0264454..89ce5f4f08 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -8,18 +8,17 @@ import ( "testing" "forgejo.org/models/auth" + "forgejo.org/modules/test" "forgejo.org/services/auth/source/ldap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestAddLdapBindDn(t *testing.T) { // Mock cli functions to do not exit on error - osExiter := cli.OsExiter - defer func() { cli.OsExiter = osExiter }() - cli.OsExiter = func(code int) {} + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases cases := []struct { @@ -226,12 +225,12 @@ func TestAddLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapBindDn.Flags + app := cli.Command{} + app.Flags = microcmdAuthAddLdapBindDn().Flags app.Action = service.addLdapBindDn // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -243,9 +242,7 @@ func TestAddLdapBindDn(t *testing.T) { func TestAddLdapSimpleAuth(t *testing.T) { // Mock cli functions to do not exit on error - osExiter := cli.OsExiter - defer func() { cli.OsExiter = osExiter }() - cli.OsExiter = func(code int) {} + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases cases := []struct { @@ -457,12 +454,12 @@ func TestAddLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapSimpleAuth.Flags + app := cli.Command{} + app.Flags = microcmdAuthAddLdapSimpleAuth().Flags app.Action = service.addLdapSimpleAuth // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -474,9 +471,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { func TestUpdateLdapBindDn(t *testing.T) { // Mock cli functions to do not exit on error - osExiter := cli.OsExiter - defer func() { cli.OsExiter = osExiter }() - cli.OsExiter = func(code int) {} + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases cases := []struct { @@ -920,12 +915,12 @@ func TestUpdateLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapBindDn.Flags + app := cli.Command{} + app.Flags = microcmdAuthUpdateLdapBindDn().Flags app.Action = service.updateLdapBindDn // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -937,9 +932,7 @@ func TestUpdateLdapBindDn(t *testing.T) { func TestUpdateLdapSimpleAuth(t *testing.T) { // Mock cli functions to do not exit on error - osExiter := cli.OsExiter - defer func() { cli.OsExiter = osExiter }() - cli.OsExiter = func(code int) {} + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases cases := []struct { @@ -1310,12 +1303,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags + app := cli.Command{} + app.Flags = microcmdAuthUpdateLdapSimpleAuth().Flags app.Action = service.updateLdapSimpleAuth // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index 58238794b8..abdcd5d48a 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "net/url" @@ -11,11 +12,11 @@ import ( auth_model "forgejo.org/models/auth" "forgejo.org/services/auth/source/oauth2" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - oauthCLIFlags = []cli.Flag{ +func oauthCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -120,23 +121,27 @@ var ( Usage: "Activate automatic team membership removal depending on groups", }, } +} - microcmdAuthAddOauth = &cli.Command{ +func microcmdAuthAddOauth() *cli.Command { + return &cli.Command{ Name: "add-oauth", Usage: "Add new Oauth authentication source", Action: runAddOauth, - Flags: oauthCLIFlags, + Flags: oauthCLIFlags(), } +} - microcmdAuthUpdateOauth = &cli.Command{ +func microcmdAuthUpdateOauth() *cli.Command { + return &cli.Command{ Name: "update-oauth", Usage: "Update existing Oauth authentication source", Action: runUpdateOauth, - Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), + Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...), } -) +} -func parseOAuth2Config(c *cli.Context) *oauth2.Source { +func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source { var customURLMapping *oauth2.CustomURLMapping if c.IsSet("use-custom-urls") { customURLMapping = &oauth2.CustomURLMapping{ @@ -168,15 +173,15 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source { } } -func runAddOauth(c *cli.Context) error { - ctx, cancel := installSignals() +func runAddOauth(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { return err } - config := parseOAuth2Config(c) + config := parseOAuth2Config(ctx, c) if config.Provider == "openidConnect" { discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL) if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { @@ -192,12 +197,12 @@ func runAddOauth(c *cli.Context) error { }) } -func runUpdateOauth(c *cli.Context) error { +func runUpdateOauth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go index e166cc38cd..48b3adaac3 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_stmp.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "strings" @@ -11,11 +12,11 @@ import ( "forgejo.org/modules/util" "forgejo.org/services/auth/source/smtp" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - smtpCLIFlags = []cli.Flag{ +func smtpCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -71,23 +72,27 @@ var ( Value: true, }, } +} - microcmdAuthAddSMTP = &cli.Command{ +func microcmdAuthAddSMTP() *cli.Command { + return &cli.Command{ Name: "add-smtp", Usage: "Add new SMTP authentication source", Action: runAddSMTP, - Flags: smtpCLIFlags, + Flags: smtpCLIFlags(), } +} - microcmdAuthUpdateSMTP = &cli.Command{ +func microcmdAuthUpdateSMTP() *cli.Command { + return &cli.Command{ Name: "update-smtp", Usage: "Update existing SMTP authentication source", Action: runUpdateSMTP, - Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), + Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...), } -) +} -func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { +func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { if c.IsSet("auth-type") { conf.Auth = c.String("auth-type") validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} @@ -123,8 +128,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { return nil } -func runAddSMTP(c *cli.Context) error { - ctx, cancel := installSignals() +func runAddSMTP(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { @@ -163,12 +168,12 @@ func runAddSMTP(c *cli.Context) error { }) } -func runUpdateSMTP(c *cli.Context) error { +func runUpdateSMTP(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index 4e7f685843..7bfd12f8f4 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -4,11 +4,13 @@ package cmd import ( + "context" + asymkey_model "forgejo.org/models/asymkey" "forgejo.org/modules/graceful" repo_service "forgejo.org/services/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -25,8 +27,8 @@ var ( } ) -func runRegenerateHooks(_ *cli.Context) error { - ctx, cancel := installSignals() +func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { @@ -35,8 +37,8 @@ func runRegenerateHooks(_ *cli.Context) error { return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } -func runRegenerateKeys(_ *cli.Context) error { - ctx, cancel := installSignals() +func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 967a6ed88a..f4f6fb49af 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -4,18 +4,21 @@ package cmd import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var subcmdUser = &cli.Command{ - Name: "user", - Usage: "Modify users", - Subcommands: []*cli.Command{ - microcmdUserCreate, - microcmdUserList, - microcmdUserChangePassword, - microcmdUserDelete, - microcmdUserGenerateAccessToken, - microcmdUserMustChangePassword, - }, +func subcmdUser() *cli.Command { + return &cli.Command{ + Name: "user", + Usage: "Modify users", + Commands: []*cli.Command{ + microcmdUserCreate(), + microcmdUserList(), + microcmdUserChangePassword(), + microcmdUserDelete(), + microcmdUserGenerateAccessToken(), + microcmdUserMustChangePassword(), + microcmdUserResetMFA(), + }, + } } diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index 563ad96afd..dd8c9d378a 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" @@ -13,40 +14,42 @@ import ( "forgejo.org/modules/setting" user_service "forgejo.org/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserChangePassword = &cli.Command{ - Name: "change-password", - Usage: "Change a user's password", - Action: runChangePassword, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "The user to change password for", +func microcmdUserChangePassword() *cli.Command { + return &cli.Command{ + Name: "change-password", + Usage: "Change a user's password", + Action: runChangePassword, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Value: "", + Usage: "The user to change password for", + }, + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Value: "", + Usage: "New password to set for user", + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "User must change password", + Value: true, + }, }, - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Value: "", - Usage: "New password to set for user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password", - Value: true, - }, - }, + } } -func runChangePassword(c *cli.Context) error { +func runChangePassword(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "username", "password"); err != nil { return err } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index f84254f39c..96431412f6 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -15,75 +16,76 @@ import ( "forgejo.org/modules/optional" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserCreate = &cli.Command{ - Name: "create", - Usage: "Create a new user in database", - Action: runCreateUser, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Usage: "Username. DEPRECATED: use username instead", +func microcmdUserCreate() *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "Create a new user in database", + Action: runCreateUser, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Usage: "Username. DEPRECATED: use username instead", + }, + &cli.StringFlag{ + Name: "username", + Usage: "Username", + }, + &cli.StringFlag{ + Name: "password", + Usage: "User password", + }, + &cli.StringFlag{ + Name: "email", + Usage: "User email address", + }, + &cli.BoolFlag{ + Name: "admin", + Usage: "User is an admin", + }, + &cli.BoolFlag{ + Name: "random-password", + Usage: "Generate a random password for the user", + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "Set this option to false to prevent forcing the user to change their password after initial login", + Value: true, + }, + &cli.IntFlag{ + Name: "random-password-length", + Usage: "Length of the random password to be generated", + Value: 12, + }, + &cli.BoolFlag{ + Name: "access-token", + Usage: "Generate access token for the user", + }, + &cli.StringFlag{ + Name: "access-token-name", + Usage: `Name of the generated access token`, + Value: "gitea-admin", + }, + &cli.StringFlag{ + Name: "access-token-scopes", + Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, + Value: "all", + }, + &cli.BoolFlag{ + Name: "restricted", + Usage: "Make a restricted user account", + }, + &cli.StringFlag{ + Name: "fullname", + Usage: `The full, human-readable name of the user`, + }, }, - &cli.StringFlag{ - Name: "username", - Usage: "Username", - }, - &cli.StringFlag{ - Name: "password", - Usage: "User password", - }, - &cli.StringFlag{ - Name: "email", - Usage: "User email address", - }, - &cli.BoolFlag{ - Name: "admin", - Usage: "User is an admin", - }, - &cli.BoolFlag{ - Name: "random-password", - Usage: "Generate a random password for the user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "Set this option to false to prevent forcing the user to change their password after initial login", - Value: true, - DisableDefaultText: true, - }, - &cli.IntFlag{ - Name: "random-password-length", - Usage: "Length of the random password to be generated", - Value: 12, - }, - &cli.BoolFlag{ - Name: "access-token", - Usage: "Generate access token for the user", - }, - &cli.StringFlag{ - Name: "access-token-name", - Usage: `Name of the generated access token`, - Value: "gitea-admin", - }, - &cli.StringFlag{ - Name: "access-token-scopes", - Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, - Value: "all", - }, - &cli.BoolFlag{ - Name: "restricted", - Usage: "Make a restricted user account", - }, - &cli.StringFlag{ - Name: "fullname", - Usage: `The full, human-readable name of the user`, - }, - }, + } } -func runCreateUser(c *cli.Context) error { +func runCreateUser(ctx context.Context, c *cli.Command) error { // this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. setting.LoadSettings() @@ -108,10 +110,10 @@ func runCreateUser(c *cli.Context) error { username = c.String("username") } else { username = c.String("name") - _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n") + _, _ = fmt.Fprint(c.Root().ErrWriter, "--name flag is deprecated. Use --username instead.\n") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 9a4cc22a77..3382c53e5f 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -12,41 +13,43 @@ import ( "forgejo.org/modules/storage" user_service "forgejo.org/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserDelete = &cli.Command{ - Name: "delete", - Usage: "Delete specific user by id, name or email", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "id", - Usage: "ID of user of the user to delete", +func microcmdUserDelete() *cli.Command { + return &cli.Command{ + Name: "delete", + Usage: "Delete specific user by id, name or email", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "id", + Usage: "ID of user of the user to delete", + }, + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "Username of the user to delete", + }, + &cli.StringFlag{ + Name: "email", + Aliases: []string{"e"}, + Usage: "Email of the user to delete", + }, + &cli.BoolFlag{ + Name: "purge", + Usage: "Purge user, all their repositories, organizations and comments", + }, }, - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Usage: "Username of the user to delete", - }, - &cli.StringFlag{ - Name: "email", - Aliases: []string{"e"}, - Usage: "Email of the user to delete", - }, - &cli.BoolFlag{ - Name: "purge", - Usage: "Purge user, all their repositories, organizations and comments", - }, - }, - Action: runDeleteUser, + Action: runDeleteUser, + } } -func runDeleteUser(c *cli.Context) error { +func runDeleteUser(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 1a6c003171..d0f2878297 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -4,49 +4,52 @@ package cmd import ( + "context" "errors" "fmt" auth_model "forgejo.org/models/auth" user_model "forgejo.org/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserGenerateAccessToken = &cli.Command{ - Name: "generate-access-token", - Usage: "Generate an access token for a specific user", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Usage: "Username", +func microcmdUserGenerateAccessToken() *cli.Command { + return &cli.Command{ + Name: "generate-access-token", + Usage: "Generate an access token for a specific user", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "Username", + }, + &cli.StringFlag{ + Name: "token-name", + Aliases: []string{"t"}, + Usage: "Token name", + Value: "gitea-admin", + }, + &cli.BoolFlag{ + Name: "raw", + Usage: "Display only the token value", + }, + &cli.StringFlag{ + Name: "scopes", + Value: "all", + Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, + }, }, - &cli.StringFlag{ - Name: "token-name", - Aliases: []string{"t"}, - Usage: "Token name", - Value: "gitea-admin", - }, - &cli.BoolFlag{ - Name: "raw", - Usage: "Display only the token value", - }, - &cli.StringFlag{ - Name: "scopes", - Value: "all", - Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, - }, - }, - Action: runGenerateAccessToken, + Action: runGenerateAccessToken, + } } -func runGenerateAccessToken(c *cli.Context) error { +func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("you must provide a username to generate a token for") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index 6044ce7c3f..ccc4b8c917 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -4,29 +4,32 @@ package cmd import ( + "context" "fmt" "os" "text/tabwriter" user_model "forgejo.org/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserList = &cli.Command{ - Name: "list", - Usage: "List users", - Action: runListUsers, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "admin", - Usage: "List only admin users", +func microcmdUserList() *cli.Command { + return &cli.Command{ + Name: "list", + Usage: "List users", + Action: runListUsers, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "admin", + Usage: "List only admin users", + }, }, - }, + } } -func runListUsers(c *cli.Context) error { - ctx, cancel := installSignals() +func runListUsers(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if err := initDB(ctx); err != nil { @@ -41,7 +44,7 @@ func runListUsers(c *cli.Context) error { w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0) if c.IsSet("admin") { - fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n") + fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\n") for _, u := range users { if u.IsAdmin { fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive) @@ -49,7 +52,7 @@ func runListUsers(c *cli.Context) error { } } else { twofa := user_model.UserList(users).GetTwoFaStatus(ctx) - fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") + fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") for _, u := range users { fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID]) } diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go index 920f5c7b21..2ccad56eb9 100644 --- a/cmd/admin_user_must_change_password.go +++ b/cmd/admin_user_must_change_password.go @@ -4,38 +4,41 @@ package cmd import ( + "context" "errors" "fmt" user_model "forgejo.org/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserMustChangePassword = &cli.Command{ - Name: "must-change-password", - Usage: "Set the must change password flag for the provided users or all users", - Action: runMustChangePassword, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "all", - Aliases: []string{"A"}, - Usage: "All users must change password, except those explicitly excluded with --exclude", +func microcmdUserMustChangePassword() *cli.Command { + return &cli.Command{ + Name: "must-change-password", + Usage: "Set the must change password flag for the provided users or all users", + Action: runMustChangePassword, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Aliases: []string{"A"}, + Usage: "All users must change password, except those explicitly excluded with --exclude", + }, + &cli.StringSliceFlag{ + Name: "exclude", + Aliases: []string{"e"}, + Usage: "Do not change the must-change-password flag for these users", + }, + &cli.BoolFlag{ + Name: "unset", + Usage: "Instead of setting the must-change-password flag, unset it", + }, }, - &cli.StringSliceFlag{ - Name: "exclude", - Aliases: []string{"e"}, - Usage: "Do not change the must-change-password flag for these users", - }, - &cli.BoolFlag{ - Name: "unset", - Usage: "Instead of setting the must-change-password flag, unset it", - }, - }, + } } -func runMustChangePassword(c *cli.Context) error { - ctx, cancel := installSignals() +func runMustChangePassword(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() if c.NArg() == 0 && !c.IsSet("all") { diff --git a/cmd/admin_user_reset_mfa.go b/cmd/admin_user_reset_mfa.go new file mode 100644 index 0000000000..8107fd97bf --- /dev/null +++ b/cmd/admin_user_reset_mfa.go @@ -0,0 +1,73 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "fmt" + + auth_model "forgejo.org/models/auth" + user_model "forgejo.org/models/user" + + "github.com/urfave/cli/v3" +) + +func microcmdUserResetMFA() *cli.Command { + return &cli.Command{ + Name: "reset-mfa", + Usage: "Remove all two-factor authentication configurations for a user", + Action: runResetMFA, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Value: "", + Usage: "The user to update", + }, + }, + } +} + +func runResetMFA(ctx context.Context, c *cli.Command) error { + if err := argsSet(c, "username"); err != nil { + return err + } + + ctx, cancel := installSignals(ctx) + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + user, err := user_model.GetUserByName(ctx, c.String("username")) + if err != nil { + return err + } + + webAuthnList, err := auth_model.GetWebAuthnCredentialsByUID(ctx, user.ID) + if err != nil { + return err + } + + for _, credential := range webAuthnList { + if _, err := auth_model.DeleteCredential(ctx, credential.ID, user.ID); err != nil { + return err + } + } + + tfaModes, err := auth_model.GetTwoFactorByUID(ctx, user.ID) + if err == nil && tfaModes != nil { + if err := auth_model.DeleteTwoFactorByID(ctx, tfaModes.ID, user.ID); err != nil { + return err + } + } else { + if _, is := err.(auth_model.ErrTwoFactorNotEnrolled); !is { + return err + } + } + + fmt.Printf("%s's two-factor authentication settings have been removed!\n", user.Name) + return nil +} diff --git a/cmd/cert.go b/cmd/cert.go index bf83af389f..f9e3a16f3e 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -6,6 +6,7 @@ package cmd import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -20,47 +21,49 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdCert represents the available cert sub-command. -var CmdCert = &cli.Command{ - Name: "cert", - Usage: "Generate self-signed certificate", - Description: `Generate a self-signed X.509 certificate for a TLS server. +func cmdCert() *cli.Command { + return &cli.Command{ + Name: "cert", + Usage: "Generate self-signed certificate", + Description: `Generate a self-signed X.509 certificate for a TLS server. Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, - Action: runCert, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "host", - Value: "", - Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Action: runCert, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "host", + Value: "", + Usage: "Comma-separated hostnames and IPs to generate a certificate for", + }, + &cli.StringFlag{ + Name: "ecdsa-curve", + Value: "", + Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", + }, + &cli.IntFlag{ + Name: "rsa-bits", + Value: 3072, + Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", + }, + &cli.StringFlag{ + Name: "start-date", + Value: "", + Usage: "Creation date formatted as Jan 1 15:04:05 2011", + }, + &cli.DurationFlag{ + Name: "duration", + Value: 365 * 24 * time.Hour, + Usage: "Duration that certificate is valid for", + }, + &cli.BoolFlag{ + Name: "ca", + Usage: "whether this cert should be its own Certificate Authority", + }, }, - &cli.StringFlag{ - Name: "ecdsa-curve", - Value: "", - Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", - }, - &cli.IntFlag{ - Name: "rsa-bits", - Value: 3072, - Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", - }, - &cli.StringFlag{ - Name: "start-date", - Value: "", - Usage: "Creation date formatted as Jan 1 15:04:05 2011", - }, - &cli.DurationFlag{ - Name: "duration", - Value: 365 * 24 * time.Hour, - Usage: "Duration that certificate is valid for", - }, - &cli.BoolFlag{ - Name: "ca", - Usage: "whether this cert should be its own Certificate Authority", - }, - }, + } } func publicKey(priv any) any { @@ -89,7 +92,7 @@ func pemBlockForKey(priv any) *pem.Block { } } -func runCert(c *cli.Context) error { +func runCert(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "host"); err != nil { return err } diff --git a/cmd/cmd.go b/cmd/cmd.go index c887d0ed52..85a482b78c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -20,19 +20,21 @@ import ( "forgejo.org/modules/setting" "forgejo.org/modules/util" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // argsSet checks that all the required arguments are set. args is a list of // arguments that must be set in the passed Context. -func argsSet(c *cli.Context, args ...string) error { +func argsSet(c *cli.Command, args ...string) error { for _, a := range args { if !c.IsSet(a) { return errors.New(a + " is not set") } - if util.IsEmptyString(c.String(a)) { - return errors.New(a + " is required") + if s, ok := c.Value(a).(string); ok { + if util.IsEmptyString(s) { + return errors.New(a + " is required") + } } } return nil @@ -73,8 +75,8 @@ If this is the intended configuration file complete the [database] section.`, se return nil } -func installSignals() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) +func installSignals(ctx context.Context) (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(ctx) go func() { // install notify signalChannel := make(chan os.Signal, 1) @@ -109,7 +111,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) } -func globalBool(c *cli.Context, name string) bool { +func globalBool(c *cli.Command, name string) bool { for _, ctx := range c.Lineage() { if ctx.Bool(name) { return true @@ -120,16 +122,16 @@ func globalBool(c *cli.Context, name string) bool { // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. -func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error { - return func(c *cli.Context) error { +func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, cli *cli.Command) (context.Context, error) { + return func(ctx context.Context, cli *cli.Command) (context.Context, error) { level := defaultLevel - if globalBool(c, "quiet") { + if globalBool(cli, "quiet") { level = log.FATAL } - if globalBool(c, "debug") || globalBool(c, "verbose") { + if globalBool(cli, "debug") || globalBool(cli, "verbose") { level = log.TRACE } log.SetConsoleLogger(log.DEFAULT, "console-default", level) - return nil + return ctx, nil } } diff --git a/cmd/docs.go b/cmd/docs.go deleted file mode 100644 index 1dc0980c00..0000000000 --- a/cmd/docs.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/urfave/cli/v2" -) - -// CmdDocs represents the available docs sub-command. -var CmdDocs = &cli.Command{ - Name: "docs", - Usage: "Output CLI documentation", - Description: "A command to output Forgejo's CLI documentation, optionally to a file.", - Action: runDocs, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "man", - Usage: "Output man pages instead", - }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Usage: "Path to output to instead of stdout (will overwrite if exists)", - }, - }, -} - -func runDocs(ctx *cli.Context) error { - docs, err := ctx.App.ToMarkdown() - if ctx.Bool("man") { - docs, err = ctx.App.ToMan() - } - if err != nil { - return err - } - - if !ctx.Bool("man") { - // Clean up markdown. The following bug was fixed in v2, but is present in v1. - // It affects markdown output (even though the issue is referring to man pages) - // https://github.com/urfave/cli/issues/1040 - firstHashtagIndex := strings.Index(docs, "#") - - if firstHashtagIndex > 0 { - docs = docs[firstHashtagIndex:] - } - } - - out := os.Stdout - if ctx.String("output") != "" { - fi, err := os.Create(ctx.String("output")) - if err != nil { - return err - } - defer fi.Close() - out = fi - } - - _, err = fmt.Fprintln(out, docs) - return err -} diff --git a/cmd/doctor.go b/cmd/doctor.go index 3807623ccd..681794f094 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "fmt" golog "log" "os" @@ -19,80 +20,86 @@ import ( "forgejo.org/modules/setting" "forgejo.org/services/doctor" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDoctor represents the available doctor sub-command. -var CmdDoctor = &cli.Command{ - Name: "doctor", - Usage: "Diagnose and optionally fix problems, convert or re-create database tables", - Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", +func cmdDoctor() *cli.Command { + return &cli.Command{ + Name: "doctor", + Usage: "Diagnose and optionally fix problems, convert or re-create database tables", + Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - Subcommands: []*cli.Command{ - cmdDoctorCheck, - cmdRecreateTable, - cmdDoctorConvert, - }, + Commands: []*cli.Command{ + cmdDoctorCheck(), + cmdRecreateTable(), + cmdDoctorConvert(), + }, + } } -var cmdDoctorCheck = &cli.Command{ - Name: "check", - Usage: "Diagnose and optionally fix problems", - Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - Action: runDoctorCheck, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "list", - Usage: "List the available checks", +func cmdDoctorCheck() *cli.Command { + return &cli.Command{ + Name: "check", + Usage: "Diagnose and optionally fix problems", + Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", + Action: runDoctorCheck, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "list", + Usage: "List the available checks", + }, + &cli.BoolFlag{ + Name: "default", + Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", + }, + &cli.StringSliceFlag{ + Name: "run", + Usage: "Run the provided checks - (if --default is set, the default checks will also run)", + }, + &cli.BoolFlag{ + Name: "all", + Usage: "Run all the available checks", + }, + &cli.BoolFlag{ + Name: "fix", + Usage: "Automatically fix what we can", + }, + &cli.StringFlag{ + Name: "log-file", + Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`, + }, + &cli.BoolFlag{ + Name: "color", + Aliases: []string{"H"}, + Usage: "Use color for outputted information", + }, }, - &cli.BoolFlag{ - Name: "default", - Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", - }, - &cli.StringSliceFlag{ - Name: "run", - Usage: "Run the provided checks - (if --default is set, the default checks will also run)", - }, - &cli.BoolFlag{ - Name: "all", - Usage: "Run all the available checks", - }, - &cli.BoolFlag{ - Name: "fix", - Usage: "Automatically fix what we can", - }, - &cli.StringFlag{ - Name: "log-file", - Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`, - }, - &cli.BoolFlag{ - Name: "color", - Aliases: []string{"H"}, - Usage: "Use color for outputted information", - }, - }, + } } -var cmdRecreateTable = &cli.Command{ - Name: "recreate-table", - Usage: "Recreate tables from XORM definitions and copy the data.", - ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "debug", - Usage: "Print SQL commands sent", +func cmdRecreateTable() *cli.Command { + return &cli.Command{ + Name: "recreate-table", + Usage: "Recreate tables from XORM definitions and copy the data.", + ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Usage: "Print SQL commands sent", + }, }, - }, - Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns. + Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns. This command will cause Xorm to recreate tables, copying over the data and deleting the old table. You should back-up your database before doing this and ensure that your database is up-to-date first.`, - Action: runRecreateTable, + Action: runRecreateTable, + } } -func runRecreateTable(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() // Redirect the default golog to here @@ -143,7 +150,7 @@ func runRecreateTable(ctx *cli.Context) error { }) } -func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { +func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) { // Silence the default loggers setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) @@ -165,8 +172,8 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { } } -func runDoctorCheck(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() colorize := log.CanColorStdout diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 06883264a3..44bebae154 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -4,25 +4,28 @@ package cmd import ( + "context" "fmt" "forgejo.org/models/db" "forgejo.org/modules/log" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdDoctorConvert represents the available convert sub-command. -var cmdDoctorConvert = &cli.Command{ - Name: "convert", - Usage: "Convert the database", - Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", - Action: runDoctorConvert, +func cmdDoctorConvert() *cli.Command { + return &cli.Command{ + Name: "convert", + Usage: "Convert the database", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Action: runDoctorConvert, + } } -func runDoctorConvert(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runDoctorConvert(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() if err := initDB(stdCtx); err != nil { diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index bfb4b9d803..c3eda8315b 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -11,7 +11,7 @@ import ( "forgejo.org/services/doctor" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestDoctorRun(t *testing.T) { @@ -22,12 +22,12 @@ func TestDoctorRun(t *testing.T) { SkipDatabaseInitialization: true, }) - app := cli.NewApp() - app.Commands = []*cli.Command{cmdDoctorCheck} - err := app.Run([]string{"./gitea", "check", "--run", "test-check"}) + app := cli.Command{} + app.Commands = []*cli.Command{cmdDoctorCheck()} + err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) require.NoError(t, err) - err = app.Run([]string{"./gitea", "check", "--run", "no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) require.ErrorContains(t, err, `unknown checks: "no-such"`) - err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) require.ErrorContains(t, err, `unknown checks: "no-such"`) } diff --git a/cmd/dump.go b/cmd/dump.go index bc0b269924..cb01e74196 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -5,6 +5,8 @@ package cmd import ( + "context" + "errors" "fmt" "io" "os" @@ -22,7 +24,7 @@ import ( "code.forgejo.org/go-chi/session" "github.com/mholt/archiver/v3" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { @@ -83,6 +85,10 @@ func (o *outputType) Set(value string) error { return fmt.Errorf("allowed values are %s", o.Join()) } +func (o *outputType) Get() any { + return o.String() +} + func (o outputType) String() string { if o.selected == "" { return o.Default @@ -96,79 +102,81 @@ var outputTypeEnum = &outputType{ } // CmdDump represents the available dump sub-command. -var CmdDump = &cli.Command{ - Name: "dump", - Usage: "Dump Forgejo files and database", - Description: `Dump compresses all related files and database into zip file. +func cmdDump() *cli.Command { + return &cli.Command{ + Name: "dump", + Usage: "Dump Forgejo files and database", + Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Forgejo server image to send to maintainer`, - Action: runDump, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "file", - Aliases: []string{"f"}, - Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()), - Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.", + Action: runDump, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()), + Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.", + }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"V"}, + Usage: "Show process details", + }, + &cli.BoolFlag{ + Name: "quiet", + Aliases: []string{"q"}, + Usage: "Only display warnings and errors", + }, + &cli.StringFlag{ + Name: "tempdir", + Aliases: []string{"t"}, + Usage: "Temporary dir path", + }, + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres", + }, + &cli.BoolFlag{ + Name: "skip-repository", + Aliases: []string{"R"}, + Usage: "Skip repositories", + }, + &cli.BoolFlag{ + Name: "skip-log", + Aliases: []string{"L"}, + Usage: "Skip logs", + }, + &cli.BoolFlag{ + Name: "skip-custom-dir", + Usage: "Skip custom directory", + }, + &cli.BoolFlag{ + Name: "skip-lfs-data", + Usage: "Skip LFS data", + }, + &cli.BoolFlag{ + Name: "skip-attachment-data", + Usage: "Skip attachment data", + }, + &cli.BoolFlag{ + Name: "skip-package-data", + Usage: "Skip package data", + }, + &cli.BoolFlag{ + Name: "skip-index", + Usage: "Skip bleve index data", + }, + &cli.BoolFlag{ + Name: "skip-repo-archives", + Usage: "Skip repository archives", + }, + &cli.GenericFlag{ + Name: "type", + Value: outputTypeEnum, + Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), + }, }, - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"V"}, - Usage: "Show process details", - }, - &cli.BoolFlag{ - Name: "quiet", - Aliases: []string{"q"}, - Usage: "Only display warnings and errors", - }, - &cli.StringFlag{ - Name: "tempdir", - Aliases: []string{"t"}, - Usage: "Temporary dir path", - }, - &cli.StringFlag{ - Name: "database", - Aliases: []string{"d"}, - Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres", - }, - &cli.BoolFlag{ - Name: "skip-repository", - Aliases: []string{"R"}, - Usage: "Skip repositories", - }, - &cli.BoolFlag{ - Name: "skip-log", - Aliases: []string{"L"}, - Usage: "Skip logs", - }, - &cli.BoolFlag{ - Name: "skip-custom-dir", - Usage: "Skip custom directory", - }, - &cli.BoolFlag{ - Name: "skip-lfs-data", - Usage: "Skip LFS data", - }, - &cli.BoolFlag{ - Name: "skip-attachment-data", - Usage: "Skip attachment data", - }, - &cli.BoolFlag{ - Name: "skip-package-data", - Usage: "Skip package data", - }, - &cli.BoolFlag{ - Name: "skip-index", - Usage: "Skip bleve index data", - }, - &cli.BoolFlag{ - Name: "skip-repo-archives", - Usage: "Skip repository archives", - }, - &cli.GenericFlag{ - Name: "type", - Value: outputTypeEnum, - Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), - }, - }, + } } func fatal(format string, args ...any) { @@ -176,7 +184,7 @@ func fatal(format string, args ...any) { log.Fatal(format, args...) } -func runDump(ctx *cli.Context) error { +func runDump(stdCtx context.Context, ctx *cli.Command) error { var file *os.File fileName := ctx.String("file") outType := ctx.String("type") @@ -212,16 +220,16 @@ func runDump(ctx *cli.Context) error { if !setting.InstallLock { log.Error("Is '%s' really the right config path?\n", setting.CustomConf) - return fmt.Errorf("forgejo is not initialized") + return errors.New("forgejo is not initialized") } setting.LoadSettings() // cannot access session settings otherwise verbose := ctx.Bool("verbose") if verbose && ctx.Bool("quiet") { - return fmt.Errorf("--quiet and --verbose cannot both be set") + return errors.New("--quiet and --verbose cannot both be set") } - stdCtx, cancel := installSignals() + stdCtx, cancel := installSignals(stdCtx) defer cancel() err := db.InitEngine(stdCtx) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 197445f908..eb89273e7f 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -19,68 +19,70 @@ import ( "forgejo.org/services/convert" "forgejo.org/services/migrations" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDumpRepository represents the available dump repository sub-command. -var CmdDumpRepository = &cli.Command{ - Name: "dump-repo", - Usage: "Dump the repository from git/github/gitea/gitlab", - Description: "This is a command for dumping the repository data.", - Action: runDumpRepository, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "git_service", - Value: "", - Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", - }, - &cli.StringFlag{ - Name: "repo_dir", - Aliases: []string{"r"}, - Value: "./data", - Usage: "Repository dir path to store the data", - }, - &cli.StringFlag{ - Name: "clone_addr", - Value: "", - Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", - }, - &cli.StringFlag{ - Name: "auth_username", - Value: "", - Usage: "The username to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "auth_password", - Value: "", - Usage: "The password to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "auth_token", - Value: "", - Usage: "The personal token to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "owner_name", - Value: "", - Usage: "The data will be stored on a directory with owner name if not empty", - }, - &cli.StringFlag{ - Name: "repo_name", - Value: "", - Usage: "The data will be stored on a directory with repository name if not empty", - }, - &cli.StringFlag{ - Name: "units", - Value: "", - Usage: `Which items will be migrated, one or more units should be separated as comma. +func cmdDumpRepository() *cli.Command { + return &cli.Command{ + Name: "dump-repo", + Usage: "Dump the repository from git/github/gitea/gitlab", + Description: "This is a command for dumping the repository data.", + Action: runDumpRepository, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "git_service", + Value: "", + Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", + }, + &cli.StringFlag{ + Name: "repo_dir", + Aliases: []string{"r"}, + Value: "./data", + Usage: "Repository dir path to store the data", + }, + &cli.StringFlag{ + Name: "clone_addr", + Value: "", + Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", + }, + &cli.StringFlag{ + Name: "auth_username", + Value: "", + Usage: "The username to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "auth_password", + Value: "", + Usage: "The password to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "auth_token", + Value: "", + Usage: "The personal token to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "owner_name", + Value: "", + Usage: "The data will be stored on a directory with owner name if not empty", + }, + &cli.StringFlag{ + Name: "repo_name", + Value: "", + Usage: "The data will be stored on a directory with repository name if not empty", + }, + &cli.StringFlag{ + Name: "units", + Value: "", + Usage: `Which items will be migrated, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, + }, }, - }, + } } -func runDumpRepository(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() if err := initDB(stdCtx); err != nil { diff --git a/cmd/embedded.go b/cmd/embedded.go index c8b9d13d25..8fa76ccef1 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -19,23 +20,25 @@ import ( "forgejo.org/modules/util" "github.com/gobwas/glob" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdEmbedded represents the available extract sub-command. -var ( - CmdEmbedded = &cli.Command{ +func cmdEmbedded() *cli.Command { + return &cli.Command{ Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", - Subcommands: []*cli.Command{ - subcmdList, - subcmdView, - subcmdExtract, + Commands: []*cli.Command{ + subcmdList(), + subcmdView(), + subcmdExtract(), }, } +} - subcmdList = &cli.Command{ +func subcmdList() *cli.Command { + return &cli.Command{ Name: "list", Usage: "List files matching the given pattern", Action: runList, @@ -47,8 +50,10 @@ var ( }, }, } +} - subcmdView = &cli.Command{ +func subcmdView() *cli.Command { + return &cli.Command{ Name: "view", Usage: "View a file matching the given pattern", Action: runView, @@ -60,8 +65,10 @@ var ( }, }, } +} - subcmdExtract = &cli.Command{ +func subcmdExtract() *cli.Command { + return &cli.Command{ Name: "extract", Usage: "Extract resources", Action: runExtract, @@ -90,9 +97,9 @@ var ( }, }, } +} - matchedAssetFiles []assetFile -) +var matchedAssetFiles []assetFile type assetFile struct { fs *assetfs.LayeredFS @@ -100,7 +107,7 @@ type assetFile struct { path string } -func initEmbeddedExtractor(c *cli.Context) error { +func initEmbeddedExtractor(_ context.Context, c *cli.Command) error { setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) patterns, err := compileCollectPatterns(c.Args().Slice()) @@ -115,32 +122,32 @@ func initEmbeddedExtractor(c *cli.Context) error { return nil } -func runList(c *cli.Context) error { - if err := runListDo(c); err != nil { +func runList(ctx context.Context, c *cli.Command) error { + if err := runListDo(ctx, c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runView(c *cli.Context) error { - if err := runViewDo(c); err != nil { +func runView(ctx context.Context, c *cli.Command) error { + if err := runViewDo(ctx, c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runExtract(c *cli.Context) error { - if err := runExtractDo(c); err != nil { +func runExtract(ctx context.Context, c *cli.Command) error { + if err := runExtractDo(ctx, c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runListDo(c *cli.Context) error { - if err := initEmbeddedExtractor(c); err != nil { +func runListDo(ctx context.Context, c *cli.Command) error { + if err := initEmbeddedExtractor(ctx, c); err != nil { return err } @@ -151,8 +158,8 @@ func runListDo(c *cli.Context) error { return nil } -func runViewDo(c *cli.Context) error { - if err := initEmbeddedExtractor(c); err != nil { +func runViewDo(ctx context.Context, c *cli.Command) error { + if err := initEmbeddedExtractor(ctx, c); err != nil { return err } @@ -174,8 +181,8 @@ func runViewDo(c *cli.Context) error { return nil } -func runExtractDo(c *cli.Context) error { - if err := initEmbeddedExtractor(c); err != nil { +func runExtractDo(ctx context.Context, c *cli.Command) error { + if err := initEmbeddedExtractor(ctx, c); err != nil { return err } @@ -271,7 +278,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error { return nil } -func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) { +func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { fs := assetfs.Layered(layer) files, err := fs.ListAllFiles(".", true) if err != nil { diff --git a/cmd/forgejo/actions.go b/cmd/forgejo/actions.go index dbe7398bcf..c445d1aa38 100644 --- a/cmd/forgejo/actions.go +++ b/cmd/forgejo/actions.go @@ -6,6 +6,7 @@ package forgejo import ( "context" "encoding/hex" + "errors" "fmt" "io" "os" @@ -16,14 +17,14 @@ import ( "forgejo.org/modules/setting" private_routers "forgejo.org/routers/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func CmdActions(ctx context.Context) *cli.Command { return &cli.Command{ Name: "actions", Usage: "Commands for managing Forgejo Actions", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ SubcmdActionsGenerateRunnerToken(ctx), SubcmdActionsGenerateRunnerSecret(ctx), SubcmdActionsRegister(ctx), @@ -36,7 +37,7 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command { Name: "generate-runner-token", Usage: "Generate a new token for a runner to use to register with the server", Before: prepareWorkPathAndCustomConf(ctx), - Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) }, + Action: RunGenerateActionsRunnerToken, Flags: []cli.Flag{ &cli.StringFlag{ Name: "scope", @@ -52,7 +53,7 @@ func SubcmdActionsGenerateRunnerSecret(ctx context.Context) *cli.Command { return &cli.Command{ Name: "generate-secret", Usage: "Generate a secret suitable for input to the register subcommand", - Action: func(cliCtx *cli.Context) error { return RunGenerateSecret(ctx, cliCtx) }, + Action: RunGenerateSecret, } } @@ -61,7 +62,7 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command { Name: "register", Usage: "Idempotent registration of a runner using a shared secret", Before: prepareWorkPathAndCustomConf(ctx), - Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) }, + Action: RunRegister, Flags: []cli.Flag{ &cli.StringFlag{ Name: "secret", @@ -105,26 +106,26 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command { } } -func readSecret(ctx context.Context, cliCtx *cli.Context) (string, error) { - if cliCtx.IsSet("secret") { - return cliCtx.String("secret"), nil +func readSecret(ctx context.Context, cli *cli.Command) (string, error) { + if cli.IsSet("secret") { + return cli.String("secret"), nil } - if cliCtx.IsSet("secret-stdin") { + if cli.IsSet("secret-stdin") { buf, err := io.ReadAll(ContextGetStdin(ctx)) if err != nil { return "", err } return string(buf), nil } - if cliCtx.IsSet("secret-file") { - path := cliCtx.String("secret-file") + if cli.IsSet("secret-file") { + path := cli.String("secret-file") buf, err := os.ReadFile(path) if err != nil { return "", err } return string(buf), nil } - return "", fmt.Errorf("at least one of the --secret, --secret-stdin, --secret-file options is required") + return "", errors.New("at least one of the --secret, --secret-stdin, --secret-file options is required") } func validateSecret(secret string) error { @@ -138,18 +139,18 @@ func validateSecret(secret string) error { return nil } -func getLabels(cliCtx *cli.Context) (*[]string, error) { - if !cliCtx.Bool("keep-labels") { - lblValue := strings.Split(cliCtx.String("labels"), ",") +func getLabels(cli *cli.Command) (*[]string, error) { + if !cli.Bool("keep-labels") { + lblValue := strings.Split(cli.String("labels"), ",") return &lblValue, nil } - if cliCtx.String("labels") != "" { - return nil, fmt.Errorf("--labels and --keep-labels should not be used together") + if cli.String("labels") != "" { + return nil, errors.New("--labels and --keep-labels should not be used together") } return nil, nil } -func RunRegister(ctx context.Context, cliCtx *cli.Context) error { +func RunRegister(ctx context.Context, cli *cli.Command) error { var cancel context.CancelFunc if !ContextGetNoInit(ctx) { ctx, cancel = installSignals(ctx) @@ -161,17 +162,17 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error { } setting.MustInstalled() - secret, err := readSecret(ctx, cliCtx) + secret, err := readSecret(ctx, cli) if err != nil { return err } if err := validateSecret(secret); err != nil { return err } - scope := cliCtx.String("scope") - name := cliCtx.String("name") - version := cliCtx.String("version") - labels, err := getLabels(cliCtx) + scope := cli.String("scope") + name := cli.String("name") + version := cli.String("version") + labels, err := getLabels(cli) if err != nil { return err } @@ -209,7 +210,7 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error { return nil } -func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error { +func RunGenerateSecret(ctx context.Context, cli *cli.Command) error { runner := actions_model.ActionRunner{} if err := runner.GenerateToken(); err != nil { return err @@ -220,7 +221,7 @@ func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error { return nil } -func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) error { +func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error { if !ContextGetNoInit(ctx) { var cancel context.CancelFunc ctx, cancel = installSignals(ctx) @@ -229,7 +230,7 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err setting.MustInstalled() - scope := cliCtx.String("scope") + scope := cli.String("scope") respText, extra := private.GenerateActionsRunnerToken(ctx, scope) if extra.HasError() { diff --git a/cmd/forgejo/actions_test.go b/cmd/forgejo/actions_test.go index b58f52184c..11315239f7 100644 --- a/cmd/forgejo/actions_test.go +++ b/cmd/forgejo/actions_test.go @@ -4,14 +4,13 @@ package forgejo import ( + "context" "fmt" "testing" - "forgejo.org/services/context" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestActions_getLabels(t *testing.T) { @@ -54,21 +53,21 @@ func TestActions_getLabels(t *testing.T) { }, } - flags := SubcmdActionsRegister(context.Context{}).Flags + flags := SubcmdActionsRegister(t.Context()).Flags for _, c := range cases { t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) { // Create a copy of command to test var result *resultType - app := cli.NewApp() + app := cli.Command{} app.Flags = flags - app.Action = func(ctx *cli.Context) error { + app.Action = func(_ context.Context, ctx *cli.Command) error { labels, err := getLabels(ctx) result = &resultType{labels, err} return nil } // Run it - _ = app.Run(c.args) + _ = app.Run(t.Context(), c.args) // Test the results require.NotNil(t, result) diff --git a/cmd/forgejo/f3.go b/cmd/forgejo/f3.go index bfd14cd1a4..c4aafeac58 100644 --- a/cmd/forgejo/f3.go +++ b/cmd/forgejo/f3.go @@ -20,7 +20,7 @@ import ( f3_cmd "code.forgejo.org/f3/gof3/v3/cmd" f3_logger "code.forgejo.org/f3/gof3/v3/logger" f3_util "code.forgejo.org/f3/gof3/v3/util" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func CmdF3(ctx context.Context) *cli.Command { @@ -28,21 +28,21 @@ func CmdF3(ctx context.Context) *cli.Command { return &cli.Command{ Name: "f3", Usage: "F3", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ SubcmdF3Mirror(ctx), }, } } func SubcmdF3Mirror(ctx context.Context) *cli.Command { - mirrorCmd := f3_cmd.CreateCmdMirror(ctx) + mirrorCmd := f3_cmd.CreateCmdMirror() mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx) f3Action := mirrorCmd.Action - mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) } + mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) } return mirrorCmd } -func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error { +func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error { setting.LoadF3Setting() if !setting.F3.Enabled { return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes") @@ -69,7 +69,7 @@ func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error } } - err := action(c) + err := action(ctx, c) if panicError, ok := err.(f3_util.PanicError); ok { log.Debug("F3 Stack trace\n%s", panicError.Stack()) } diff --git a/cmd/forgejo/forgejo.go b/cmd/forgejo/forgejo.go index 3b95c80991..171ef1a71d 100644 --- a/cmd/forgejo/forgejo.go +++ b/cmd/forgejo/forgejo.go @@ -16,7 +16,7 @@ import ( "forgejo.org/modules/private" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type key int @@ -34,7 +34,7 @@ func CmdForgejo(ctx context.Context) *cli.Command { Name: "forgejo-cli", Usage: "Forgejo CLI", Flags: []cli.Flag{}, - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ CmdActions(ctx), CmdF3(ctx), }, @@ -147,12 +147,12 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er return cli.Exit(extra.Error, 1) } -func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error { - return func(c *cli.Context) error { +func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context, cli *cli.Command) (context.Context, error) { + return func(ctx context.Context, cli *cli.Command) (context.Context, error) { if !ContextGetNoInit(ctx) { var args setting.ArgWorkPathAndCustomConf // from children to parent, check the global flags - for _, curCtx := range c.Lineage() { + for _, curCtx := range cli.Lineage() { if curCtx.IsSet("work-path") && args.WorkPath == "" { args.WorkPath = curCtx.String("work-path") } @@ -165,6 +165,6 @@ func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) erro } setting.InitWorkPathAndCommonConfig(os.Getenv, args) } - return nil + return ctx, nil } } diff --git a/cmd/generate.go b/cmd/generate.go index dcbdcd0353..7076ae541f 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -5,56 +5,65 @@ package cmd import ( + "context" "fmt" "os" "forgejo.org/modules/generate" "github.com/mattn/go-isatty" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - // CmdGenerate represents the available generate sub-command. - CmdGenerate = &cli.Command{ +// CmdGenerate represents the available generate sub-command. +func cmdGenerate() *cli.Command { + return &cli.Command{ Name: "generate", Usage: "Generate Gitea's secrets/keys/tokens", - Subcommands: []*cli.Command{ - subcmdSecret, + Commands: []*cli.Command{ + subcmdSecret(), }, } +} - subcmdSecret = &cli.Command{ +func subcmdSecret() *cli.Command { + return &cli.Command{ Name: "secret", Usage: "Generate a secret token", - Subcommands: []*cli.Command{ - microcmdGenerateInternalToken, - microcmdGenerateLfsJwtSecret, - microcmdGenerateSecretKey, + Commands: []*cli.Command{ + microcmdGenerateInternalToken(), + microcmdGenerateLfsJwtSecret(), + microcmdGenerateSecretKey(), }, } +} - microcmdGenerateInternalToken = &cli.Command{ +func microcmdGenerateInternalToken() *cli.Command { + return &cli.Command{ Name: "INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN", Action: runGenerateInternalToken, } +} - microcmdGenerateLfsJwtSecret = &cli.Command{ +func microcmdGenerateLfsJwtSecret() *cli.Command { + return &cli.Command{ Name: "JWT_SECRET", Aliases: []string{"LFS_JWT_SECRET"}, Usage: "Generate a new JWT_SECRET", Action: runGenerateLfsJwtSecret, } +} - microcmdGenerateSecretKey = &cli.Command{ +func microcmdGenerateSecretKey() *cli.Command { + return &cli.Command{ Name: "SECRET_KEY", Usage: "Generate a new SECRET_KEY", Action: runGenerateSecretKey, } -) +} -func runGenerateInternalToken(c *cli.Context) error { +func runGenerateInternalToken(ctx context.Context, c *cli.Command) error { internalToken, err := generate.NewInternalToken() if err != nil { return err @@ -63,25 +72,25 @@ func runGenerateInternalToken(c *cli.Context) error { fmt.Printf("%s", internalToken) if isatty.IsTerminal(os.Stdout.Fd()) { - fmt.Printf("\n") + fmt.Println() } return nil } -func runGenerateLfsJwtSecret(c *cli.Context) error { +func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error { _, jwtSecretBase64 := generate.NewJwtSecret() fmt.Printf("%s", jwtSecretBase64) if isatty.IsTerminal(os.Stdout.Fd()) { - fmt.Printf("\n") + fmt.Print("\n") } return nil } -func runGenerateSecretKey(c *cli.Context) error { +func runGenerateSecretKey(ctx context.Context, c *cli.Command) error { secretKey, err := generate.NewSecretKey() if err != nil { return err @@ -90,7 +99,7 @@ func runGenerateSecretKey(c *cli.Context) error { fmt.Printf("%s", secretKey) if isatty.IsTerminal(os.Stdout.Fd()) { - fmt.Printf("\n") + fmt.Print("\n") } return nil diff --git a/cmd/hook.go b/cmd/hook.go index 935c1b08ea..909cdfdf84 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -21,29 +21,31 @@ import ( repo_module "forgejo.org/modules/repository" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( hookBatchSize = 30 ) -var ( - // CmdHook represents the available hooks sub-command. - CmdHook = &cli.Command{ +// CmdHook represents the available hooks sub-command. +func cmdHook() *cli.Command { + return &cli.Command{ Name: "hook", Usage: "(internal) Should only be called by Git", Description: "Delegate commands to corresponding Git hooks", Before: PrepareConsoleLoggerLevel(log.FATAL), - Subcommands: []*cli.Command{ - subcmdHookPreReceive, - subcmdHookUpdate, - subcmdHookPostReceive, - subcmdHookProcReceive, + Commands: []*cli.Command{ + subcmdHookPreReceive(), + subcmdHookUpdate(), + subcmdHookPostReceive(), + subcmdHookProcReceive(), }, } +} - subcmdHookPreReceive = &cli.Command{ +func subcmdHookPreReceive() *cli.Command { + return &cli.Command{ Name: "pre-receive", Usage: "Delegate pre-receive Git hook", Description: "This command should only be called by Git", @@ -54,7 +56,10 @@ var ( }, }, } - subcmdHookUpdate = &cli.Command{ +} + +func subcmdHookUpdate() *cli.Command { + return &cli.Command{ Name: "update", Usage: "Delegate update Git hook", Description: "This command should only be called by Git", @@ -65,7 +70,10 @@ var ( }, }, } - subcmdHookPostReceive = &cli.Command{ +} + +func subcmdHookPostReceive() *cli.Command { + return &cli.Command{ Name: "post-receive", Usage: "Delegate post-receive Git hook", Description: "This command should only be called by Git", @@ -76,8 +84,11 @@ var ( }, }, } - // Note: new hook since git 2.29 - subcmdHookProcReceive = &cli.Command{ +} + +// Note: new hook since git 2.29 +func subcmdHookProcReceive() *cli.Command { + return &cli.Command{ Name: "proc-receive", Usage: "Delegate proc-receive Git hook", Description: "This command should only be called by Git", @@ -88,7 +99,7 @@ var ( }, }, } -) +} type delayWriter struct { internal io.Writer @@ -161,11 +172,11 @@ func (n *nilWriter) WriteString(s string) (int, error) { return len(s), nil } -func runHookPreReceive(c *cli.Context) error { +func runHookPreReceive(ctx context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), true) @@ -247,7 +258,7 @@ Forgejo or set your environment appropriately.`, "") newCommitIDs[count] = newCommitID refFullNames[count] = refFullName count++ - fmt.Fprintf(out, "*") + fmt.Fprint(out, "*") if count >= hookBatchSize { fmt.Fprintf(out, " Checking %d references\n", count) @@ -263,10 +274,10 @@ Forgejo or set your environment appropriately.`, "") lastline = 0 } } else { - fmt.Fprintf(out, ".") + fmt.Fprint(out, ".") } if lastline >= hookBatchSize { - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") lastline = 0 } } @@ -283,7 +294,7 @@ Forgejo or set your environment appropriately.`, "") return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error) } } else if lastline > 0 { - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") } fmt.Fprintf(out, "Checked %d references in total\n", total) @@ -291,13 +302,13 @@ Forgejo or set your environment appropriately.`, "") } // runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update -func runHookUpdate(c *cli.Context) error { +func runHookUpdate(ctx context.Context, c *cli.Command) error { // Now if we're an internal don't do anything else if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() if c.NArg() != 3 { @@ -323,8 +334,8 @@ func runHookUpdate(c *cli.Context) error { return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "") } -func runHookPostReceive(c *cli.Context) error { - ctx, cancel := installSignals() +func runHookPostReceive(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), true) @@ -399,7 +410,7 @@ Forgejo or set your environment appropriately.`, "") continue } - fmt.Fprintf(out, ".") + fmt.Fprint(out, ".") oldCommitIDs[count] = string(fields[0]) newCommitIDs[count] = string(fields[1]) refFullNames[count] = git.RefName(fields[2]) @@ -487,8 +498,8 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) { } } -func runHookProcReceive(c *cli.Context) error { - ctx, cancel := installSignals() +func runHookProcReceive(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), true) diff --git a/cmd/hook_test.go b/cmd/hook_test.go index 89bd3cf737..82ed392fb8 100644 --- a/cmd/hook_test.go +++ b/cmd/hook_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Capture what's being written into a standard file descriptor. @@ -134,14 +134,14 @@ func TestDelayWriter(t *testing.T) { defer ts.Close() defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")() - app := cli.NewApp() - app.Commands = []*cli.Command{subcmdHookPreReceive} + app := cli.Command{} + app.Commands = []*cli.Command{subcmdHookPreReceive()} t.Run("Should delay", func(t *testing.T) { defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)() finish := captureOutput(t, os.Stdout) - err = app.Run([]string{"./forgejo", "pre-receive"}) + err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"}) require.NoError(t, err) out := finish() @@ -153,7 +153,7 @@ func TestDelayWriter(t *testing.T) { defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)() finish := captureOutput(t, os.Stdout) - err = app.Run([]string{"./forgejo", "pre-receive"}) + err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"}) require.NoError(t, err) out := finish() @@ -163,15 +163,15 @@ func TestDelayWriter(t *testing.T) { } func TestRunHookUpdate(t *testing.T) { - app := cli.NewApp() - app.Commands = []*cli.Command{subcmdHookUpdate} + app := cli.Command{} + app.Commands = []*cli.Command{subcmdHookUpdate()} t.Run("Removal of internal reference", func(t *testing.T) { defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() defer test.MockVariableValue(&setting.IsProd, false)() finish := captureOutput(t, os.Stderr) - err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) + err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) out := finish() require.Error(t, err) @@ -183,7 +183,7 @@ func TestRunHookUpdate(t *testing.T) { defer test.MockVariableValue(&setting.IsProd, false)() finish := captureOutput(t, os.Stderr) - err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"}) + err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"}) out := finish() require.Error(t, err) @@ -191,12 +191,12 @@ func TestRunHookUpdate(t *testing.T) { }) t.Run("Removal of branch", func(t *testing.T) { - err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) + err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) require.NoError(t, err) }) t.Run("Not enough arguments", func(t *testing.T) { - err := app.Run([]string{"./forgejo", "update"}) + err := app.Run(t.Context(), []string{"./forgejo", "update"}) require.NoError(t, err) }) } diff --git a/cmd/keys.go b/cmd/keys.go index 2d241984c4..00901903f4 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -11,45 +12,47 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdKeys represents the available keys sub-command -var CmdKeys = &cli.Command{ - Name: "keys", - Usage: "(internal) Should only be called by SSH server", - Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint", - Before: PrepareConsoleLoggerLevel(log.FATAL), - Action: runKeys, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "expected", - Aliases: []string{"e"}, - Value: "git", - Usage: "Expected user for whom provide key commands", +func cmdKeys() *cli.Command { + return &cli.Command{ + Name: "keys", + Usage: "(internal) Should only be called by SSH server", + Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint", + Before: PrepareConsoleLoggerLevel(log.FATAL), + Action: runKeys, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "expected", + Aliases: []string{"e"}, + Value: "git", + Usage: "Expected user for whom provide key commands", + }, + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Value: "", + Usage: "Username trying to log in by SSH", + }, + &cli.StringFlag{ + Name: "type", + Aliases: []string{"t"}, + Value: "", + Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", + }, + &cli.StringFlag{ + Name: "content", + Aliases: []string{"k"}, + Value: "", + Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", + }, }, - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "Username trying to log in by SSH", - }, - &cli.StringFlag{ - Name: "type", - Aliases: []string{"t"}, - Value: "", - Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", - }, - &cli.StringFlag{ - Name: "content", - Aliases: []string{"k"}, - Value: "", - Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", - }, - }, + } } -func runKeys(c *cli.Context) error { +func runKeys(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("No username provided") } @@ -68,7 +71,7 @@ func runKeys(c *cli.Context) error { return errors.New("No key type and content provided") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), true) @@ -78,6 +81,6 @@ func runKeys(c *cli.Context) error { if extra.Error != nil { return extra.Error } - _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text)) + _, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) return nil } diff --git a/cmd/mailer.go b/cmd/mailer.go index cddebd6d36..d05d6c849b 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -4,16 +4,17 @@ package cmd import ( + "context" "fmt" "forgejo.org/modules/private" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -func runSendMail(c *cli.Context) error { - ctx, cancel := installSignals() +func runSendMail(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setting.MustInstalled() diff --git a/cmd/main.go b/cmd/main.go index 9d559e078f..65cde47884 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,7 +14,7 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdHelp is our own help subcommand with more information @@ -25,18 +25,18 @@ func cmdHelp() *cli.Command { Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *cli.Context) (err error) { - lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil} + Action: func(ctx context.Context, c *cli.Command) (err error) { + lineage := c.Lineage() // The order is from child to parent: help, doctor, Forgejo targetCmdIdx := 0 - if c.Command.Name == "help" { + if c.Name == "help" { targetCmdIdx = 1 } - if lineage[targetCmdIdx+1].Command != nil { - err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name) + if targetCmdIdx+1 < len(lineage) { + err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name) } else { err = cli.ShowAppHelp(c) } - _, _ = fmt.Fprintf(c.App.Writer, ` + _, _ = fmt.Fprintf(c.Root().Writer, ` DEFAULT CONFIGURATION: AppPath: %s WorkPath: %s @@ -77,25 +77,25 @@ func appGlobalFlags() []cli.Flag { } } -func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) { - command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...) +func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) { + command.Flags = append(globalFlags(), command.Flags...) command.Action = prepareWorkPathAndCustomConf(command.Action) command.HideHelp = true if command.Name != "help" { - command.Subcommands = append(command.Subcommands, cmdHelp()) + command.Commands = append(command.Commands, cmdHelp()) } - for i := range command.Subcommands { - prepareSubcommandWithConfig(command.Subcommands[i], globalFlags) + for i := range command.Commands { + prepareSubcommandWithConfig(command.Commands[i], globalFlags) } } // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times -func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error { - return func(ctx *cli.Context) error { +func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context, _ *cli.Command) error { + return func(ctx context.Context, cli *cli.Command) error { var args setting.ArgWorkPathAndCustomConf // from children to parent, check the global flags - for _, curCtx := range ctx.Lineage() { + for _, curCtx := range cli.Lineage() { if curCtx.IsSet("work-path") && args.WorkPath == "" { args.WorkPath = curCtx.String("work-path") } @@ -107,15 +107,15 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) } } setting.InitWorkPathAndCommonConfig(os.Getenv, args) - if ctx.Bool("help") || action == nil { + if cli.Bool("help") || action == nil { // the default behavior of "urfave/cli": "nil action" means "show help" - return cmdHelp().Action(ctx) + return cmdHelp().Action(ctx, cli) } - return action(ctx) + return action(ctx, cli) } } -func NewMainApp(version, versionExtra string) *cli.App { +func NewMainApp(version, versionExtra string) *cli.Command { path, err := os.Executable() if err != nil { panic(err) @@ -124,7 +124,7 @@ func NewMainApp(version, versionExtra string) *cli.App { subCmdsStandalone := make([]*cli.Command, 0, 10) subCmdWithConfig := make([]*cli.Command, 0, 10) - globalFlags := make([]cli.Flag, 0, 10) + globalFlags := func() []cli.Flag { return []cli.Flag{} } // // If the executable is forgejo-cli, provide a Forgejo specific CLI @@ -133,14 +133,16 @@ func NewMainApp(version, versionExtra string) *cli.App { if executable == "forgejo-cli" { subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background())) subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background())) - globalFlags = append(globalFlags, []cli.Flag{ - &cli.BoolFlag{ - Name: "quiet", - }, - &cli.BoolFlag{ - Name: "verbose", - }, - }...) + globalFlags = func() []cli.Flag { + return []cli.Flag{ + &cli.BoolFlag{ + Name: "quiet", + }, + &cli.BoolFlag{ + Name: "verbose", + }, + } + } } else { // // Otherwise provide a Gitea compatible CLI which includes Forgejo @@ -149,55 +151,54 @@ func NewMainApp(version, versionExtra string) *cli.App { // binary and rename it to forgejo if they want. // subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background())) - subCmdWithConfig = append(subCmdWithConfig, CmdActions) + subCmdWithConfig = append(subCmdWithConfig, cmdActions()) } return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags) } -func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App { - app := cli.NewApp() - app.HelpName = "forgejo" - app.Name = "Forgejo" +func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command { + app := &cli.Command{} + app.Name = "forgejo" app.Usage = "Beyond coding. We forge." app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Version = version + versionExtra - app.EnableBashCompletion = true + app.EnableShellCompletion = true // these sub-commands need to use config file subCmdWithConfig := []*cli.Command{ cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config" - CmdWeb, - CmdServ, - CmdHook, - CmdKeys, - CmdDump, - CmdAdmin, - CmdMigrate, - CmdDoctor, - CmdManager, - CmdEmbedded, - CmdMigrateStorage, - CmdDumpRepository, - CmdRestoreRepository, + cmdWeb(), + cmdServ(), + cmdHook(), + cmdKeys(), + cmdDump(), + cmdAdmin(), + cmdMigrate(), + cmdDoctor(), + cmdManager(), + cmdEmbedded(), + cmdMigrateStorage(), + cmdDumpRepository(), + cmdRestoreRepository(), } subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...) // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ - CmdCert, - CmdGenerate, - CmdDocs, + cmdCert(), + cmdGenerate(), } subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...) - app.DefaultCommand = CmdWeb.Name + app.DefaultCommand = cmdWeb().Name - globalFlags := appGlobalFlags() - globalFlags = append(globalFlags, globalFlagsArgs...) + globalFlags := func() []cli.Flag { + return append(appGlobalFlags(), globalFlagsArgs()...) + } app.Flags = append(app.Flags, cli.VersionFlag) - app.Flags = append(app.Flags, globalFlags...) + app.Flags = append(app.Flags, globalFlags()...) app.HideHelp = true // use our own help action to show helps (with more information like default config) app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { @@ -210,8 +211,8 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd return app } -func RunMainApp(app *cli.App, args ...string) error { - err := app.Run(args) +func RunMainApp(app *cli.Command, args ...string) error { + err := app.Run(context.Background(), args) if err == nil { return nil } @@ -220,7 +221,7 @@ func RunMainApp(app *cli.App, args ...string) error { cli.OsExiter(1) return err } - _, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err) + _, _ = fmt.Fprintf(app.Root().ErrWriter, "Command error: %v\n", err) cli.OsExiter(1) return err } diff --git a/cmd/main_test.go b/cmd/main_test.go index 1ff71005e3..737150c62f 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,6 +4,8 @@ package cmd import ( + "context" + "errors" "fmt" "io" "path/filepath" @@ -16,7 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestMain(m *testing.M) { @@ -27,10 +29,10 @@ func makePathOutput(workPath, customPath, customConf string) string { return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) } -func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App { +func newTestApp(testCmdAction func(_ context.Context, ctx *cli.Command) error) *cli.Command { app := NewMainApp("version", "version-extra") testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} - prepareSubcommandWithConfig(testCmd, appGlobalFlags()) + prepareSubcommandWithConfig(testCmd, appGlobalFlags) app.Commands = append(app.Commands, testCmd) app.DefaultCommand = testCmd.Name return app @@ -42,7 +44,7 @@ type runResult struct { ExitCode int } -func runTestApp(app *cli.App, args ...string) (runResult, error) { +func runTestApp(app *cli.Command, args ...string) (runResult, error) { outBuf := new(strings.Builder) errBuf := new(strings.Builder) app.Writer = outBuf @@ -65,7 +67,6 @@ func TestCliCmd(t *testing.T) { defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") cli.CommandHelpTemplate = "(command help template)" - cli.AppHelpTemplate = "(app help template)" cli.SubcommandHelpTemplate = "(subcommand help template)" cases := []struct { @@ -109,12 +110,17 @@ func TestCliCmd(t *testing.T) { }, } - app := newTestApp(func(ctx *cli.Context) error { - _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil - }) for _, c := range cases { t.Run(c.cmd, func(t *testing.T) { + defer test.MockProtect(&setting.AppWorkPath)() + defer test.MockProtect(&setting.CustomPath)() + defer test.MockProtect(&setting.CustomConf)() + + app := newTestApp(func(_ context.Context, ctx *cli.Command) error { + _, _ = fmt.Fprint(ctx.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }) + for k, v := range c.env { t.Setenv(k, v) } @@ -122,34 +128,34 @@ func TestCliCmd(t *testing.T) { r, err := runTestApp(app, args...) require.NoError(t, err, c.cmd) assert.NotEmpty(t, c.exp, c.cmd) - assert.Contains(t, r.Stdout, c.exp, c.cmd) + assert.Contains(t, r.Stdout, c.exp, c.cmd+"\n"+r.Stdout) }) } } func TestCliCmdError(t *testing.T) { - app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") }) + app := newTestApp(func(_ context.Context, ctx *cli.Command) error { return errors.New("normal error") }) r, err := runTestApp(app, "./gitea", "test-cmd") require.Error(t, err) assert.Equal(t, 1, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "Command error: normal error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) }) + app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return cli.Exit("exit error", 2) }) r, err = runTestApp(app, "./gitea", "test-cmd") require.Error(t, err) assert.Equal(t, 2, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "exit error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") require.Error(t, err) assert.Equal(t, 1, r.ExitCode) - assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) - assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... + assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) + assert.Empty(t, r.Stdout) - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd") require.NoError(t, err) assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called diff --git a/cmd/manager.go b/cmd/manager.go index 56089947fd..029329b44e 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -4,30 +4,34 @@ package cmd import ( + "context" "os" "time" "forgejo.org/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - // CmdManager represents the manager command - CmdManager = &cli.Command{ +// CmdManager represents the manager command +func cmdManager() *cli.Command { + return &cli.Command{ Name: "manager", Usage: "Manage the running forgejo process", Description: "This is a command for managing the running forgejo process", - Subcommands: []*cli.Command{ - subcmdShutdown, - subcmdRestart, - subcmdReloadTemplates, - subcmdFlushQueues, - subcmdLogging, - subCmdProcesses, + Commands: []*cli.Command{ + subcmdShutdown(), + subcmdRestart(), + subcmdReloadTemplates(), + subcmdFlushQueues(), + subcmdLogging(), + subCmdProcesses(), }, } - subcmdShutdown = &cli.Command{ +} + +func subcmdShutdown() *cli.Command { + return &cli.Command{ Name: "shutdown", Usage: "Gracefully shutdown the running process", Flags: []cli.Flag{ @@ -37,7 +41,10 @@ var ( }, Action: runShutdown, } - subcmdRestart = &cli.Command{ +} + +func subcmdRestart() *cli.Command { + return &cli.Command{ Name: "restart", Usage: "Gracefully restart the running process - (not implemented for windows servers)", Flags: []cli.Flag{ @@ -47,7 +54,10 @@ var ( }, Action: runRestart, } - subcmdReloadTemplates = &cli.Command{ +} + +func subcmdReloadTemplates() *cli.Command { + return &cli.Command{ Name: "reload-templates", Usage: "Reload template files in the running process", Flags: []cli.Flag{ @@ -57,7 +67,10 @@ var ( }, Action: runReloadTemplates, } - subcmdFlushQueues = &cli.Command{ +} + +func subcmdFlushQueues() *cli.Command { + return &cli.Command{ Name: "flush-queues", Usage: "Flush queues in the running process", Action: runFlushQueues, @@ -76,7 +89,10 @@ var ( }, }, } - subCmdProcesses = &cli.Command{ +} + +func subCmdProcesses() *cli.Command { + return &cli.Command{ Name: "processes", Usage: "Display running processes within the current process", Action: runProcesses, @@ -106,10 +122,10 @@ var ( }, }, } -) +} -func runShutdown(c *cli.Context) error { - ctx, cancel := installSignals() +func runShutdown(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -117,8 +133,8 @@ func runShutdown(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runRestart(c *cli.Context) error { - ctx, cancel := installSignals() +func runRestart(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -126,8 +142,8 @@ func runRestart(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runReloadTemplates(c *cli.Context) error { - ctx, cancel := installSignals() +func runReloadTemplates(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -135,8 +151,8 @@ func runReloadTemplates(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runFlushQueues(c *cli.Context) error { - ctx, cancel := installSignals() +func runFlushQueues(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -144,8 +160,8 @@ func runFlushQueues(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runProcesses(c *cli.Context) error { - ctx, cancel := installSignals() +func runProcesses(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c6db2bb05a..c543afe872 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -11,11 +12,11 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - defaultLoggingFlags = []cli.Flag{ +func defaultLoggingFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "logger", Usage: `Logger name - will default to "default"`, @@ -56,11 +57,13 @@ var ( Name: "debug", }, } +} - subcmdLogging = &cli.Command{ +func subcmdLogging() *cli.Command { + return &cli.Command{ Name: "logging", Usage: "Adjust logging commands", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "pause", Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)", @@ -104,11 +107,11 @@ var ( }, { Name: "add", Usage: "Add a logger", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "file", Usage: "Add a file logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ + Flags: append(defaultLoggingFlags(), []cli.Flag{ &cli.StringFlag{ Name: "filename", Aliases: []string{"f"}, @@ -152,7 +155,7 @@ var ( }, { Name: "conn", Usage: "Add a net conn logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ + Flags: append(defaultLoggingFlags(), []cli.Flag{ &cli.BoolFlag{ Name: "reconnect-on-message", Aliases: []string{"R"}, @@ -193,10 +196,10 @@ var ( }, }, } -) +} -func runRemoveLogger(c *cli.Context) error { - ctx, cancel := installSignals() +func runRemoveLogger(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -210,8 +213,8 @@ func runRemoveLogger(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runAddConnLogger(c *cli.Context) error { - ctx, cancel := installSignals() +func runAddConnLogger(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -237,11 +240,11 @@ func runAddConnLogger(c *cli.Context) error { if c.IsSet("reconnect-on-message") { vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func runAddFileLogger(c *cli.Context) error { - ctx, cancel := installSignals() +func runAddFileLogger(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -270,10 +273,10 @@ func runAddFileLogger(c *cli.Context) error { if c.IsSet("compression-level") { vals["compressionLevel"] = c.Int("compression-level") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { +func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { if len(c.String("level")) > 0 { vals["level"] = log.LevelFromString(c.String("level")).String() } @@ -300,15 +303,15 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { if c.IsSet("writer") { writer = c.String("writer") } - ctx, cancel := installSignals() + ctx, cancel := installSignals(ctx) defer cancel() extra := private.AddLogger(ctx, logger, writer, mode, vals) return handleCliResponseExtra(extra) } -func runPauseLogging(c *cli.Context) error { - ctx, cancel := installSignals() +func runPauseLogging(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -317,8 +320,8 @@ func runPauseLogging(c *cli.Context) error { return nil } -func runResumeLogging(c *cli.Context) error { - ctx, cancel := installSignals() +func runResumeLogging(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -327,8 +330,8 @@ func runResumeLogging(c *cli.Context) error { return nil } -func runReleaseReopenLogging(c *cli.Context) error { - ctx, cancel := installSignals() +func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) @@ -337,8 +340,8 @@ func runReleaseReopenLogging(c *cli.Context) error { return nil } -func runSetLogSQL(c *cli.Context) error { - ctx, cancel := installSignals() +func runSetLogSQL(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setup(ctx, c.Bool("debug"), false) diff --git a/cmd/migrate.go b/cmd/migrate.go index c192ca1966..5a485d17f9 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -11,19 +11,21 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdMigrate represents the available migrate sub-command. -var CmdMigrate = &cli.Command{ - Name: "migrate", - Usage: "Migrate the database", - Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.", - Action: runMigrate, +func cmdMigrate() *cli.Command { + return &cli.Command{ + Name: "migrate", + Usage: "Migrate the database", + Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.", + Action: runMigrate, + } } -func runMigrate(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runMigrate(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() if err := initDB(stdCtx); err != nil { diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index 6a04dd48ae..d741a883e3 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -22,79 +22,81 @@ import ( "forgejo.org/modules/setting" "forgejo.org/modules/storage" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "xorm.io/xorm" ) // CmdMigrateStorage represents the available migrate storage sub-command. -var CmdMigrateStorage = &cli.Command{ - Name: "migrate-storage", - Usage: "Migrate the storage", - Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", - Action: runMigrateStorage, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "type", - Aliases: []string{"t"}, - Value: "", - Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'", +func cmdMigrateStorage() *cli.Command { + return &cli.Command{ + Name: "migrate-storage", + Usage: "Migrate the storage", + Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", + Action: runMigrateStorage, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "type", + Aliases: []string{"t"}, + Value: "", + Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'", + }, + &cli.StringFlag{ + Name: "storage", + Aliases: []string{"s"}, + Value: "", + Usage: "New storage type: local (default) or minio", + }, + &cli.StringFlag{ + Name: "path", + Aliases: []string{"p"}, + Value: "", + Usage: "New storage placement if store is local (leave blank for default)", + }, + &cli.StringFlag{ + Name: "minio-endpoint", + Value: "", + Usage: "Minio storage endpoint", + }, + &cli.StringFlag{ + Name: "minio-access-key-id", + Value: "", + Usage: "Minio storage accessKeyID", + }, + &cli.StringFlag{ + Name: "minio-secret-access-key", + Value: "", + Usage: "Minio storage secretAccessKey", + }, + &cli.StringFlag{ + Name: "minio-bucket", + Value: "", + Usage: "Minio storage bucket", + }, + &cli.StringFlag{ + Name: "minio-location", + Value: "", + Usage: "Minio storage location to create bucket", + }, + &cli.StringFlag{ + Name: "minio-base-path", + Value: "", + Usage: "Minio storage base path on the bucket", + }, + &cli.BoolFlag{ + Name: "minio-use-ssl", + Usage: "Enable SSL for minio", + }, + &cli.BoolFlag{ + Name: "minio-insecure-skip-verify", + Usage: "Skip SSL verification", + }, + &cli.StringFlag{ + Name: "minio-checksum-algorithm", + Value: "", + Usage: "Minio checksum algorithm (default/md5)", + }, }, - &cli.StringFlag{ - Name: "storage", - Aliases: []string{"s"}, - Value: "", - Usage: "New storage type: local (default) or minio", - }, - &cli.StringFlag{ - Name: "path", - Aliases: []string{"p"}, - Value: "", - Usage: "New storage placement if store is local (leave blank for default)", - }, - &cli.StringFlag{ - Name: "minio-endpoint", - Value: "", - Usage: "Minio storage endpoint", - }, - &cli.StringFlag{ - Name: "minio-access-key-id", - Value: "", - Usage: "Minio storage accessKeyID", - }, - &cli.StringFlag{ - Name: "minio-secret-access-key", - Value: "", - Usage: "Minio storage secretAccessKey", - }, - &cli.StringFlag{ - Name: "minio-bucket", - Value: "", - Usage: "Minio storage bucket", - }, - &cli.StringFlag{ - Name: "minio-location", - Value: "", - Usage: "Minio storage location to create bucket", - }, - &cli.StringFlag{ - Name: "minio-base-path", - Value: "", - Usage: "Minio storage base path on the bucket", - }, - &cli.BoolFlag{ - Name: "minio-use-ssl", - Usage: "Enable SSL for minio", - }, - &cli.BoolFlag{ - Name: "minio-insecure-skip-verify", - Usage: "Skip SSL verification", - }, - &cli.StringFlag{ - Name: "minio-checksum-algorithm", - Value: "", - Usage: "Minio checksum algorithm (default/md5)", - }, - }, + } } func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error { @@ -182,8 +184,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora }) } -func runMigrateStorage(ctx *cli.Context) error { - stdCtx, cancel := installSignals() +func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error { + stdCtx, cancel := installSignals(stdCtx) defer cancel() if err := initDB(stdCtx); err != nil { diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 1e53ce26ba..0e9f0bb50a 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -4,52 +4,55 @@ package cmd import ( + "context" "strings" "forgejo.org/modules/private" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdRestoreRepository represents the available restore a repository sub-command. -var CmdRestoreRepository = &cli.Command{ - Name: "restore-repo", - Usage: "Restore the repository from disk", - Description: "This is a command for restoring the repository data.", - Action: runRestoreRepository, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo_dir", - Aliases: []string{"r"}, - Value: "./data", - Usage: "Repository dir path to restore from", - }, - &cli.StringFlag{ - Name: "owner_name", - Value: "", - Usage: "Restore destination owner name", - }, - &cli.StringFlag{ - Name: "repo_name", - Value: "", - Usage: "Restore destination repository name", - }, - &cli.StringFlag{ - Name: "units", - Value: "", - Usage: `Which items will be restored, one or more units should be separated as comma. +func cmdRestoreRepository() *cli.Command { + return &cli.Command{ + Name: "restore-repo", + Usage: "Restore the repository from disk", + Description: "This is a command for restoring the repository data.", + Action: runRestoreRepository, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo_dir", + Aliases: []string{"r"}, + Value: "./data", + Usage: "Repository dir path to restore from", + }, + &cli.StringFlag{ + Name: "owner_name", + Value: "", + Usage: "Restore destination owner name", + }, + &cli.StringFlag{ + Name: "repo_name", + Value: "", + Usage: "Restore destination repository name", + }, + &cli.StringFlag{ + Name: "units", + Value: "", + Usage: `Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, + }, + &cli.BoolFlag{ + Name: "validation", + Usage: "Sanity check the content of the files before trying to load them", + }, }, - &cli.BoolFlag{ - Name: "validation", - Usage: "Sanity check the content of the files before trying to load them", - }, - }, + } } -func runRestoreRepository(c *cli.Context) error { - ctx, cancel := installSignals() +func runRestoreRepository(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() setting.MustInstalled() diff --git a/cmd/serv.go b/cmd/serv.go index 4b288632d2..1fac2d13f5 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -33,7 +33,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/kballard/go-shellquote" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( @@ -41,20 +41,22 @@ const ( ) // CmdServ represents the available serv sub-command. -var CmdServ = &cli.Command{ - Name: "serv", - Usage: "(internal) Should only be called by SSH shell", - Description: "Serv provides access auth for repositories", - Before: PrepareConsoleLoggerLevel(log.FATAL), - Action: runServ, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "enable-pprof", +func cmdServ() *cli.Command { + return &cli.Command{ + Name: "serv", + Usage: "(internal) Should only be called by SSH shell", + Description: "Serv provides access auth for repositories", + Before: PrepareConsoleLoggerLevel(log.FATAL), + Action: runServ, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "enable-pprof", + }, + &cli.BoolFlag{ + Name: "debug", + }, }, - &cli.BoolFlag{ - Name: "debug", - }, - }, + } } func setup(ctx context.Context, debug, gitNeeded bool) { @@ -131,8 +133,8 @@ func handleCliResponseExtra(extra private.ResponseExtra) error { return nil } -func runServ(c *cli.Context) error { - ctx, cancel := installSignals() +func runServ(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) defer cancel() // FIXME: This needs to internationalised @@ -194,7 +196,7 @@ func runServ(c *cli.Context) error { if git.CheckGitVersionAtLeast("2.29") == nil { // for AGit Flow if cmd == "ssh_info" { - fmt.Print(`{"type":"gitea","version":1}`) + fmt.Print(`{"type":"agit","version":1}`) return nil } } diff --git a/cmd/web.go b/cmd/web.go index 1e67b2e922..87965a7c1e 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -26,48 +26,50 @@ import ( "forgejo.org/routers/install" "github.com/felixge/fgprof" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // PIDFile could be set from build tag var PIDFile = "/run/gitea.pid" // CmdWeb represents the available web sub-command. -var CmdWeb = &cli.Command{ - Name: "web", - Usage: "Start the Forgejo web server", - Description: `The Forgejo web server is the only thing you need to run, +func cmdWeb() *cli.Command { + return &cli.Command{ + Name: "web", + Usage: "Start the Forgejo web server", + Description: `The Forgejo web server is the only thing you need to run, and it takes care of all the other things for you`, - Before: PrepareConsoleLoggerLevel(log.INFO), - Action: runWeb, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "port", - Aliases: []string{"p"}, - Value: "3000", - Usage: "Temporary port number to prevent conflict", + Before: PrepareConsoleLoggerLevel(log.INFO), + Action: runWeb, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Value: "3000", + Usage: "Temporary port number to prevent conflict", + }, + &cli.StringFlag{ + Name: "install-port", + Value: "3000", + Usage: "Temporary port number to run the install page on to prevent conflict", + }, + &cli.StringFlag{ + Name: "pid", + Aliases: []string{"P"}, + Value: PIDFile, + Usage: "Custom pid file path", + }, + &cli.BoolFlag{ + Name: "quiet", + Aliases: []string{"q"}, + Usage: "Only display Fatal logging errors until logging is set-up", + }, + &cli.BoolFlag{ + Name: "verbose", + Usage: "Set initial logging to TRACE level until logging is properly set-up", + }, }, - &cli.StringFlag{ - Name: "install-port", - Value: "3000", - Usage: "Temporary port number to run the install page on to prevent conflict", - }, - &cli.StringFlag{ - Name: "pid", - Aliases: []string{"P"}, - Value: PIDFile, - Usage: "Custom pid file path", - }, - &cli.BoolFlag{ - Name: "quiet", - Aliases: []string{"q"}, - Usage: "Only display Fatal logging errors until logging is set-up", - }, - &cli.BoolFlag{ - Name: "verbose", - Usage: "Set initial logging to TRACE level until logging is properly set-up", - }, - }, + } } func runHTTPRedirector() { @@ -128,7 +130,7 @@ func showWebStartupMessage(msg string) { } } -func serveInstall(ctx *cli.Context) error { +func serveInstall(_ context.Context, ctx *cli.Command) error { showWebStartupMessage("Prepare to run install page") routers.InitWebInstallPage(graceful.GetManager().HammerContext()) @@ -161,7 +163,7 @@ func serveInstall(ctx *cli.Context) error { return nil } -func serveInstalled(ctx *cli.Context) error { +func serveInstalled(_ context.Context, ctx *cli.Command) error { setting.InitCfgProvider(setting.CustomConf) setting.LoadCommonSettings() setting.MustInstalled() @@ -233,7 +235,7 @@ func servePprof() { finished() } -func runWeb(ctx *cli.Context) error { +func runWeb(ctx context.Context, cli *cli.Command) error { defer func() { if panicked := recover(); panicked != nil { log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) @@ -251,12 +253,12 @@ func runWeb(ctx *cli.Context) error { } // Set pid file setting - if ctx.IsSet("pid") { - createPIDFile(ctx.String("pid")) + if cli.IsSet("pid") { + createPIDFile(cli.String("pid")) } if !setting.InstallLock { - if err := serveInstall(ctx); err != nil { + if err := serveInstall(ctx, cli); err != nil { return err } } else { @@ -267,7 +269,7 @@ func runWeb(ctx *cli.Context) error { go servePprof() } - return serveInstalled(ctx) + return serveInstalled(ctx, cli) } func setPort(port string) error { diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index 95f34527ac..e8e799b5f3 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -4,16 +4,17 @@ package main import ( + "context" "os" "forgejo.org/modules/log" "forgejo.org/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func main() { - app := cli.NewApp() + app := cli.Command{} app.Name = "environment-to-ini" app.Usage = "Use provided environment to update configuration ini" app.Description = `As a helper to allow docker users to update the forgejo configuration @@ -72,13 +73,13 @@ func main() { }, } app.Action = runEnvironmentToIni - err := app.Run(os.Args) + err := app.Run(context.Background(), os.Args) if err != nil { log.Fatal("Failed to run app with %s: %v", os.Args, err) } } -func runEnvironmentToIni(c *cli.Context) error { +func runEnvironmentToIni(ctx context.Context, c *cli.Command) error { // the config system may change the environment variables, so get a copy first, to be used later env := append([]string{}, os.Environ()...) setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index bdc9ba7b2e..da657df60c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1025,6 +1025,10 @@ LEVEL = Info ;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. ;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls ;; +;; Comma separated list of default mirror repo units. +;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. +;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages +;; ;; Prefix archive files by placing them in a directory named after the repository ;PREFIX_ARCHIVE_FILES = true ;; @@ -1572,6 +1576,15 @@ LEVEL = Info ;; - manage_gpg_keys: a user cannot configure gpg keys ;;EXTERNAL_USER_DISABLE_FEATURES = +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[moderation] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; When true enables moderation capabilities; default is false. +;; If enabled it will be possible for users to report abusive content (new actions are added in the UI and /report_abuse route will be enabled) and a new Moderation section will be added to Admin settings where the reports can be reviewed. +;ENABLED = false + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[openid] diff --git a/go.mod b/go.mod index aea551d6e2..66519155b8 100644 --- a/go.mod +++ b/go.mod @@ -2,17 +2,18 @@ module forgejo.org go 1.24 -toolchain go1.24.2 +toolchain go1.24.4 require ( - code.forgejo.org/f3/gof3/v3 v3.10.6 + code.forgejo.org/f3/gof3/v3 v3.11.0 code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 + code.forgejo.org/forgejo/go-rpmutils v1.0.0 code.forgejo.org/forgejo/levelqueue v1.0.0 code.forgejo.org/forgejo/reply v1.0.2 - code.forgejo.org/go-chi/binding v1.0.0 - code.forgejo.org/go-chi/cache v1.0.0 - code.forgejo.org/go-chi/captcha v1.0.1 - code.forgejo.org/go-chi/session v1.0.1 + code.forgejo.org/go-chi/binding v1.0.1 + code.forgejo.org/go-chi/cache v1.0.1 + code.forgejo.org/go-chi/captcha v1.0.2 + code.forgejo.org/go-chi/session v1.0.2 code.gitea.io/actions-proto-go v0.4.0 code.gitea.io/sdk/gitea v0.21.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 @@ -20,12 +21,12 @@ require ( github.com/42wim/httpsig v1.2.3 github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/ProtonMail/go-crypto v1.1.6 + github.com/ProtonMail/go-crypto v1.3.0 github.com/PuerkitoBio/goquery v1.10.3 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 - github.com/alecthomas/chroma/v2 v2.17.2 + github.com/alecthomas/chroma/v2 v2.18.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb - github.com/blevesearch/bleve/v2 v2.5.1 + github.com/blevesearch/bleve/v2 v2.5.2 github.com/buildkite/terminal-to-html/v3 v3.16.8 github.com/caddyserver/certmagic v0.23.0 github.com/chi-middleware/proxy v1.1.1 @@ -40,14 +41,14 @@ require ( github.com/gliderlabs/ssh v0.3.8 github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 - github.com/go-chi/chi/v5 v5.2.0 + github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/cors v1.2.1 github.com/go-co-op/gocron v1.37.0 github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-git/go-git/v5 v5.13.2 github.com/go-ldap/ldap/v3 v3.4.6 - github.com/go-openapi/spec v0.20.14 - github.com/go-sql-driver/mysql v1.9.1 + github.com/go-openapi/spec v0.21.0 + github.com/go-sql-driver/mysql v1.9.2 github.com/go-webauthn/webauthn v0.13.0 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f @@ -86,27 +87,27 @@ require ( github.com/prometheus/client_golang v1.21.1 github.com/redis/go-redis/v9 v9.8.0 github.com/robfig/cron/v3 v3.0.1 - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 - github.com/sassoftware/go-rpmutils v0.4.0 - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 + github.com/sergi/go-diff v1.4.0 github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 github.com/ulikunitz/xz v0.5.12 github.com/urfave/cli/v2 v2.27.6 + github.com/urfave/cli/v3 v3.3.3 github.com/valyala/fastjson v1.6.4 github.com/yohcop/openid-go v1.0.1 - github.com/yuin/goldmark v1.7.11 + github.com/yuin/goldmark v1.7.12 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc - gitlab.com/gitlab-org/api/client-go v0.126.0 - go.uber.org/mock v0.5.1 - golang.org/x/crypto v0.38.0 + gitlab.com/gitlab-org/api/client-go v0.129.0 + go.uber.org/mock v0.5.2 + golang.org/x/crypto v0.39.0 golang.org/x/image v0.27.0 - golang.org/x/net v0.40.0 + golang.org/x/net v0.41.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.14.0 + golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 - golang.org/x/text v0.25.0 + golang.org/x/text v0.26.0 google.golang.org/protobuf v1.36.4 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/ini.v1 v1.67.0 @@ -121,7 +122,6 @@ require ( dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect - github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/andybalholm/brotli v1.1.1 // indirect @@ -146,13 +146,13 @@ require ( github.com/blevesearch/zapx/v13 v13.4.2 // indirect github.com/blevesearch/zapx/v14 v14.4.2 // indirect github.com/blevesearch/zapx/v15 v15.4.2 // indirect - github.com/blevesearch/zapx/v16 v16.2.3 // indirect + github.com/blevesearch/zapx/v16 v16.2.4 // indirect github.com/boombuler/barcode v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.3.8 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -170,9 +170,9 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-webauthn/x v0.1.21 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -237,9 +237,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.34.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 255835b68e..994dad4aac 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,15 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -code.forgejo.org/f3/gof3/v3 v3.10.6 h1:Ru/Iz+pqM8IPi7atUHE7+q7v3O3DRbYgMFqrFTsO1m8= -code.forgejo.org/f3/gof3/v3 v3.10.6/go.mod h1:K6lQCWQIyN/5rjP/OJL9fMA6fd++satndE20w/I6Kss= +code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE= +code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM= code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs= code.forgejo.org/forgejo/act v1.26.0/go.mod h1:HFDFrXPrqfM9aH2RCnMiBdo/3ThxDmZjp58InPjGOfo= code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE= code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M= +code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk= code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA= code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/UfU/XgPpLuE= @@ -16,14 +18,14 @@ code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCd code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U= code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA= code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= -code.forgejo.org/go-chi/binding v1.0.0 h1:EIDJtk9brK7WsT7rvS/D4cxX8XlnhY3LMy8ex1jeHu0= -code.forgejo.org/go-chi/binding v1.0.0/go.mod h1:fWwqaHj0H1/KeCpBqdvKunflq8pYfciEHI5v3UUeE2E= -code.forgejo.org/go-chi/cache v1.0.0 h1:akLfGxNlHcacmtutovNtYFSTMsbdcp5MGjAEsP4pxnE= -code.forgejo.org/go-chi/cache v1.0.0/go.mod h1:OVlZ/TqDYJ+RUJ+R+J+OLxtlyjo3pbjBeK7LAWAB+Vk= -code.forgejo.org/go-chi/captcha v1.0.1 h1:/oe1fvGOpdyyeGijg3oMYNOYLvEovNvp79Y3gLe3qbk= -code.forgejo.org/go-chi/captcha v1.0.1/go.mod h1:6EbjSVVa7WoZFENgwK/hLAJZq+HBXtgRsjnIngILC8Y= -code.forgejo.org/go-chi/session v1.0.1 h1:RNkcJQZJBqlvJoIFXSth87b3kMFZLDBA18VcitD+Z0Y= -code.forgejo.org/go-chi/session v1.0.1/go.mod h1:y69sjS984wc7k4xyu77yNE5HKeSlBoQW8VSGdsK7RAs= +code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY= +code.forgejo.org/go-chi/binding v1.0.1/go.mod h1:oTFFDg/dkwFbmVuusiULB1OlrIJM95cOGK7Nc3GYcoo= +code.forgejo.org/go-chi/cache v1.0.1 h1:w6IsDcPbeEnEYZn7M2HJe3/3/Ehtcw/72VjcVK7+lBw= +code.forgejo.org/go-chi/cache v1.0.1/go.mod h1:K3aQSyRIN4xiuqV1kanfQ6O4ToDpzDpY3bNOyGjFe3U= +code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkAaqZGQ= +code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE= +code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc= +code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc= code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4= @@ -46,13 +48,11 @@ github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= -github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= @@ -62,8 +62,8 @@ github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI= -github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= +github.com/alecthomas/chroma/v2 v2.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4= +github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -87,8 +87,8 @@ github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCk github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/blevesearch/bleve/v2 v2.5.1 h1:cc/O++W2Hcjp1SU5ETHeE+QYWv2oV88ldYEPowdmg8M= -github.com/blevesearch/bleve/v2 v2.5.1/go.mod h1:9g/wnbWKm9AgXrU8Ecqi+IDdqjUHWymwkQRDg+5tafU= +github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8= +github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo= github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y= github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg= @@ -121,13 +121,13 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= -github.com/blevesearch/zapx/v16 v16.2.3 h1:7Y0r+a3diEvlazsncexq1qoFOcBd64xwMS7aDm4lo1s= -github.com/blevesearch/zapx/v16 v16.2.3/go.mod h1:wVJ+GtURAaRG9KQAMNYyklq0egV+XJlGcXNCE0OFjjA= +github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww= +github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -150,8 +150,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= -github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -215,8 +215,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= -github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= @@ -239,16 +239,16 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= -github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= -github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -499,13 +499,11 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= -github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= -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/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -537,6 +535,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= +github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -554,8 +554,8 @@ github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBz github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= -github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= +github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -564,8 +564,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk= -gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE= +gitlab.com/gitlab-org/api/client-go v0.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE= +gitlab.com/gitlab-org/api/client-go v0.129.0/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -573,8 +573,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= -go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -591,8 +591,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= @@ -603,8 +603,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -620,8 +620,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -633,8 +633,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -684,10 +684,10 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -695,8 +695,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000000..f9605bc2d9 --- /dev/null +++ b/manifest.scm @@ -0,0 +1,38 @@ +;;; Copyright 2025 The Forgejo Authors. All rights reserved. +;;; SPDX-License-Identifier: MIT +;;; +;;; Commentary: +;;; +;;; This is a GNU Guix manifest that can be used to create a +;;; development environment to build and test Forgejo. +;;; +;;; The following is a usage example to create a containerized +;;; environment, with HOME shared for the Go cache and the network +;;; made available to fetch required Go and Node dependencies. +;;; +#| +guix shell -CNF --share=$HOME -m manifest.scm +export GOTOOLCHAIN=local # to use the Go binary from Guix +export CC=gcc CGO_ENABLED=1 +export TAGS="timetzdata sqlite sqlite_unlock_notify" +make clean +make -j$(nproc) +make test -j$(nproc) # run unit tests +make test-sqlite -j$(nproc) # run integration tests +make watch # run an instance/rebuild on changes +|# +(specifications->manifest + (list "bash-minimal" + "coreutils" + "findutils" + "gcc-toolchain" + "git" ;libpcre support is required + "git-lfs" + "gnupg" + "go" + "grep" + "make" + "node" + "nss-certs" + "openssh" + "sed")) diff --git a/models/actions/run.go b/models/actions/run.go index 61159bc929..48756b7a08 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" "slices" "strings" @@ -222,29 +223,38 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork var hasWaiting bool for _, v := range jobs { id, job := v.Job() - needs := job.Needs() - if err := v.SetJob(id, job.EraseNeeds()); err != nil { - return err + status := StatusFailure + payload := []byte{} + needs := []string{} + name := run.Title + runsOn := []string{} + if job != nil { + needs = job.Needs() + if err := v.SetJob(id, job.EraseNeeds()); err != nil { + return err + } + payload, _ = v.Marshal() + + if len(needs) > 0 || run.NeedApproval { + status = StatusBlocked + } else { + status = StatusWaiting + hasWaiting = true + } + name, _ = util.SplitStringAtByteN(job.Name, 255) + runsOn = job.RunsOn() } - payload, _ := v.Marshal() - status := StatusWaiting - if len(needs) > 0 || run.NeedApproval { - status = StatusBlocked - } else { - hasWaiting = true - } - job.Name, _ = util.SplitStringAtByteN(job.Name, 255) runJobs = append(runJobs, &ActionRunJob{ RunID: run.ID, RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA, IsForkPullRequest: run.IsForkPullRequest, - Name: job.Name, + Name: name, WorkflowPayload: payload, JobID: id, Needs: needs, - RunsOn: job.RunsOn(), + RunsOn: runsOn, Status: status, }) } @@ -346,7 +356,7 @@ func UpdateRunWithoutNotification(ctx context.Context, run *ActionRun, cols ...s return err } if affected == 0 { - return fmt.Errorf("run has changed") + return errors.New("run has changed") // It's impossible that the run is not found, since Gitea never deletes runs. } diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go index cbcb4beb8e..afc754f26a 100644 --- a/models/actions/run_job_list.go +++ b/models/actions/run_job_list.go @@ -54,6 +54,8 @@ type FindRunJobOptions struct { CommitSHA string Statuses []Status UpdatedBefore timeutil.TimeStamp + Events []string // []webhook_module.HookEventType + RunNumber int64 } func (opts FindRunJobOptions) ToConds() builder.Cond { @@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond { if opts.UpdatedBefore > 0 { cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore}) } + if len(opts.Events) > 0 { + cond = cond.And(builder.In("event", opts.Events)) + } + if opts.RunNumber > 0 { + cond = cond.And(builder.Eq{"`index`": opts.RunNumber}) + } return cond } diff --git a/models/actions/status.go b/models/actions/status.go index f4357af731..e42c221121 100644 --- a/models/actions/status.go +++ b/models/actions/status.go @@ -34,6 +34,15 @@ var statusNames = map[Status]string{ StatusBlocked: "blocked", } +var nameToStatus = make(map[string]Status, len(statusNames)) + +func init() { + // Populate name to status lookup map + for status, name := range statusNames { + nameToStatus[name] = status + } +} + // String returns the string name of the Status func (s Status) String() string { return statusNames[s] @@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result { } return runnerv1.Result_RESULT_UNSPECIFIED } + +func StatusFromString(name string) (Status, bool) { + status, exists := nameToStatus[name] + return status, exists +} diff --git a/models/activities/action.go b/models/activities/action.go index f4be7d23e2..a309637d04 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -7,6 +7,7 @@ package activities import ( "context" + "errors" "fmt" "net/url" "path" @@ -458,7 +459,7 @@ type GetFeedsOptions struct { // GetFeeds returns actions according to the provided options func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) { if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil { - return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") + return nil, 0, errors.New("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } cond, err := activityQueryCondition(ctx, opts) diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index 0cc3f759c6..11badb77e2 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -13,6 +13,12 @@ import ( "forgejo.org/modules/timeutil" ) +const ( + // contributionsMaxAgeSeconds How old data to retrieve for the heatmap. + // 371 days to cover the entire heatmap (53 *full* weeks) + contributionsMaxAgeSeconds = 32054400 +) + // UserHeatmapData represents the data needed to create a heatmap type UserHeatmapData struct { Timestamp timeutil.TimeStamp `json:"timestamp"` @@ -62,7 +68,7 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi Select(groupBy+" AS timestamp, count(user_id) as contributions"). Table("action"). Where(cond). - And("created_unix > ?", timeutil.TimeStampNow()-31536000). + And("created_unix >= ?", timeutil.TimeStampNow()-contributionsMaxAgeSeconds). GroupBy("timestamp"). OrderBy("timestamp"). Find(&hdata) diff --git a/models/activities/user_heatmap_test.go b/models/activities/user_heatmap_test.go index d922f9a78b..34308cb3d4 100644 --- a/models/activities/user_heatmap_test.go +++ b/models/activities/user_heatmap_test.go @@ -54,6 +54,10 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { "multiple actions performed with two grouped together", 10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`, }, + { + "test cutoff within", + 40, 40, 1, `[{"timestamp":1577404800,"contributions":1}]`, + }, } // Prepare require.NoError(t, unittest.PrepareTestDatabase()) diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index b7e10ce85c..64866da076 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -5,6 +5,7 @@ package asymkey import ( "context" + "errors" "fmt" "strings" "time" @@ -209,7 +210,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified // deleteGPGKey does the actual key deletion func deleteGPGKey(ctx context.Context, keyID string) (int64, error) { if keyID == "" { - return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure + return 0, errors.New("empty KeyId forbidden") // Should never happen but just to be sure } // Delete imported key n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport)) diff --git a/models/asymkey/gpg_key_common.go b/models/asymkey/gpg_key_common.go index db1912c316..5b8a22fe63 100644 --- a/models/asymkey/gpg_key_common.go +++ b/models/asymkey/gpg_key_common.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto" "encoding/base64" + "errors" "fmt" "hash" "io" @@ -75,7 +76,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) { // Check type pkey, ok := p.(*packet.PublicKey) if !ok { - return nil, fmt.Errorf("key is not a public key") + return nil, errors.New("key is not a public key") } return pkey, nil } @@ -122,15 +123,15 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) { func extractSignature(s string) (*packet.Signature, error) { r, err := readArmoredSign(strings.NewReader(s)) if err != nil { - return nil, fmt.Errorf("Failed to read signature armor") + return nil, errors.New("Failed to read signature armor") } p, err := packet.Read(r) if err != nil { - return nil, fmt.Errorf("Failed to read signature packet") + return nil, errors.New("Failed to read signature packet") } sig, ok := p.(*packet.Signature) if !ok { - return nil, fmt.Errorf("Packet is not a signature") + return nil, errors.New("Packet is not a signature") } return sig, nil } diff --git a/models/asymkey/gpg_key_object_verification.go b/models/asymkey/gpg_key_object_verification.go index ccd31a38b1..745ed04869 100644 --- a/models/asymkey/gpg_key_object_verification.go +++ b/models/asymkey/gpg_key_object_verification.go @@ -6,6 +6,7 @@ package asymkey import ( "context" + "errors" "fmt" "hash" "strings" @@ -316,7 +317,7 @@ func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { // Check if key can sign if !k.CanSign { - return fmt.Errorf("key can not sign") + return errors.New("key can not sign") } // Decode key pkey, err := base64DecPubKey(k.Content) diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go index 305e464b4b..8177db6439 100644 --- a/models/asymkey/ssh_key_parse.go +++ b/models/asymkey/ssh_key_parse.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "encoding/binary" "encoding/pem" + "errors" "fmt" "math/big" "os" @@ -93,7 +94,7 @@ func parseKeyString(content string) (string, error) { block, _ := pem.Decode([]byte(content)) if block == nil { - return "", fmt.Errorf("failed to parse PEM block containing the public key") + return "", errors.New("failed to parse PEM block containing the public key") } if strings.Contains(block.Type, "PRIVATE") { return "", ErrKeyIsPrivate @@ -226,7 +227,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { // The ssh library can parse the key, so next we find out what key exactly we have. switch pkeyType { - case ssh.KeyAlgoDSA: + case ssh.KeyAlgoDSA: //nolint:staticcheck rawPub := struct { Name string P, Q, G, Y *big.Int diff --git a/models/db/log.go b/models/db/log.go index b94af8e39c..387709cc50 100644 --- a/models/db/log.go +++ b/models/db/log.go @@ -69,6 +69,9 @@ func (l *XORMLogBridge) Warn(v ...any) { // Warnf show warning log func (l *XORMLogBridge) Warnf(format string, v ...any) { + if format == "Table %s Column %s db default is %s, struct default is %s" || format == "Table %s Column %s db nullable is %v, struct nullable is %v" { + return + } l.Log(stackLevel, log.WARN, format, v...) } diff --git a/models/fixtures/TestGetUsedForUser/action_artifact.yaml b/models/fixtures/TestGetUsedForUser/action_artifact.yaml new file mode 100644 index 0000000000..db5392126d --- /dev/null +++ b/models/fixtures/TestGetUsedForUser/action_artifact.yaml @@ -0,0 +1,17 @@ +- + id: 1001 + run_id: 792 + runner_id: 1 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + storage_path: "27/5/1730330775594233150.chunk" + file_size: 693147180559 + file_compressed_size: 693147180559 + content_encoding: "application/zip" + artifact_path: "big-file.zip" + artifact_name: "big-file" + status: 4 + created_unix: 1730330775 + updated_unix: 1730330775 + expired_unix: 1738106775 diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index b2febb4ed8..f1592d4569 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -74,3 +74,11 @@ is_private: false created_unix: 1680454039 content: '4|' # issueId 5 + +- id: 10 + user_id: 40 + op_type: 1 # create repo + act_user_id: 40 + repo_id: 60 # public + is_private: false + created_unix: 1577404800 # end of heatmap \ No newline at end of file diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index 7a7bf34197..5b6f89ae0e 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -471,3 +471,64 @@ need_approval: 0 approved_by: 0 event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}' + + +# GET action run(s) test +- + id: 892 + title: "successful push run" + repo_id: 63 + owner_id: 2 + workflow_id: "success.yaml" + index: 1 + trigger_user_id: 2 + ref: "refs/heads/main" + commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee" + event: "push" + is_fork_pull_request: 0 + status: 1 # success + started: 1683636528 + stopped: 1683636626 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 + +- + id: 893 + title: "failed pull_request run" + repo_id: 63 + owner_id: 2 + workflow_id: "failed.yaml" + index: 2 + trigger_user_id: 2 + ref: "refs/heads/bugfix-1" + commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f" + event: "pull_request" + is_fork_pull_request: 0 + status: 2 # failure + started: 1683636528 + stopped: 1683636626 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 + +- + id: 894 + title: "running workflow_dispatch run" + repo_id: 63 + owner_id: 2 + workflow_id: "running.yaml" + index: 3 + trigger_user_id: 2 + ref: "refs/heads/main" + commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee" + event: "workflow_dispatch" + is_fork_pull_request: 0 + status: 6 # running + started: 1683636528 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 diff --git a/models/fixtures/follow.yml b/models/fixtures/follow.yml index b8d35828bf..da3d4a60c1 100644 --- a/models/fixtures/follow.yml +++ b/models/fixtures/follow.yml @@ -17,3 +17,13 @@ id: 4 user_id: 31 follow_id: 33 + +- + id: 5 + user_id: 4 + follow_id: 8 + +- + id: 6 + user_id: 5 + follow_id: 8 diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml index fc0e03bca1..c62d451868 100644 --- a/models/fixtures/hook_task.yml +++ b/models/fixtures/hook_task.yml @@ -18,7 +18,7 @@ id: 2 hook_id: 1 uuid: uuid2 - is_delivered: false + is_delivered: true - id: 3 @@ -40,4 +40,4 @@ id: 4 hook_id: 3 uuid: uuid4 - is_delivered: false + is_delivered: true diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index cd49a51796..773f238645 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -795,3 +795,10 @@ type: 10 config: "{}" created_unix: 946684810 + +- + id: 115 + repo_id: 63 + type: 10 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 0ba4d06e14..c383fa43ac 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -31,6 +31,8 @@ close_issues_via_commit_in_any_branch: false created_unix: 1731254961 updated_unix: 1731254961 + topics: '[]' + - id: 2 owner_id: 2 @@ -61,7 +63,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: true - + topics: '[]' - id: 3 owner_id: 3 @@ -94,6 +96,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1700000001 updated_unix: 1700000001 + topics: '[]' - id: 4 @@ -125,6 +128,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 5 @@ -158,6 +162,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1700000002 updated_unix: 1700000002 + topics: '[]' - id: 6 @@ -190,6 +195,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1710000001 updated_unix: 1710000001 + topics: '[]' - id: 7 @@ -222,6 +228,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1710000003 updated_unix: 1710000003 + topics: '[]' - id: 8 @@ -254,6 +261,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1710000002 updated_unix: 1710000002 + topics: '[]' - id: 9 @@ -284,6 +292,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 10 @@ -315,6 +324,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 11 @@ -346,6 +356,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 12 @@ -376,6 +387,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 13 @@ -406,6 +418,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 14 @@ -437,6 +450,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 15 @@ -468,6 +482,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 16 @@ -499,6 +514,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 17 @@ -529,6 +545,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 18 @@ -559,6 +576,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 19 @@ -589,6 +607,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 20 @@ -619,6 +638,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 21 @@ -649,6 +669,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 22 @@ -679,6 +700,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 23 @@ -709,6 +731,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 24 @@ -739,6 +762,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 25 @@ -769,6 +793,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 26 @@ -799,6 +824,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 27 @@ -829,6 +855,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 28 @@ -859,6 +886,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 29 @@ -889,6 +917,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 30 @@ -919,6 +948,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 31 @@ -950,6 +980,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 32 # org public repo @@ -982,6 +1013,7 @@ close_issues_via_commit_in_any_branch: false created_unix: 1700000003 updated_unix: 1700000003 + topics: '[]' - id: 33 @@ -1013,6 +1045,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 34 @@ -1043,6 +1076,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 35 @@ -1073,6 +1107,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 36 @@ -1104,6 +1139,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 37 @@ -1135,6 +1171,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 38 @@ -1166,6 +1203,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 39 @@ -1197,6 +1235,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 40 @@ -1228,6 +1267,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 41 @@ -1259,6 +1299,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 42 @@ -1290,6 +1331,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 43 @@ -1320,6 +1362,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 44 @@ -1351,6 +1394,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 45 @@ -1381,6 +1425,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 46 @@ -1412,6 +1457,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 47 @@ -1443,6 +1489,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 48 @@ -1474,6 +1521,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 49 @@ -1506,6 +1554,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 50 @@ -1537,6 +1586,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 51 @@ -1568,6 +1618,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 52 @@ -1599,6 +1650,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 53 @@ -1627,6 +1679,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 54 @@ -1639,6 +1692,7 @@ is_archived: false is_private: true status: 0 + topics: '[]' - id: 55 @@ -1651,6 +1705,7 @@ is_private: true num_issues: 1 status: 0 + topics: '[]' - id: 56 @@ -1664,6 +1719,7 @@ is_private: true status: 0 num_issues: 0 + topics: '[]' - id: 57 @@ -1677,6 +1733,7 @@ is_private: false status: 0 num_issues: 0 + topics: '[]' - id: 58 # org public repo @@ -1708,6 +1765,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 1059 @@ -1721,6 +1779,7 @@ is_private: false status: 0 num_issues: 0 + topics: '[]' - id: 59 @@ -1734,6 +1793,7 @@ is_private: true status: 0 num_issues: 0 + topics: '[]' - id: 60 @@ -1765,6 +1825,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 61 @@ -1796,6 +1857,7 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' - id: 62 owner_id: 2 @@ -1826,3 +1888,36 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + topics: '[]' + +- + id: 63 + owner_id: 2 + owner_name: user2 + lower_name: test_action_run_search + name: test_action_run_search + default_branch: main + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: true + is_empty: false + is_archived: false + is_mirror: false + status: 0 + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + is_fsck_enabled: true + close_issues_via_commit_in_any_branch: false + topics: '[]' diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 630505b8b4..1a03185cf1 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -53,6 +53,7 @@ login_source: 0 login_name: user2 type: 0 + website: https://keyoxide.org/eb114f5e6c0dc2bcdd183550a4b61a2dc5923710 salt: ZogKvWdyEx max_repo_creation: -1 is_active: true @@ -69,7 +70,7 @@ num_followers: 2 num_following: 1 num_stars: 2 - num_repos: 17 + num_repos: 18 num_teams: 0 num_members: 0 visibility: 0 @@ -93,7 +94,7 @@ login_name: org3 type: 1 salt: ZogKvWdyEx - max_repo_creation: -1 + max_repo_creation: 1000 is_active: false is_admin: false is_restricted: false @@ -143,7 +144,7 @@ avatar_email: user4@example.com use_custom_avatar: true num_followers: 0 - num_following: 1 + num_following: 2 num_stars: 0 num_repos: 0 num_teams: 0 @@ -181,7 +182,7 @@ avatar_email: user5@example.com use_custom_avatar: true num_followers: 0 - num_following: 0 + num_following: 1 num_stars: 0 num_repos: 1 num_teams: 0 @@ -294,7 +295,7 @@ avatar: "" avatar_email: user8@example.com use_custom_avatar: true - num_followers: 1 + num_followers: 3 num_following: 1 num_stars: 0 num_repos: 0 diff --git a/models/forgefed/federationhost_test.go b/models/forgefed/federationhost_test.go index 824495c9cb..d11affbae0 100644 --- a/models/forgefed/federationhost_test.go +++ b/models/forgefed/federationhost_test.go @@ -35,7 +35,7 @@ func Test_FederationHostValidation(t *testing.T) { HostSchema: "https", } if res, _ := validation.IsValid(sut); res { - t.Errorf("sut should be invalid: HostFqdn empty") + t.Error("sut should be invalid: HostFqdn empty") } sut = FederationHost{ @@ -48,7 +48,7 @@ func Test_FederationHostValidation(t *testing.T) { HostSchema: "https", } if res, _ := validation.IsValid(sut); res { - t.Errorf("sut should be invalid: HostFqdn too long (len=256)") + t.Error("sut should be invalid: HostFqdn too long (len=256)") } sut = FederationHost{ @@ -59,7 +59,7 @@ func Test_FederationHostValidation(t *testing.T) { HostSchema: "https", } if res, _ := validation.IsValid(sut); res { - t.Errorf("sut should be invalid: NodeInfo invalid") + t.Error("sut should be invalid: NodeInfo invalid") } sut = FederationHost{ @@ -72,7 +72,7 @@ func Test_FederationHostValidation(t *testing.T) { HostSchema: "https", } if res, _ := validation.IsValid(sut); res { - t.Errorf("sut should be invalid: Future timestamp") + t.Error("sut should be invalid: Future timestamp") } sut = FederationHost{ @@ -85,6 +85,6 @@ func Test_FederationHostValidation(t *testing.T) { HostSchema: "https", } if res, _ := validation.IsValid(sut); res { - t.Errorf("sut should be invalid: HostFqdn lower case") + t.Error("sut should be invalid: HostFqdn lower case") } } diff --git a/models/forgefed/nodeinfo_test.go b/models/forgefed/nodeinfo_test.go index 9e37e77100..a0c9781b90 100644 --- a/models/forgefed/nodeinfo_test.go +++ b/models/forgefed/nodeinfo_test.go @@ -4,7 +4,7 @@ package forgefed import ( - "fmt" + "errors" "reflect" "strings" "testing" @@ -28,7 +28,7 @@ func Test_NodeInfoWellKnownUnmarshalJSON(t *testing.T) { }, "empty": { item: []byte(``), - wantErr: fmt.Errorf("cannot parse JSON: cannot parse empty string; unparsed tail: \"\""), + wantErr: errors.New("cannot parse JSON: cannot parse empty string; unparsed tail: \"\""), }, } @@ -74,7 +74,7 @@ func Test_NewNodeInfoWellKnown(t *testing.T) { _, err := NewNodeInfoWellKnown([]byte(`invalid`)) if err == nil { - t.Errorf("error was expected here") + t.Error("error was expected here") } } @@ -87,6 +87,6 @@ func Test_NewNodeInfo(t *testing.T) { _, err := NewNodeInfo([]byte(`invalid`)) if err == nil { - t.Errorf("error was expected here") + t.Error("error was expected here") } } diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 73226c525f..21a2077d06 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -5,6 +5,7 @@ package forgejo_migrations //nolint:revive import ( "context" + "errors" "fmt" "os" @@ -98,6 +99,10 @@ var migrations = []*Migration{ NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation), // v29 -> v30 NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI), + // v30 -> v31 + NewMigration("Normalize repository.topics to empty slice instead of null", SetTopicsAsEmptySlice), + // v31 -> v32 + NewMigration("Migrate maven package name concatenation", ChangeMavenArtifactConcatenation), } // GetCurrentDBVersion returns the current Forgejo database version. @@ -130,7 +135,7 @@ func EnsureUpToDate(x *xorm.Engine) error { } if currentDB < 0 { - return fmt.Errorf("database has not been initialized") + return errors.New("database has not been initialized") } expected := ExpectedVersion() diff --git a/models/forgejo_migrations/v31.go b/models/forgejo_migrations/v31.go new file mode 100644 index 0000000000..fdcab21b1a --- /dev/null +++ b/models/forgejo_migrations/v31.go @@ -0,0 +1,58 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import ( + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func SetTopicsAsEmptySlice(x *xorm.Engine) error { + var err error + switch x.Dialect().URI().DBType { + case schemas.MYSQL: + _, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics = 'null'") + case schemas.SQLITE: + _, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics = 'null'") + case schemas.POSTGRES: + _, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics::text = 'null'") + } + + if err != nil { + return err + } + + if x.Dialect().URI().DBType == schemas.SQLITE { + sessMigration := x.NewSession() + defer sessMigration.Close() + if err := sessMigration.Begin(); err != nil { + return err + } + _, err = sessMigration.Exec("ALTER TABLE `repository` RENAME COLUMN `topics` TO `topics_backup`") + if err != nil { + return err + } + _, err = sessMigration.Exec("ALTER TABLE `repository` ADD COLUMN `topics` TEXT NOT NULL DEFAULT '[]'") + if err != nil { + return err + } + _, err = sessMigration.Exec("UPDATE `repository` SET `topics` = `topics_backup`") + if err != nil { + return err + } + _, err = sessMigration.Exec("ALTER TABLE `repository` DROP COLUMN `topics_backup`") + if err != nil { + return err + } + + return sessMigration.Commit() + } + + type Repository struct { + ID int64 `xorm:"pk autoincr"` + Topics []string `xorm:"TEXT JSON NOT NULL"` + } + + return x.Sync(new(Repository)) +} diff --git a/models/forgejo_migrations/v31_test.go b/models/forgejo_migrations/v31_test.go new file mode 100644 index 0000000000..5b4aac2a60 --- /dev/null +++ b/models/forgejo_migrations/v31_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import ( + "testing" + + migration_tests "forgejo.org/models/migrations/test" + + "github.com/stretchr/testify/require" +) + +func Test_SetTopicsAsEmptySlice(t *testing.T) { + type Repository struct { + ID int64 `xorm:"pk autoincr"` + Topics []string `xorm:"TEXT JSON"` + } + + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Repository)) + defer deferable() + if x == nil || t.Failed() { + return + } + + require.NoError(t, SetTopicsAsEmptySlice(x)) + + var repos []Repository + require.NoError(t, x.Find(&repos)) + + for _, repo := range repos { + if repo.ID == 2 { + require.Equal(t, []string{"go", "dev"}, repo.Topics, "Valid topics should remain unchanged") + } else { + require.Equal(t, []string{}, repo.Topics, "NULL topics should be set to empty array") + } + } +} diff --git a/models/forgejo_migrations/v32.go b/models/forgejo_migrations/v32.go new file mode 100644 index 0000000000..bed335ab6b --- /dev/null +++ b/models/forgejo_migrations/v32.go @@ -0,0 +1,414 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import ( + "encoding/xml" + "fmt" + "regexp" + "slices" + "sort" + "strconv" + "strings" + + "forgejo.org/models/packages" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/packages/maven" + packages_service "forgejo.org/services/packages" + + "golang.org/x/net/context" + "xorm.io/xorm" +) + +var getPackage = packages_service.GetPackageFileStream + +type Snapshot struct { + baseVersion string + date string + time string + build int +} + +type Metadata struct { + XMLName xml.Name `xml:"metadata"` + ModelVersion string `xml:"modelVersion,attr"` + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Version string `xml:"version"` +} + +type mavenPackageResult struct { + PackageFile *packages.PackageFile `xorm:"extends"` + PackageVersion *packages.PackageVersion `xorm:"extends"` + Package *packages.Package `xorm:"extends"` + PackageName string `xorm:"-"` + Snapshot *Snapshot `xorm:"-"` + GroupID string `xorm:"-"` + ArtifactID string `xorm:"-"` +} + +// ChangeMavenArtifactConcatenation resolves old dash-concatenated Maven coordinates and regenerates metadata. +// Note: runs per-owner in a single transaction; failures roll back all owners. +func ChangeMavenArtifactConcatenation(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + // get unique owner IDs of Maven packages + var ownerIDs []*int64 + if err := sess. + Table("package"). + Select("package.owner_id"). + Where("package.type = 'maven'"). + GroupBy("package.owner_id"). + OrderBy("package.owner_id DESC"). + Find(&ownerIDs); err != nil { + return err + } + + for _, id := range ownerIDs { + if err := fixMavenArtifactPerOwner(sess, id); err != nil { + log.Error("owner %d migration failed: %v", id, err) + return err // rollback all + } + } + + return sess.Commit() +} + +func fixMavenArtifactPerOwner(sess *xorm.Session, ownerID *int64) error { + results, err := getMavenPackageResultsToUpdate(sess, ownerID) + if err != nil { + return err + } + + if err = resolvePackageCollisions(results, sess); err != nil { + return err + } + + if err = processPackageVersions(results, sess); err != nil { + return err + } + + return processPackageFiles(results, sess) +} + +// processPackageFiles updates Maven package files and versions in the database +// Returns an error if any database or processing operation fails. +func processPackageFiles(results []*mavenPackageResult, sess *xorm.Session) error { + processedVersion := make(map[string][]*mavenPackageResult) + + for _, r := range results { + if r.Snapshot != nil { + key := fmt.Sprintf("%s:%s", r.PackageName, r.PackageVersion.LowerVersion) + processedVersion[key] = append(processedVersion[key], r) + } + + // Only update version_id when it differs + if r.PackageVersion.ID != r.PackageFile.VersionID { + pattern := strings.TrimSuffix(r.PackageFile.Name, ".pom") + "%" + // Per routers/api/packages/maven/maven.go:338, POM files already have the `IsLead`, so no update needed for this prop + if _, err := sess.Exec("UPDATE package_file SET version_id = ? WHERE version_id = ? and name like ?", r.PackageVersion.ID, r.PackageFile.VersionID, pattern); err != nil { + return err + } + } + } + + // If maven-metadata.xml is missing (snapshot path collision), skip regeneration + // Without this metadata, Maven cannot resolve snapshot details + for _, packageResults := range processedVersion { + sort.Slice(packageResults, func(i, j int) bool { + return packageResults[i].Snapshot.build > packageResults[j].Snapshot.build + }) + + rs := packageResults[0] + + pf, md, err := parseMetadata(sess, rs) + if err != nil { + return err + } + + if pf != nil && md != nil && md.GroupID == rs.GroupID && md.ArtifactID == rs.ArtifactID { + if pf.VersionID != rs.PackageFile.VersionID { + if _, err := sess.ID(pf.ID).Cols("version_id").Update(pf); err != nil { + return err + } + } + continue + } + + log.Warn("no maven-metadata.xml found for (id: %d) [%s:%s]", rs.PackageVersion.ID, rs.PackageName, rs.PackageVersion.Version) + } + + return nil +} + +// parseMetadata retrieves metadata for a Maven package file from the database and decodes it into a Metadata object. +// Returns the associated PackageFile, Metadata, and any error encountered during processing. +func parseMetadata(sess *xorm.Session, snapshot *mavenPackageResult) (*packages.PackageFile, *Metadata, error) { + ctx := context.Background() + + var pf packages.PackageFile + found, err := sess.Table(pf). + Where("version_id = ?", snapshot.PackageFile.VersionID). // still the old id + And("lower_name = ?", "maven-metadata.xml"). + Get(&pf) + if err != nil { + return nil, nil, err + } + + if !found { + return nil, nil, nil + } + + s, _, _, err := getPackage(ctx, &pf) + if err != nil { + return nil, nil, err + } + + defer s.Close() + dec := xml.NewDecoder(s) + var m Metadata + if err := dec.Decode(&m); err != nil { + return nil, nil, err + } + + return &pf, &m, nil +} + +// processPackageVersions processes Maven package versions by updating metadata or inserting new records as necessary. +// It avoids redundant updates by tracking already processed versions using a map. Returns an error on failure. +func processPackageVersions(results []*mavenPackageResult, sess *xorm.Session) error { + processedVersion := make(map[string]int64) + + for _, r := range results { + key := fmt.Sprintf("%s:%s", r.PackageName, r.PackageVersion.Version) + + if id, ok := processedVersion[key]; ok { + r.PackageVersion.ID = id + continue + } + + // for non collisions, just update the metadata + if r.PackageVersion.PackageID == r.Package.ID { + if _, err := sess.ID(r.PackageVersion.ID).Cols("metadata_json").Update(r.PackageVersion); err != nil { + return err + } + } else { + log.Info("Create new maven package version for %s:%s", r.PackageName, r.PackageVersion.Version) + r.PackageVersion.ID = 0 + r.PackageVersion.PackageID = r.Package.ID + if _, err := sess.Insert(r.PackageVersion); err != nil { + return err + } + } + + processedVersion[key] = r.PackageVersion.ID + } + + return nil +} + +// getMavenPackageResultsToUpdate retrieves Maven package results that need updates based on the owner ID. +// It processes POM metadata, fixes package inconsistencies, and filters corrupted package versions. +func getMavenPackageResultsToUpdate(sess *xorm.Session, ownerID *int64) ([]*mavenPackageResult, error) { + ctx := context.Background() + var candidates []*mavenPackageResult + if err := sess. + Table("package_file"). + Select("package_file.*, package_version.*, package.*"). + Join("INNER", "package_version", "package_version.id = package_file.version_id"). + Join("INNER", "package", "package.id = package_version.package_id"). + Where("package_file.lower_name LIKE ?", "%.pom"). + And("package.type = ?", "maven"). + And("package.owner_id = ?", ownerID). + OrderBy("package_version.id DESC, package_file.id DESC"). + Find(&candidates); err != nil { + return nil, err + } + + var results []*mavenPackageResult + var corruptedVersionIDs []int64 + + // fetch actual metadata from blob as all packages needs to be fixed following the new string concatenation + for _, r := range candidates { + if err := processPomMetadata(ctx, r); err != nil { + // Skip corrupted versions; admin intervention may be needed to repair these files. + log.Warn("Failed to process package file [id: %d] ignoring package version[%d]: %v", r.PackageFile.ID, r.PackageVersion.ID, err) + + corruptedVersionIDs = append(corruptedVersionIDs, r.PackageVersion.ID) + + continue + } + + results = append(results, r) + log.Debug("Resolved id [%d] from [%s:%s] to [%s:%s] [Snapshot: %v]", r.Package.ID, r.Package.Name, r.PackageVersion.Version, r.PackageName, r.PackageVersion.Version, r.Snapshot) + } + + for _, corruptedVersionID := range corruptedVersionIDs { + for i := 0; i < len(results); { + if corruptedVersionID == results[i].PackageVersion.ID { + results = append(results[:i], results[i+1:]...) + } else { + i++ + } + } + } + + return results, nil +} + +// resolvePackageCollisions handles name collisions by keeping the first existing record and inserting new Package records for subsequent collisions. +// Returns a map from PackageName to its resolved Package.ID. +func resolvePackageCollisions(results []*mavenPackageResult, sess *xorm.Session) error { + // Group new names by lowerName + collisions := make(map[string][]string) + for _, r := range results { + names := collisions[r.Package.LowerName] + if !slices.Contains(names, r.PackageName) { + collisions[r.Package.LowerName] = append(names, r.PackageName) + } + } + + pkgIDByName := make(map[string]int64) + var err error + + for _, r := range results { + list := collisions[r.Package.LowerName] + + // update to the upcoming package name which is colon separated + r.Package.Name = r.PackageName + r.Package.LowerName = r.PackageName + + // exiting entry + if id, ok := pkgIDByName[r.PackageName]; ok { + r.Package.ID = id + // first package kept the current id + } else if list[0] == r.PackageName { + pkgIDByName[r.PackageName] = r.Package.ID + + if _, err = sess.ID(r.Package.ID).Cols("name", "lower_name").Update(r.Package); err != nil { + return err + } + // create a new entry + } else { + log.Info("Create new maven package for %s", r.Package.Name) + + r.Package.ID = 0 + if _, err = sess.Insert(r.Package); err != nil { + return err + } + + pkgIDByName[r.PackageName] = r.Package.ID + } + } + + return nil +} + +// processPomMetadata processes a Maven package file, parses its POM metadata, and updates PackageVersion information. +func processPomMetadata(ctx context.Context, mpr *mavenPackageResult) error { + s, _, _, err := getPackage(ctx, mpr.PackageFile) + if err != nil { + return fmt.Errorf("unable to get package stream: %v", err) + } + defer s.Close() + + actualPom, err := maven.ParsePackageMetaData(s) + if err != nil { + return fmt.Errorf("failed to parse POM metadata: %v", err) + } + + raw, err := json.Marshal(actualPom) + if err != nil { + return fmt.Errorf("failed to marshal metadata: %v", err) + } + + var currentPom *maven.Metadata + if err = json.Unmarshal([]byte(mpr.PackageVersion.MetadataJSON), ¤tPom); err != nil { + return fmt.Errorf("failed to unmarshal metadata: %v", err) + } + + // since the rest api can also be (ab)used to upload artifacts wrong, just ignore them + if isInvalidMatch(currentPom, actualPom) { + return fmt.Errorf("artifact mismatch: actual [%s] expected [%s]", actualPom.ArtifactID, currentPom.ArtifactID) + } + + // this will also fix packages that missed its groupID + // Ref: https://codeberg.org/forgejo/forgejo/pulls/6329 + mpr.PackageVersion.MetadataJSON = string(raw) + + // Since Maven packages are case-sensitive, avoid potential clashes and clean-ups + // by enforcing consistent case handling similar to RPM packages. + mpr.PackageName = fmt.Sprintf("%s:%s", actualPom.GroupID, actualPom.ArtifactID) + + mpr.GroupID = actualPom.GroupID + mpr.ArtifactID = actualPom.ArtifactID + + if strings.HasSuffix(mpr.PackageVersion.Version, "-SNAPSHOT") { + snap, err := extraSnapshotDetails(currentPom, actualPom, mpr) + if err != nil { + return err + } + mpr.Snapshot = snap + } else { + // only snapshots are affected but kept in case of not complete fixtures + expectedFileName := fmt.Sprintf("%s-%s.pom", actualPom.ArtifactID, mpr.PackageVersion.Version) + if mpr.PackageFile.Name != expectedFileName { + log.Warn("invalid package file name - this is a collision which needs to be resolved expected [%s], actual [%s]", expectedFileName, mpr.PackageFile.Name) + } + } + + return nil +} + +// extraSnapshotDetails extracts detailed snapshot information +// Returns a Snapshot object encapsulating the extracted details or an error if the filename is invalid or parsing fails. +func extraSnapshotDetails(currentPom, actualPom *maven.Metadata, mpr *mavenPackageResult) (*Snapshot, error) { + pattern := `^%s-` + + `(?P[\d\.]+)-` + + `(?P\d{8})\.` + + `(?P