diff --git a/.deadcode-out b/.deadcode-out
index 3f21d8ef36..92937ab0ed 100644
--- a/.deadcode-out
+++ b/.deadcode-out
@@ -100,6 +100,8 @@ package "code.gitea.io/gitea/models/unittest"
func LoadFixtures
func Copy
func CopyDir
+ func NewMockWebServer
+ func NormalizedFullPath
func FixturesDir
func fatalTestError
func InitSettings
@@ -322,6 +324,7 @@ package "code.gitea.io/gitea/services/pull"
package "code.gitea.io/gitea/services/repository"
func GetBranchCommitID
func IsErrForkAlreadyExist
+ func UpdateRepositoryUnits
package "code.gitea.io/gitea/services/repository/archiver"
func ArchiveRepository
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 1886787e33..ce98f05632 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -89,11 +89,6 @@
"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/NYTimes/gziphandler",
- "path": "github.com/NYTimes/gziphandler/LICENSE",
- "licenseText": " 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 2016-2017 The New York Times Company\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/ProtonMail/go-crypto",
"path": "github.com/ProtonMail/go-crypto/LICENSE",
@@ -664,6 +659,11 @@
"path": "github.com/klauspost/compress/LICENSE",
"licenseText": "Copyright (c) 2012 The Go Authors. All rights reserved.\nCopyright (c) 2019 Klaus Post. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(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\n------------------\n\nFiles: gzhttp/*\n\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 2016-2017 The New York Times Company\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\n------------------\n\nFiles: s2/cmd/internal/readahead/*\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Klaus Post\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\n---------------------\nFiles: snappy/*\nFiles: internal/snapref/*\n\nCopyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(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\n-----------------\n\nFiles: s2/cmd/internal/filepathx/*\n\nCopyright 2016 The filepathx Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
+ {
+ "name": "github.com/klauspost/compress/gzhttp",
+ "path": "github.com/klauspost/compress/gzhttp/LICENSE",
+ "licenseText": " 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 2016-2017 The New York Times Company\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/klauspost/compress/internal/snapref",
"path": "github.com/klauspost/compress/internal/snapref/LICENSE",
diff --git a/contrib/ide/vscode/settings.json b/contrib/ide/vscode/settings.json
index e33bccf902..2ec666f3c1 100644
--- a/contrib/ide/vscode/settings.json
+++ b/contrib/ide/vscode/settings.json
@@ -1,4 +1,4 @@
{
- "go.buildTags": "'sqlite sqlite_unlock_notify'",
+ "go.buildTags": "sqlite,sqlite_unlock_notify",
"go.testFlags": ["-v"]
-}
\ No newline at end of file
+}
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index e412c3b9c6..d1d22aa536 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -412,6 +412,10 @@ USER = root
;;
;; Whether execute database models migrations automatically
;AUTO_MIGRATION = true
+;;
+;; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
+;;
+;SLOW_QUERY_TRESHOLD = 5s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -817,6 +821,11 @@ LEVEL = Info
;; Every new user will have restricted permissions depending on this setting
;DEFAULT_USER_IS_RESTRICTED = false
;;
+;; Users will be able to use dots when choosing their username. Disabling this is
+;; helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
+;; extensions that use strange regex patterns.
+; ALLOW_DOTS_IN_USERNAMES = true
+;;
;; Either "public", "limited" or "private", default is "public"
;; Limited is for users visible only to signed users
;; Private is for users visible only to members of their organizations
@@ -903,6 +912,14 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;[badges]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Enable repository badges (via shields.io or a similar generator)
+;ENABLED = true
+;; Template for the badge generator.
+;GENERATOR_URL_TEMPLATE = https://img.shields.io/badge/{{.label}}-{{.text}}-{{.color}}
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1470,6 +1487,8 @@ LEVEL = Info
;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
+;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
+;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1783,9 +1802,6 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
-;AVATAR_UPLOAD_PATH = data/avatars
-;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
-;;
;; How Gitea deals with missing repository avatars
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
;REPOSITORY_AVATAR_FALLBACK = none
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index eb9b8d1ae9..eae956798f 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -458,6 +458,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
+- `SLOW_QUERY_TRESHOLD` **5s**: Threshold value in seconds beyond which query execution time is logged as a warning in the xorm logger.
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
@@ -517,6 +518,7 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
+- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act.
## Security (`security`)
diff --git a/go.mod b/go.mod
index ed6d991b5f..83cbdbfe68 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,6 @@ require (
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
- github.com/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.8.1
github.com/alecthomas/chroma/v2 v2.12.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@@ -77,14 +76,12 @@ require (
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/minio/minio-go/v7 v7.0.66
- github.com/minio/sha256-simd v1.0.1
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc5
- github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.17.0
github.com/quasoft/websspi v1.1.2
@@ -100,7 +97,6 @@ require (
github.com/ulikunitz/xz v0.5.11
github.com/urfave/cli/v2 v2.26.0
github.com/xanzy/go-gitlab v0.95.2
- github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.6.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
@@ -232,6 +228,7 @@ require (
github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.57 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
+ github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
@@ -247,6 +244,7 @@ require (
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pierrec/lz4/v4 v4.1.19 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
@@ -277,8 +275,6 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
- github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
- github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
diff --git a/go.sum b/go.sum
index ec43f6aa0f..60601f0815 100644
--- a/go.sum
+++ b/go.sum
@@ -93,8 +93,6 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
-github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
-github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
@@ -837,13 +835,6 @@ github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23n
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
-github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
-github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
-github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
diff --git a/models/actions/run.go b/models/actions/run.go
index fcac58d515..9c7f049bbc 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -171,14 +171,13 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
}
// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
-func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
+func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error {
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
- RepoID: repoID,
- Ref: ref,
- WorkflowID: workflowID,
- TriggerEvent: event,
- Status: []Status{StatusRunning, StatusWaiting},
+ RepoID: repoID,
+ Ref: ref,
+ WorkflowID: workflowID,
+ Status: []Status{StatusRunning, StatusWaiting},
})
if err != nil {
return err
@@ -312,6 +311,32 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return commiter.Commit()
}
+func GetLatestRun(ctx context.Context, repoID int64) (*ActionRun, error) {
+ var run ActionRun
+ has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).OrderBy("id DESC").Limit(1).Get(&run)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, fmt.Errorf("latest run: %w", util.ErrNotExist)
+ }
+ return &run, nil
+}
+
+func GetLatestRunForBranchAndWorkflow(ctx context.Context, repoID int64, branch, workflowFile, event string) (*ActionRun, error) {
+ var run ActionRun
+ q := db.GetEngine(ctx).Where("repo_id=?", repoID).And("ref=?", branch).And("workflow_id=?", workflowFile)
+ if event != "" {
+ q = q.And("event=?", event)
+ }
+ has, err := q.Desc("id").Get(&run)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, util.NewNotExistErrorf("run with repo_id %d, ref %s, workflow_id %s", repoID, branch, workflowFile)
+ }
+ return &run, nil
+}
+
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
var run ActionRun
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
diff --git a/models/actions/run_list.go b/models/actions/run_list.go
index 388bfc4f86..375c46221b 100644
--- a/models/actions/run_list.go
+++ b/models/actions/run_list.go
@@ -10,7 +10,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
- webhook_module "code.gitea.io/gitea/modules/webhook"
"xorm.io/builder"
)
@@ -72,7 +71,6 @@ type FindRunOptions struct {
WorkflowID string
Ref string // the commit/tag/… that caused this workflow
TriggerUserID int64
- TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true
Status []Status
}
@@ -100,9 +98,6 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.Ref != "" {
cond = cond.And(builder.Eq{"ref": opts.Ref})
}
- if opts.TriggerEvent != "" {
- cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
- }
return cond
}
diff --git a/models/actions/schedule.go b/models/actions/schedule.go
index d450e7aa07..34d23f1c01 100644
--- a/models/actions/schedule.go
+++ b/models/actions/schedule.go
@@ -5,7 +5,6 @@ package actions
import (
"context"
- "fmt"
"time"
"code.gitea.io/gitea/models/db"
@@ -119,22 +118,3 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit()
}
-
-func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
- // If actions disabled when there is schedule task, this will remove the outdated schedule tasks
- // There is no other place we can do this because the app.ini will be changed manually
- if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
- return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
- }
- // cancel running cron jobs of this repository and delete old schedules
- if err := CancelRunningJobs(
- ctx,
- repo.ID,
- repo.DefaultBranch,
- "",
- webhook_module.HookEventSchedule,
- ); err != nil {
- return fmt.Errorf("CancelRunningJobs: %v", err)
- }
- return nil
-}
diff --git a/models/asymkey/main_test.go b/models/asymkey/main_test.go
index be71f848d9..87b5c22c4a 100644
--- a/models/asymkey/main_test.go
+++ b/models/asymkey/main_test.go
@@ -14,6 +14,7 @@ func TestMain(m *testing.M) {
FixtureFiles: []string{
"gpg_key.yml",
"public_key.yml",
+ "TestParseCommitWithSSHSignature/public_key.yml",
"deploy_key.yml",
"gpg_key_import.yml",
"user.yml",
diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go
index 267ab252c8..2b15450c98 100644
--- a/models/asymkey/ssh_key_authorized_keys.go
+++ b/models/asymkey/ssh_key_authorized_keys.go
@@ -169,7 +169,12 @@ func RewriteAllPublicKeys(ctx context.Context) error {
return err
}
- t.Close()
+ if err := t.Sync(); err != nil {
+ return err
+ }
+ if err := t.Close(); err != nil {
+ return err
+ }
return util.Rename(tmpPath, fPath)
}
diff --git a/models/asymkey/ssh_key_authorized_principals.go b/models/asymkey/ssh_key_authorized_principals.go
index 107d70c766..f3017c3089 100644
--- a/models/asymkey/ssh_key_authorized_principals.go
+++ b/models/asymkey/ssh_key_authorized_principals.go
@@ -92,7 +92,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
return err
}
- t.Close()
+ if err := t.Sync(); err != nil {
+ return err
+ }
+ if err := t.Close(); err != nil {
+ return err
+ }
return util.Rename(tmpPath, fPath)
}
diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_commit_verification.go
index 27c6df3578..2b802710a8 100644
--- a/models/asymkey/ssh_key_commit_verification.go
+++ b/models/asymkey/ssh_key_commit_verification.go
@@ -39,6 +39,12 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
log.Error("GetEmailAddresses: %v", err)
}
+ // Add the noreply email address as verified address.
+ committerEmailAddresses = append(committerEmailAddresses, &user_model.EmailAddress{
+ IsActivated: true,
+ Email: committer.GetPlaceholderEmail(),
+ })
+
activated := false
for _, e := range committerEmailAddresses {
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
diff --git a/models/asymkey/ssh_key_commit_verification_test.go b/models/asymkey/ssh_key_commit_verification_test.go
new file mode 100644
index 0000000000..e1ed409bd7
--- /dev/null
+++ b/models/asymkey/ssh_key_commit_verification_test.go
@@ -0,0 +1,146 @@
+// Copyright 2023 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseCommitWithSSHSignature(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
+
+ t.Run("No commiter", func(t *testing.T) {
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{})
+ assert.False(t, commitVerification.Verified)
+ assert.Equal(t, NoKeyFound, commitVerification.Reason)
+ })
+
+ t.Run("Commiter without keys", func(t *testing.T) {
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user)
+ assert.False(t, commitVerification.Verified)
+ assert.Equal(t, NoKeyFound, commitVerification.Reason)
+ })
+
+ t.Run("Correct signature with wrong email", func(t *testing.T) {
+ gitCommit := &git.Commit{
+ Committer: &git.Signature{
+ Email: "non-existent",
+ },
+ Signature: &git.CommitGPGSignature{
+ Payload: `tree 2d491b2985a7ff848d5c02748e7ea9f9f7619f9f
+parent 45b03601635a1f463b81963a4022c7f87ce96ef9
+author user2 1699710556 +0100
+committer user2 1699710556 +0100
+
+Using email that isn't known to Forgejo
+`,
+ Signature: `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95
+f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
+/bS1LX1lZNuzm2LR2qEgw=
+-----END SSH SIGNATURE-----
+`,
+ },
+ }
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ assert.False(t, commitVerification.Verified)
+ assert.Equal(t, NoKeyFound, commitVerification.Reason)
+ })
+
+ t.Run("Incorrect signature with correct email", func(t *testing.T) {
+ gitCommit := &git.Commit{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ },
+ Signature: &git.CommitGPGSignature{
+ Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f
+parent c2780d5c313da2a947eae22efd7dacf4213f4e7f
+author user2 1699707877 +0100
+committer user2 1699707877 +0100
+
+Add content
+`,
+ Signature: `-----BEGIN SSH SIGNATURE-----`,
+ },
+ }
+
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ assert.False(t, commitVerification.Verified)
+ assert.Equal(t, NoKeyFound, commitVerification.Reason)
+ })
+
+ t.Run("Valid signature with correct email", func(t *testing.T) {
+ gitCommit := &git.Commit{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ },
+ Signature: &git.CommitGPGSignature{
+ Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f
+parent c2780d5c313da2a947eae22efd7dacf4213f4e7f
+author user2 1699707877 +0100
+committer user2 1699707877 +0100
+
+Add content
+`,
+ Signature: `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95
+f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQBe2Fwk/FKY3SBCnG6jSYcO6ucyahp2SpQ/0P+otslzIHpWNW8cQ0fGLdhhaFynJXQ
+fs9cMpZVM9BfIKNUSO8QY=
+-----END SSH SIGNATURE-----
+`,
+ },
+ }
+
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ assert.True(t, commitVerification.Verified)
+ assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
+ assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
+ })
+
+ t.Run("Valid signature with noreply email", func(t *testing.T) {
+ defer test.MockVariableValue(&setting.Service.NoReplyAddress, "noreply.example.com")()
+
+ gitCommit := &git.Commit{
+ Committer: &git.Signature{
+ Email: "user2@noreply.example.com",
+ },
+ Signature: &git.CommitGPGSignature{
+ Payload: `tree 4836c7f639f37388bab4050ef5c97bbbd54272fc
+parent 795be1b0117ea5c65456050bb9fd84744d4fd9c6
+author user2 1699709594 +0100
+committer user2 1699709594 +0100
+
+Commit with noreply
+`,
+ Signature: `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95
+f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQJz83KKxD6Bz/ZvNpqkA3RPOSQ4LQ5FfEItbtoONkbwV9wAWMnmBqgggo/lnXCJ3oq
+muPLbvEduU+Ze/1Ol1pgk=
+-----END SSH SIGNATURE-----
+`,
+ },
+ }
+
+ commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ assert.True(t, commitVerification.Verified)
+ assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
+ assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
+ })
+}
diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go
index fe57276700..003ca5c9ab 100644
--- a/models/auth/access_token_scope.go
+++ b/models/auth/access_token_scope.go
@@ -250,7 +250,7 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
remainingScopes = remainingScopes[i+1:]
}
singleScope := AccessTokenScope(v)
- if singleScope == "" {
+ if singleScope == "" || singleScope == "sudo" {
continue
}
if singleScope == AccessTokenScopeAll {
diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go
index a6097e45d7..d11c5e6a3d 100644
--- a/models/auth/access_token_scope_test.go
+++ b/models/auth/access_token_scope_test.go
@@ -20,7 +20,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
tests := []scopeTestNormalize{
{"", "", nil},
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
- {"all", "all", nil},
+ {"all,sudo", "all", nil},
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil},
}
diff --git a/models/auth/session_test.go b/models/auth/session_test.go
new file mode 100644
index 0000000000..3475fdd2cd
--- /dev/null
+++ b/models/auth/session_test.go
@@ -0,0 +1,142 @@
+// Copyright 2023 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package auth_test
+
+import (
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAuthSession(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ defer timeutil.MockUnset()
+
+ key := "I-Like-Free-Software"
+
+ t.Run("Create Session", func(t *testing.T) {
+ // Ensure it doesn't exist.
+ ok, err := auth.ExistSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.False(t, ok)
+
+ preCount, err := auth.CountSessions(db.DefaultContext)
+ assert.NoError(t, err)
+
+ now := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
+ timeutil.MockSet(now)
+
+ // New session is created.
+ sess, err := auth.ReadSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.EqualValues(t, key, sess.Key)
+ assert.Empty(t, sess.Data)
+ assert.EqualValues(t, now.Unix(), sess.Expiry)
+
+ // Ensure it exists.
+ ok, err = auth.ExistSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Ensure the session is taken into account for count..
+ postCount, err := auth.CountSessions(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Greater(t, postCount, preCount)
+ })
+
+ t.Run("Update session", func(t *testing.T) {
+ data := []byte{0xba, 0xdd, 0xc0, 0xde}
+ now := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
+ timeutil.MockSet(now)
+
+ // Update session.
+ err := auth.UpdateSession(db.DefaultContext, key, data)
+ assert.NoError(t, err)
+
+ timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
+
+ // Read updated session.
+ // Ensure data is updated and expiry is set from the update session call.
+ sess, err := auth.ReadSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.EqualValues(t, key, sess.Key)
+ assert.EqualValues(t, data, sess.Data)
+ assert.EqualValues(t, now.Unix(), sess.Expiry)
+
+ timeutil.MockSet(now)
+ })
+
+ t.Run("Delete session", func(t *testing.T) {
+ // Ensure it't exist.
+ ok, err := auth.ExistSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ preCount, err := auth.CountSessions(db.DefaultContext)
+ assert.NoError(t, err)
+
+ err = auth.DestroySession(db.DefaultContext, key)
+ assert.NoError(t, err)
+
+ // Ensure it doens't exists.
+ ok, err = auth.ExistSession(db.DefaultContext, key)
+ assert.NoError(t, err)
+ assert.False(t, ok)
+
+ // Ensure the session is taken into account for count..
+ postCount, err := auth.CountSessions(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Less(t, postCount, preCount)
+ })
+
+ t.Run("Cleanup sessions", func(t *testing.T) {
+ timeutil.MockSet(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC))
+
+ _, err := auth.ReadSession(db.DefaultContext, "sess-1")
+ assert.NoError(t, err)
+
+ // One minute later.
+ timeutil.MockSet(time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC))
+ _, err = auth.ReadSession(db.DefaultContext, "sess-2")
+ assert.NoError(t, err)
+
+ // 5 minutes, shouldn't clean up anything.
+ err = auth.CleanupSessions(db.DefaultContext, 5*60)
+ assert.NoError(t, err)
+
+ ok, err := auth.ExistSession(db.DefaultContext, "sess-1")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ ok, err = auth.ExistSession(db.DefaultContext, "sess-2")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // 1 minute, should clean up sess-1.
+ err = auth.CleanupSessions(db.DefaultContext, 60)
+ assert.NoError(t, err)
+
+ ok, err = auth.ExistSession(db.DefaultContext, "sess-1")
+ assert.NoError(t, err)
+ assert.False(t, ok)
+
+ ok, err = auth.ExistSession(db.DefaultContext, "sess-2")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ // Now, should clean up sess-2.
+ err = auth.CleanupSessions(db.DefaultContext, 0)
+ assert.NoError(t, err)
+
+ ok, err = auth.ExistSession(db.DefaultContext, "sess-2")
+ assert.NoError(t, err)
+ assert.False(t, ok)
+ })
+}
diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go
index 51061e5205..d0c341a192 100644
--- a/models/auth/twofactor.go
+++ b/models/auth/twofactor.go
@@ -6,6 +6,7 @@ package auth
import (
"context"
"crypto/md5"
+ "crypto/sha256"
"crypto/subtle"
"encoding/base32"
"encoding/base64"
@@ -18,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
- "github.com/minio/sha256-simd"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
)
diff --git a/models/db/engine.go b/models/db/engine.go
index b2fbdcfbf0..d78583df31 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -11,10 +11,13 @@ import (
"io"
"reflect"
"strings"
+ "time"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
+ "xorm.io/xorm/contexts"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
@@ -144,6 +147,16 @@ func InitEngine(ctx context.Context) error {
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetDefaultContext(ctx)
+ if setting.Database.SlowQueryTreshold > 0 {
+ xormEngine.AddHook(&SlowQueryHook{
+ Treshold: setting.Database.SlowQueryTreshold,
+ Logger: log.GetLogger("xorm"),
+ })
+ }
+ xormEngine.AddHook(&ErrorQueryHook{
+ Logger: log.GetLogger("xorm"),
+ })
+
SetDefaultEngine(ctx, xormEngine)
return nil
}
@@ -299,3 +312,38 @@ func SetLogSQL(ctx context.Context, on bool) {
sess.Engine().ShowSQL(on)
}
}
+
+type SlowQueryHook struct {
+ Treshold time.Duration
+ Logger log.Logger
+}
+
+var _ contexts.Hook = &SlowQueryHook{}
+
+func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
+ return c.Ctx, nil
+}
+
+func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
+ if c.ExecuteTime >= h.Treshold {
+ h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
+ }
+ return nil
+}
+
+type ErrorQueryHook struct {
+ Logger log.Logger
+}
+
+var _ contexts.Hook = &ErrorQueryHook{}
+
+func (ErrorQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
+ return c.Ctx, nil
+}
+
+func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
+ if c.Err != nil {
+ h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
+ }
+ return nil
+}
diff --git a/models/db/engine_test.go b/models/db/engine_test.go
index c9ae5f1542..e8f1c19b1c 100644
--- a/models/db/engine_test.go
+++ b/models/db/engine_test.go
@@ -6,15 +6,19 @@ package db_test
import (
"path/filepath"
"testing"
+ "time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
"github.com/stretchr/testify/assert"
+ "xorm.io/xorm"
)
func TestDumpDatabase(t *testing.T) {
@@ -85,3 +89,65 @@ func TestPrimaryKeys(t *testing.T) {
}
}
}
+
+func TestSlowQuery(t *testing.T) {
+ lc, cleanup := test.NewLogChecker("slow-query")
+ lc.StopMark("[Slow SQL Query]")
+ defer cleanup()
+
+ e := db.GetEngine(db.DefaultContext)
+ engine, ok := e.(*xorm.Engine)
+ assert.True(t, ok)
+
+ // It's not possible to clean this up with XORM, but it's luckily not harmful
+ // to leave around.
+ engine.AddHook(&db.SlowQueryHook{
+ Treshold: time.Second * 10,
+ Logger: log.GetLogger("slow-query"),
+ })
+
+ // NOOP query.
+ e.Exec("SELECT 1 WHERE false;")
+
+ _, stopped := lc.Check(100 * time.Millisecond)
+ assert.False(t, stopped)
+
+ engine.AddHook(&db.SlowQueryHook{
+ Treshold: 0, // Every query should be logged.
+ Logger: log.GetLogger("slow-query"),
+ })
+
+ // NOOP query.
+ e.Exec("SELECT 1 WHERE false;")
+
+ _, stopped = lc.Check(100 * time.Millisecond)
+ assert.True(t, stopped)
+}
+
+func TestErrorQuery(t *testing.T) {
+ lc, cleanup := test.NewLogChecker("error-query")
+ lc.StopMark("[Error SQL Query]")
+ defer cleanup()
+
+ e := db.GetEngine(db.DefaultContext)
+ engine, ok := e.(*xorm.Engine)
+ assert.True(t, ok)
+
+ // It's not possible to clean this up with XORM, but it's luckily not harmful
+ // to leave around.
+ engine.AddHook(&db.ErrorQueryHook{
+ Logger: log.GetLogger("error-query"),
+ })
+
+ // Valid query.
+ e.Exec("SELECT 1 WHERE false;")
+
+ _, stopped := lc.Check(100 * time.Millisecond)
+ assert.False(t, stopped)
+
+ // Table doesn't exist.
+ e.Exec("SELECT column FROM table;")
+
+ _, stopped = lc.Check(100 * time.Millisecond)
+ assert.True(t, stopped)
+}
diff --git a/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml b/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml
new file mode 100644
index 0000000000..f76dabb1c1
--- /dev/null
+++ b/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml
@@ -0,0 +1,13 @@
+-
+ id: 1000
+ owner_id: 2
+ name: user2@localhost
+ fingerprint: "SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4"
+ content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBknvWcuxM/W0iXGkzY4f2O6feX+Q7o46pKcxUbcOgh user2@localhost"
+ # private key (base64-ed) LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQkc1dmJtVUFBQUFFYm05dVpRQUFBQUFBQUFBQkFBQUFNd0FBQUF0emMyZ3RaVwpReU5UVXhPUUFBQUNDZ1pKNzFuTHNUUDF0SWx4cE0yT0g5anVuM2wva082T09xU25NVkczRG9JUUFBQUpocG43YTZhWisyCnVnQUFBQXR6YzJndFpXUXlOVFV4T1FBQUFDQ2daSjcxbkxzVFAxdElseHBNMk9IOWp1bjNsL2tPNk9PcVNuTVZHM0RvSVEKQUFBRUFxVm12bmo1LzZ5TW12ck9Ub29xa3F5MmUrc21aK0tBcEtKR0crRnY5MlA2QmtudldjdXhNL1cwaVhHa3pZNGYyTwo2ZmVYK1E3bzQ2cEtjeFViY09naEFBQUFFMmQxYzNSbFpFQm5kWE4wWldRdFltVmhjM1FCQWc9PQotLS0tLUVORCBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0=
+ mode: 2
+ type: 1
+ verified: true
+ created_unix: 1559593109
+ updated_unix: 1565224552
+ login_source_id: 0
diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml
index 372a79509f..01635064c5 100644
--- a/models/fixtures/release.yml
+++ b/models/fixtures/release.yml
@@ -150,3 +150,17 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
+
+- id: 12
+ repo_id: 1059
+ publisher_id: 2
+ tag_name: "v1.0"
+ lower_tag_name: "v1.0"
+ target: "main"
+ title: "v1.0"
+ sha1: "d8f53dfb33f6ccf4169c34970b5e747511c18beb"
+ num_commits: 1
+ is_draft: false
+ is_prerelease: false
+ is_tag: false
+ created_unix: 946684803
diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml
index e6c59f527a..e3590b06f0 100644
--- a/models/fixtures/repo_unit.yml
+++ b/models/fixtures/repo_unit.yml
@@ -608,6 +608,38 @@
type: 1
created_unix: 946684810
+# BEGIN Forgejo [GITEA] Improve HTML title on repositories
+-
+ id: 1093
+ repo_id: 1059
+ type: 1
+ created_unix: 946684810
+
+-
+ id: 1094
+ repo_id: 1059
+ type: 2
+ created_unix: 946684810
+
+-
+ id: 1095
+ repo_id: 1059
+ type: 3
+ created_unix: 946684810
+
+-
+ id: 1096
+ repo_id: 1059
+ type: 4
+ created_unix: 946684810
+
+-
+ id: 1097
+ repo_id: 1059
+ type: 5
+ created_unix: 946684810
+# END Forgejo [GITEA] Improve HTML title on repositories
+
-
id: 91
repo_id: 58
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 66eff2eee8..8ef58b64ac 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -1467,6 +1467,7 @@
owner_name: user27
lower_name: repo49
name: repo49
+ description: A wonderful repository with more than just a README.md
default_branch: master
num_watches: 0
num_stars: 0
@@ -1694,6 +1695,19 @@
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
+-
+ id: 1059
+ owner_id: 2
+ owner_name: user2
+ lower_name: repo59
+ name: repo59
+ default_branch: master
+ is_empty: false
+ is_archived: false
+ is_private: false
+ status: 0
+ num_issues: 0
+
-
id: 59
owner_id: 2
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 79fbb981f6..a68e00453a 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -66,7 +66,7 @@
num_followers: 2
num_following: 1
num_stars: 2
- num_repos: 15
+ num_repos: 16
num_teams: 0
num_members: 0
visibility: 0
diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go
index 58f158bd17..f0e22c046f 100644
--- a/models/forgejo_migrations/migrate.go
+++ b/models/forgejo_migrations/migrate.go
@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/forgejo/semver"
forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20"
+ forgejo_v1_22 "code.gitea.io/gitea/models/forgejo_migrations/v1_22"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -43,6 +44,10 @@ var migrations = []*Migration{
NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
// v2 -> v3
NewMigration("create the forgejo_auth_token table", forgejo_v1_20.CreateAuthorizationTokenTable),
+ // v3 -> v4
+ NewMigration("Add default_permissions to repo_unit", forgejo_v1_22.AddDefaultPermissionsToRepoUnit),
+ // v4 -> v5
+ NewMigration("create the forgejo_repo_flag table", forgejo_v1_22.CreateRepoFlagTable),
}
// GetCurrentDBVersion returns the current Forgejo database version.
diff --git a/models/forgejo_migrations/v1_22/v4.go b/models/forgejo_migrations/v1_22/v4.go
new file mode 100644
index 0000000000..f1195f5f66
--- /dev/null
+++ b/models/forgejo_migrations/v1_22/v4.go
@@ -0,0 +1,17 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddDefaultPermissionsToRepoUnit(x *xorm.Engine) error {
+ type RepoUnit struct {
+ ID int64
+ DefaultPermissions int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(&RepoUnit{})
+}
diff --git a/models/forgejo_migrations/v1_22/v5.go b/models/forgejo_migrations/v1_22/v5.go
new file mode 100644
index 0000000000..55f9fe1338
--- /dev/null
+++ b/models/forgejo_migrations/v1_22/v5.go
@@ -0,0 +1,22 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+type RepoFlag struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX"`
+ Name string `xorm:"UNIQUE(s) INDEX"`
+}
+
+func (RepoFlag) TableName() string {
+ return "forgejo_repo_flag"
+}
+
+func CreateRepoFlagTable(x *xorm.Engine) error {
+ return x.Sync(new(RepoFlag))
+}
diff --git a/models/git/branch.go b/models/git/branch.go
index db02fc9b28..6baad65ab4 100644
--- a/models/git/branch.go
+++ b/models/git/branch.go
@@ -128,6 +128,10 @@ func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
return err
}
+func (b *Branch) GetRepo(ctx context.Context) (*repo_model.Repository, error) {
+ return repo_model.GetRepositoryByID(ctx, b.RepoID)
+}
+
func (b *Branch) LoadPusher(ctx context.Context) (err error) {
if b.Pusher == nil && b.PusherID > 0 {
b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID)
@@ -283,7 +287,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *
}
// RenameBranch rename a branch
-func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) {
+func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
@@ -358,7 +362,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
}
// 5. do git action
- if err = gitAction(ctx, isDefault); err != nil {
+ if err = gitAction(isDefault); err != nil {
return err
}
diff --git a/models/git/branch_test.go b/models/git/branch_test.go
index fd5d6519e9..d480e2ec30 100644
--- a/models/git/branch_test.go
+++ b/models/git/branch_test.go
@@ -4,7 +4,6 @@
package git_test
import (
- "context"
"testing"
"code.gitea.io/gitea/models/db"
@@ -133,7 +132,7 @@ func TestRenameBranch(t *testing.T) {
}, git_model.WhitelistOptions{}))
assert.NoError(t, committer.Commit())
- assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error {
+ assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error {
_isDefault = isDefault
return nil
}))
diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go
index c5bbfdedc2..e08bd7fbf5 100644
--- a/models/issues/comment_test.go
+++ b/models/issues/comment_test.go
@@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
@@ -97,3 +98,29 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Issue{})
}
+
+func TestUpdateCommentsMigrationsByType(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
+ comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1, IssueID: issue.ID})
+
+ // Set repository to migrated from Gitea.
+ repo.OriginalServiceType = structs.GiteaService
+ repo_model.UpdateRepositoryCols(db.DefaultContext, repo, "original_service_type")
+
+ // Set comment to have an original author.
+ comment.OriginalAuthor = "Example User"
+ comment.OriginalAuthorID = 1
+ comment.PosterID = 0
+ _, err := db.GetEngine(db.DefaultContext).ID(comment.ID).Cols("original_author", "original_author_id", "poster_id").Update(comment)
+ assert.NoError(t, err)
+
+ assert.NoError(t, issues_model.UpdateCommentsMigrationsByType(db.DefaultContext, structs.GiteaService, "1", 513))
+
+ comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1, IssueID: issue.ID})
+ assert.Empty(t, comment.OriginalAuthor)
+ assert.Empty(t, comment.OriginalAuthorID)
+ assert.EqualValues(t, 513, comment.PosterID)
+}
diff --git a/models/migrations/base/hash.go b/models/migrations/base/hash.go
index 0debec272b..00fd1efd4a 100644
--- a/models/migrations/base/hash.go
+++ b/models/migrations/base/hash.go
@@ -4,9 +4,9 @@
package base
import (
+ "crypto/sha256"
"encoding/hex"
- "github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2"
)
diff --git a/models/migrations/v1_14/v166.go b/models/migrations/v1_14/v166.go
index 78f33e8f9b..e5731582fd 100644
--- a/models/migrations/v1_14/v166.go
+++ b/models/migrations/v1_14/v166.go
@@ -4,9 +4,9 @@
package v1_14 //nolint
import (
+ "crypto/sha256"
"encoding/hex"
- "github.com/minio/sha256-simd"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go
index ed1bc3bda5..67e950177d 100644
--- a/models/migrations/v1_21/v276.go
+++ b/models/migrations/v1_21/v276.go
@@ -4,13 +4,7 @@
package v1_21 //nolint
import (
- "context"
- "fmt"
- "path/filepath"
- "strings"
-
- "code.gitea.io/gitea/modules/git"
- giturl "code.gitea.io/gitea/modules/git/url"
+ repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
@@ -73,7 +67,7 @@ func migratePullMirrors(x *xorm.Engine) error {
start += len(mirrors)
for _, m := range mirrors {
- remoteAddress, err := getRemoteAddress(m.RepoOwner, m.RepoName, "origin")
+ remoteAddress, err := repo_model.GetPushMirrorRemoteAddress(m.RepoOwner, m.RepoName, "origin")
if err != nil {
return err
}
@@ -136,7 +130,7 @@ func migratePushMirrors(x *xorm.Engine) error {
start += len(mirrors)
for _, m := range mirrors {
- remoteAddress, err := getRemoteAddress(m.RepoOwner, m.RepoName, m.RemoteName)
+ remoteAddress, err := repo_model.GetPushMirrorRemoteAddress(m.RepoOwner, m.RepoName, m.RemoteName)
if err != nil {
return err
}
@@ -160,20 +154,3 @@ func migratePushMirrors(x *xorm.Engine) error {
return sess.Commit()
}
-
-func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
- repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
-
- remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
- if err != nil {
- return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
- }
-
- u, err := giturl.Parse(remoteURL)
- if err != nil {
- return "", err
- }
- u.User = nil
-
- return u.String(), nil
-}
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 395ecdf1a5..0b66e62d7d 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -33,6 +33,16 @@ func (p *Permission) IsAdmin() bool {
return p.AccessMode >= perm_model.AccessModeAdmin
}
+// IsGloballyWriteable returns true if the unit is writeable by all users of the instance.
+func (p *Permission) IsGloballyWriteable(unitType unit.Type) bool {
+ for _, u := range p.Units {
+ if u.Type == unitType {
+ return u.DefaultPermissions == repo_model.UnitAccessModeWrite
+ }
+ }
+ return false
+}
+
// HasAccess returns true if the current user has at least read access to any unit of this repository
func (p *Permission) HasAccess() bool {
if p.UnitsMode == nil {
@@ -198,7 +208,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
if err := repo.LoadOwner(ctx); err != nil {
return perm, err
}
+
if !repo.Owner.IsOrganization() {
+ // for a public repo, different repo units may have different default
+ // permissions for non-restricted users.
+ if !repo.IsPrivate && !user.IsRestricted && len(repo.Units) > 0 {
+ perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
+ for _, u := range repo.Units {
+ if _, ok := perm.UnitsMode[u.Type]; !ok {
+ perm.UnitsMode[u.Type] = u.DefaultPermissions.ToAccessMode(perm.AccessMode)
+ }
+ }
+ }
+
return perm, nil
}
@@ -239,10 +261,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
}
- // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
+ // for a public repo on an organization, a non-restricted user should
+ // have the same permission on non-team defined units as the default
+ // permissions for the repo unit.
if !found && !repo.IsPrivate && !user.IsRestricted {
if _, ok := perm.UnitsMode[u.Type]; !ok {
- perm.UnitsMode[u.Type] = perm_model.AccessModeRead
+ perm.UnitsMode[u.Type] = u.DefaultPermissions.ToAccessMode(perm_model.AccessModeRead)
}
}
}
diff --git a/models/pull/automerge.go b/models/pull/automerge.go
index f69fcb60d1..f31159a8d8 100644
--- a/models/pull/automerge.go
+++ b/models/pull/automerge.go
@@ -74,7 +74,7 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
return false, nil, err
}
- doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID)
+ doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID)
if err != nil {
return false, nil, err
}
diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go
index 24c58faf84..60e3093f44 100644
--- a/models/repo/pushmirror.go
+++ b/models/repo/pushmirror.go
@@ -5,10 +5,16 @@ package repo
import (
"context"
+ "fmt"
+ "path/filepath"
+ "strings"
"time"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/git"
+ giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -129,3 +135,21 @@ func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any
}
return sess.Iterate(new(PushMirror), f)
}
+
+// GetPushMirrorRemoteAddress returns the address of associated with a repository's given remote.
+func GetPushMirrorRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
+ repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
+
+ remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
+ if err != nil {
+ return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
+ }
+
+ u, err := giturl.Parse(remoteURL)
+ if err != nil {
+ return "", err
+ }
+ u.User = nil
+
+ return u.String(), nil
+}
diff --git a/models/repo/repo_flags.go b/models/repo/repo_flags.go
new file mode 100644
index 0000000000..de76ed2b37
--- /dev/null
+++ b/models/repo/repo_flags.go
@@ -0,0 +1,102 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/models/db"
+
+ "xorm.io/builder"
+)
+
+// RepoFlag represents a single flag against a repository
+type RepoFlag struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX"`
+ Name string `xorm:"UNIQUE(s) INDEX"`
+}
+
+func init() {
+ db.RegisterModel(new(RepoFlag))
+}
+
+// TableName provides the real table name
+func (RepoFlag) TableName() string {
+ return "forgejo_repo_flag"
+}
+
+// ListFlags returns the array of flags on the repo.
+func (repo *Repository) ListFlags(ctx context.Context) ([]RepoFlag, error) {
+ var flags []RepoFlag
+ err := db.GetEngine(ctx).Table(&RepoFlag{}).Where("repo_id = ?", repo.ID).Find(&flags)
+ if err != nil {
+ return nil, err
+ }
+ return flags, nil
+}
+
+// IsFlagged returns whether a repo has any flags or not
+func (repo *Repository) IsFlagged(ctx context.Context) bool {
+ has, _ := db.Exist[RepoFlag](ctx, builder.Eq{"repo_id": repo.ID})
+ return has
+}
+
+// GetFlag returns a single RepoFlag based on its name
+func (repo *Repository) GetFlag(ctx context.Context, flagName string) (bool, *RepoFlag, error) {
+ flag, has, err := db.Get[RepoFlag](ctx, builder.Eq{"repo_id": repo.ID, "name": flagName})
+ if err != nil {
+ return false, nil, err
+ }
+ return has, flag, nil
+}
+
+// HasFlag returns true if a repo has a given flag, false otherwise
+func (repo *Repository) HasFlag(ctx context.Context, flagName string) bool {
+ has, _ := db.Exist[RepoFlag](ctx, builder.Eq{"repo_id": repo.ID, "name": flagName})
+ return has
+}
+
+// AddFlag adds a new flag to the repo
+func (repo *Repository) AddFlag(ctx context.Context, flagName string) error {
+ return db.Insert(ctx, RepoFlag{
+ RepoID: repo.ID,
+ Name: flagName,
+ })
+}
+
+// DeleteFlag removes a flag from the repo
+func (repo *Repository) DeleteFlag(ctx context.Context, flagName string) (int64, error) {
+ return db.DeleteByBean(ctx, &RepoFlag{RepoID: repo.ID, Name: flagName})
+}
+
+// ReplaceAllFlags replaces all flags of a repo with a new set
+func (repo *Repository) ReplaceAllFlags(ctx context.Context, flagNames []string) error {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ if err := db.DeleteBeans(ctx, &RepoFlag{RepoID: repo.ID}); err != nil {
+ return err
+ }
+
+ if len(flagNames) == 0 {
+ return committer.Commit()
+ }
+
+ var flags []RepoFlag
+ for _, name := range flagNames {
+ flags = append(flags, RepoFlag{
+ RepoID: repo.ID,
+ Name: name,
+ })
+ }
+ if err := db.Insert(ctx, &flags); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
diff --git a/models/repo/repo_flags_test.go b/models/repo/repo_flags_test.go
new file mode 100644
index 0000000000..0e4f5c1ba9
--- /dev/null
+++ b/models/repo/repo_flags_test.go
@@ -0,0 +1,114 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo_test
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRepositoryFlags(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+
+ // ********************
+ // ** NEGATIVE TESTS **
+ // ********************
+
+ // Unless we add flags, the repo has none
+ flags, err := repo.ListFlags(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Empty(t, flags)
+
+ // If the repo has no flags, it is not flagged
+ flagged := repo.IsFlagged(db.DefaultContext)
+ assert.False(t, flagged)
+
+ // Trying to find a flag when there is none
+ has := repo.HasFlag(db.DefaultContext, "foo")
+ assert.False(t, has)
+
+ // Trying to retrieve a non-existent flag indicates not found
+ has, _, err = repo.GetFlag(db.DefaultContext, "foo")
+ assert.NoError(t, err)
+ assert.False(t, has)
+
+ // Deleting a non-existent flag fails
+ deleted, err := repo.DeleteFlag(db.DefaultContext, "no-such-flag")
+ assert.NoError(t, err)
+ assert.Equal(t, int64(0), deleted)
+
+ // ********************
+ // ** POSITIVE TESTS **
+ // ********************
+
+ // Adding a flag works
+ err = repo.AddFlag(db.DefaultContext, "foo")
+ assert.NoError(t, err)
+
+ // Adding it again fails
+ err = repo.AddFlag(db.DefaultContext, "foo")
+ assert.Error(t, err)
+
+ // Listing flags includes the one we added
+ flags, err = repo.ListFlags(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Len(t, flags, 1)
+ assert.Equal(t, "foo", flags[0].Name)
+
+ // With a flag added, the repo is flagged
+ flagged = repo.IsFlagged(db.DefaultContext)
+ assert.True(t, flagged)
+
+ // The flag can be found
+ has = repo.HasFlag(db.DefaultContext, "foo")
+ assert.True(t, has)
+
+ // Added flag can be retrieved
+ _, flag, err := repo.GetFlag(db.DefaultContext, "foo")
+ assert.NoError(t, err)
+ assert.Equal(t, "foo", flag.Name)
+
+ // Deleting a flag works
+ deleted, err = repo.DeleteFlag(db.DefaultContext, "foo")
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), deleted)
+
+ // The list is now empty
+ flags, err = repo.ListFlags(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Empty(t, flags)
+
+ // Replacing an empty list works
+ err = repo.ReplaceAllFlags(db.DefaultContext, []string{"bar"})
+ assert.NoError(t, err)
+
+ // The repo is now flagged with "bar"
+ has = repo.HasFlag(db.DefaultContext, "bar")
+ assert.True(t, has)
+
+ // Replacing a tag set with another works
+ err = repo.ReplaceAllFlags(db.DefaultContext, []string{"baz", "quux"})
+ assert.NoError(t, err)
+
+ // The repo now has two tags
+ flags, err = repo.ListFlags(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Len(t, flags, 2)
+ assert.Equal(t, "baz", flags[0].Name)
+ assert.Equal(t, "quux", flags[1].Name)
+
+ // Replacing flags with an empty set deletes all flags
+ err = repo.ReplaceAllFlags(db.DefaultContext, []string{})
+ assert.NoError(t, err)
+
+ // The repo is now unflagged
+ flagged = repo.IsFlagged(db.DefaultContext)
+ assert.False(t, flagged)
+}
diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go
index 8a1799aac0..a8b958109c 100644
--- a/models/repo/repo_list_test.go
+++ b/models/repo/repo_list_test.go
@@ -138,12 +138,12 @@ func getTestCases() []struct {
{
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
- count: 31,
+ count: 32,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
- count: 36,
+ count: 37,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@@ -158,7 +158,7 @@ func getTestCases() []struct {
{
name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
- count: 31,
+ count: 32,
},
{
name: "AllTemplates",
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index 8a3ba1ee89..3df5236ea7 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
@@ -39,13 +40,43 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
return util.ErrNotExist
}
+// RepoUnitAccessMode specifies the users access mode to a repo unit
+type UnitAccessMode int
+
+const (
+ // UnitAccessModeUnset - no unit mode set
+ UnitAccessModeUnset UnitAccessMode = iota // 0
+ // UnitAccessModeNone no access
+ UnitAccessModeNone // 1
+ // UnitAccessModeRead read access
+ UnitAccessModeRead // 2
+ // UnitAccessModeWrite write access
+ UnitAccessModeWrite // 3
+)
+
+func (mode UnitAccessMode) ToAccessMode(modeIfUnset perm.AccessMode) perm.AccessMode {
+ switch mode {
+ case UnitAccessModeUnset:
+ return modeIfUnset
+ case UnitAccessModeNone:
+ return perm.AccessModeNone
+ case UnitAccessModeRead:
+ return perm.AccessModeRead
+ case UnitAccessModeWrite:
+ return perm.AccessModeWrite
+ default:
+ return perm.AccessModeNone
+ }
+}
+
// RepoUnit describes all units of a repository
type RepoUnit struct { //revive:disable-line:exported
- ID int64
- RepoID int64 `xorm:"INDEX(s)"`
- Type unit.Type `xorm:"INDEX(s)"`
- Config convert.Conversion `xorm:"TEXT"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+ ID int64
+ RepoID int64 `xorm:"INDEX(s)"`
+ Type unit.Type `xorm:"INDEX(s)"`
+ Config convert.Conversion `xorm:"TEXT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+ DefaultPermissions UnitAccessMode `xorm:"NOT NULL DEFAULT 0"`
}
func init() {
@@ -283,3 +314,29 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error {
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
return err
}
+
+// UpdateRepositoryUnits updates a repository's units
+func UpdateRepositoryUnits(ctx context.Context, repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ // Delete existing settings of units before adding again
+ for _, u := range units {
+ deleteUnitTypes = append(deleteUnitTypes, u.Type)
+ }
+
+ if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil {
+ return err
+ }
+
+ if len(units) > 0 {
+ if err = db.Insert(ctx, units); err != nil {
+ return err
+ }
+ }
+
+ return committer.Commit()
+}
diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go
index a760594013..27a34fd0eb 100644
--- a/models/repo/repo_unit_test.go
+++ b/models/repo/repo_unit_test.go
@@ -6,6 +6,8 @@ package repo
import (
"testing"
+ "code.gitea.io/gitea/models/perm"
+
"github.com/stretchr/testify/assert"
)
@@ -28,3 +30,10 @@ func TestActionsConfig(t *testing.T) {
cfg.DisableWorkflow("test3.yaml")
assert.EqualValues(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString())
}
+
+func TestRepoUnitAccessMode(t *testing.T) {
+ assert.Equal(t, UnitAccessModeNone.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeNone)
+ assert.Equal(t, UnitAccessModeRead.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeRead)
+ assert.Equal(t, UnitAccessModeWrite.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeWrite)
+ assert.Equal(t, UnitAccessModeUnset.ToAccessMode(perm.AccessModeRead), perm.AccessModeRead)
+}
diff --git a/models/repo/topic.go b/models/repo/topic.go
index b71f43bc88..dd61c48551 100644
--- a/models/repo/topic.go
+++ b/models/repo/topic.go
@@ -199,7 +199,7 @@ func FindTopics(ctx context.Context, opts *FindTopicOptions) ([]*Topic, int64, e
sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result
}
- if opts.PageSize != 0 && opts.Page != 0 {
+ if opts.PageSize > 0 {
sess = db.SetSessionPagination(sess, opts)
}
topics := make([]*Topic, 0, 10)
diff --git a/models/unittest/mock_http.go b/models/unittest/mock_http.go
new file mode 100644
index 0000000000..afdc5bed21
--- /dev/null
+++ b/models/unittest/mock_http.go
@@ -0,0 +1,113 @@
+// Copyright 2017 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package unittest
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "slices"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/log"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Mocks HTTP responses of a third-party service (such as GitHub, GitLab…)
+// This has two modes:
+// - live mode: the requests made to the mock HTTP server are transmitted to the live
+// service, and responses are saved as test data files
+// - test mode: the responses to requests to the mock HTTP server are read from the
+// test data files
+func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveMode bool) *httptest.Server {
+ mockServerBaseURL := ""
+ ignoredHeaders := []string{"cf-ray", "server", "date", "report-to", "nel", "x-request-id"}
+
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ path := NormalizedFullPath(r.URL)
+ log.Info("Mock HTTP Server: got request for path %s", r.URL.Path)
+ // TODO check request method (support POST?)
+ fixturePath := fmt.Sprintf("%s/%s", testDataDir, strings.NewReplacer("/", "_", "?", "!").Replace(path))
+ if liveMode {
+ liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path)
+
+ request, err := http.NewRequest(r.Method, liveURL, nil)
+ assert.NoError(t, err, "constructing an HTTP request to %s failed", liveURL)
+ for headerName, headerValues := range r.Header {
+ // do not pass on the encoding: let the Transport of the HTTP client handle that for us
+ if strings.ToLower(headerName) != "accept-encoding" {
+ for _, headerValue := range headerValues {
+ request.Header.Add(headerName, headerValue)
+ }
+ }
+ }
+
+ response, err := http.DefaultClient.Do(request)
+ assert.NoError(t, err, "HTTP request to %s failed: %s", liveURL)
+
+ fixture, err := os.Create(fixturePath)
+ assert.NoError(t, err, "failed to open the fixture file %s for writing", fixturePath)
+ defer fixture.Close()
+ fixtureWriter := bufio.NewWriter(fixture)
+
+ for headerName, headerValues := range response.Header {
+ for _, headerValue := range headerValues {
+ if !slices.Contains(ignoredHeaders, strings.ToLower(headerName)) {
+ _, err := fixtureWriter.WriteString(fmt.Sprintf("%s: %s\n", headerName, headerValue))
+ assert.NoError(t, err, "writing the header of the HTTP response to the fixture file failed")
+ }
+ }
+ }
+ _, err = fixtureWriter.WriteString("\n")
+ assert.NoError(t, err, "writing the header of the HTTP response to the fixture file failed")
+ fixtureWriter.Flush()
+
+ log.Info("Mock HTTP Server: writing response to %s", fixturePath)
+ _, err = io.Copy(fixture, response.Body)
+ assert.NoError(t, err, "writing the body of the HTTP response to %s failed", liveURL)
+
+ err = fixture.Sync()
+ assert.NoError(t, err, "writing the body of the HTTP response to the fixture file failed")
+ }
+
+ fixture, err := os.ReadFile(fixturePath)
+ assert.NoError(t, err, "missing mock HTTP response: "+fixturePath)
+
+ w.WriteHeader(http.StatusOK)
+
+ // replace any mention of the live HTTP service by the mocked host
+ stringFixture := strings.ReplaceAll(string(fixture), liveServerBaseURL, mockServerBaseURL)
+ // parse back the fixture file into a series of HTTP headers followed by response body
+ lines := strings.Split(stringFixture, "\n")
+ for idx, line := range lines {
+ colonIndex := strings.Index(line, ": ")
+ if colonIndex != -1 {
+ w.Header().Set(line[0:colonIndex], line[colonIndex+2:])
+ } else {
+ // we reached the end of the headers (empty line), so what follows is the body
+ responseBody := strings.Join(lines[idx+1:], "\n")
+ _, err := w.Write([]byte(responseBody))
+ assert.NoError(t, err, "writing the body of the HTTP response failed")
+ break
+ }
+ }
+ }))
+ mockServerBaseURL = server.URL
+ return server
+}
+
+func NormalizedFullPath(url *url.URL) string {
+ // TODO normalize path (remove trailing slash?)
+ // TODO normalize RawQuery (order query parameters?)
+ if len(url.Query()) == 0 {
+ return url.EscapedPath()
+ }
+ return fmt.Sprintf("%s?%s", url.EscapedPath(), url.RawQuery)
+}
diff --git a/models/user/email_address.go b/models/user/email_address.go
index 2af2621f5f..986f12e900 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -189,6 +189,25 @@ func GetEmailAddresses(ctx context.Context, uid int64) ([]*EmailAddress, error)
return emails, nil
}
+type ActivatedEmailAddress struct {
+ ID int64
+ Email string
+}
+
+func GetActivatedEmailAddresses(ctx context.Context, uid int64) ([]*ActivatedEmailAddress, error) {
+ emails := make([]*ActivatedEmailAddress, 0, 8)
+ if err := db.GetEngine(ctx).
+ Table("email_address").
+ Select("id, email").
+ Where("uid=?", uid).
+ And("is_activated=?", true).
+ Asc("id").
+ Find(&emails); err != nil {
+ return nil, err
+ }
+ return emails, nil
+}
+
// GetEmailAddressByID gets a user's email address by ID
func GetEmailAddressByID(ctx context.Context, uid, id int64) (*EmailAddress, error) {
// User ID is required for security reasons
@@ -356,31 +375,7 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
return UpdateUserCols(ctx, user, "rands")
}
-// MakeEmailPrimary sets primary email address of given user.
-func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
- has, err := db.GetEngine(ctx).Get(email)
- if err != nil {
- return err
- } else if !has {
- return ErrEmailAddressNotExist{Email: email.Email}
- }
-
- if !email.IsActivated {
- return ErrEmailNotActivated
- }
-
- user := &User{}
- has, err = db.GetEngine(ctx).ID(email.UID).Get(user)
- if err != nil {
- return err
- } else if !has {
- return ErrUserNotExist{
- UID: email.UID,
- Name: "",
- KeyID: 0,
- }
- }
-
+func makeEmailPrimary(ctx context.Context, user *User, email *EmailAddress) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
@@ -410,6 +405,57 @@ func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
return committer.Commit()
}
+// ReplaceInactivePrimaryEmail replaces the primary email of a given user, even if the primary is not yet activated.
+func ReplaceInactivePrimaryEmail(ctx context.Context, oldEmail string, email *EmailAddress) error {
+ user := &User{}
+ has, err := db.GetEngine(ctx).ID(email.UID).Get(user)
+ if err != nil {
+ return err
+ } else if !has {
+ return ErrUserNotExist{
+ UID: email.UID,
+ Name: "",
+ KeyID: 0,
+ }
+ }
+
+ err = AddEmailAddress(ctx, email)
+ if err != nil {
+ return err
+ }
+
+ err = makeEmailPrimary(ctx, user, email)
+ if err != nil {
+ return err
+ }
+
+ return DeleteEmailAddress(ctx, &EmailAddress{UID: email.UID, Email: oldEmail})
+}
+
+// MakeEmailPrimary sets primary email address of given user.
+func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
+ has, err := db.GetEngine(ctx).Get(email)
+ if err != nil {
+ return err
+ } else if !has {
+ return ErrEmailAddressNotExist{Email: email.Email}
+ }
+
+ if !email.IsActivated {
+ return ErrEmailNotActivated
+ }
+
+ user := &User{}
+ has, err = db.GetEngine(ctx).ID(email.UID).Get(user)
+ if err != nil {
+ return err
+ } else if !has {
+ return ErrUserNotExist{UID: email.UID}
+ }
+
+ return makeEmailPrimary(ctx, user, email)
+}
+
// VerifyActiveEmailCode verifies active email code when active account
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
minutes := setting.Service.ActiveCodeLives
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
index 7f3ca75cfd..c2c0378061 100644
--- a/models/user/email_address_test.go
+++ b/models/user/email_address_test.go
@@ -4,6 +4,7 @@
package user_test
import (
+ "fmt"
"testing"
"code.gitea.io/gitea/models/db"
@@ -166,6 +167,28 @@ func TestMakeEmailPrimary(t *testing.T) {
assert.Equal(t, "user101@example.com", user.Email)
}
+func TestReplaceInactivePrimaryEmail(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ email := &user_model.EmailAddress{
+ Email: "user9999999@example.com",
+ UID: 9999999,
+ }
+ err := user_model.ReplaceInactivePrimaryEmail(db.DefaultContext, "user10@example.com", email)
+ assert.Error(t, err)
+ assert.True(t, user_model.IsErrUserNotExist(err))
+
+ email = &user_model.EmailAddress{
+ Email: "user201@example.com",
+ UID: 10,
+ }
+ err = user_model.ReplaceInactivePrimaryEmail(db.DefaultContext, "user10@example.com", email)
+ assert.NoError(t, err)
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
+ assert.Equal(t, "user201@example.com", user.Email)
+}
+
func TestActivate(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -309,3 +332,37 @@ func TestEmailAddressValidate(t *testing.T) {
})
}
}
+
+func TestGetActivatedEmailAddresses(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ testCases := []struct {
+ UID int64
+ expected []*user_model.ActivatedEmailAddress
+ }{
+ {
+ UID: 1,
+ expected: []*user_model.ActivatedEmailAddress{{ID: 9, Email: "user1@example.com"}, {ID: 33, Email: "user1-2@example.com"}, {ID: 34, Email: "user1-3@example.com"}},
+ },
+ {
+ UID: 2,
+ expected: []*user_model.ActivatedEmailAddress{{ID: 3, Email: "user2@example.com"}},
+ },
+ {
+ UID: 4,
+ expected: []*user_model.ActivatedEmailAddress{{ID: 11, Email: "user4@example.com"}},
+ },
+ {
+ UID: 11,
+ expected: []*user_model.ActivatedEmailAddress{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(fmt.Sprintf("User %d", testCase.UID), func(t *testing.T) {
+ emails, err := user_model.GetActivatedEmailAddresses(db.DefaultContext, testCase.UID)
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.expected, emails)
+ })
+ }
+}
diff --git a/models/user/user.go b/models/user/user.go
index 3d48b2fb31..96e625d1c2 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -228,6 +228,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) {
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
}
+// GetAllAdmins returns a slice of all adminusers found in DB.
+func GetAllAdmins(ctx context.Context) ([]*User, error) {
+ users := make([]*User, 0)
+ return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users)
+}
+
// IsLocal returns true if user login type is LoginPlain.
func (u *User) IsLocal() bool {
return u.LoginType <= auth.Plain
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 06be05c090..59806b1724 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -533,6 +533,16 @@ func TestIsUserVisibleToViewer(t *testing.T) {
test(user31, nil, false)
}
+func TestGetAllAdmins(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ admins, err := user_model.GetAllAdmins(db.DefaultContext)
+ assert.NoError(t, err)
+
+ assert.Len(t, admins, 1)
+ assert.Equal(t, int64(1), admins[0].ID)
+}
+
func Test_ValidateUser(t *testing.T) {
oldSetting := setting.Service.AllowedUserVisibilityModesSlice
defer func() {
@@ -552,6 +562,11 @@ func Test_ValidateUser(t *testing.T) {
}
func Test_NormalizeUserFromEmail(t *testing.T) {
+ oldSetting := setting.Service.AllowDotsInUsernames
+ defer func() {
+ setting.Service.AllowDotsInUsernames = oldSetting
+ }()
+ setting.Service.AllowDotsInUsernames = true
testCases := []struct {
Input string
Expected string
diff --git a/modules/actions/github.go b/modules/actions/github.go
index fafea4e11a..a988b2a124 100644
--- a/modules/actions/github.go
+++ b/modules/actions/github.go
@@ -35,6 +35,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
case GithubEventGollum:
return triggedEvent == webhook_module.HookEventWiki
+ case GithubEventSchedule:
+ return triggedEvent == webhook_module.HookEventSchedule
+
case GithubEventIssues:
switch triggedEvent {
case webhook_module.HookEventIssues,
@@ -70,9 +73,6 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
return false
}
- case GithubEventSchedule:
- return triggedEvent == webhook_module.HookEventSchedule
-
default:
return eventName == string(triggedEvent)
}
diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go
index 8a44e9dbe2..00d83e06d7 100644
--- a/modules/actions/workflows.go
+++ b/modules/actions/workflows.go
@@ -22,7 +22,7 @@ import (
type DetectedWorkflow struct {
EntryName string
- TriggerEvent *jobparser.Event
+ TriggerEvent string
Content []byte
}
@@ -103,7 +103,6 @@ func DetectWorkflows(
commit *git.Commit,
triggedEvent webhook_module.HookEventType,
payload api.Payloader,
- detectSchedule bool,
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
entries, err := ListWorkflows(commit)
if err != nil {
@@ -118,7 +117,6 @@ func DetectWorkflows(
return nil, nil, err
}
- // one workflow may have multiple events
events, err := GetEventsFromContent(content)
if err != nil {
log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
@@ -127,18 +125,17 @@ func DetectWorkflows(
for _, evt := range events {
log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent)
if evt.IsSchedule() {
- if detectSchedule {
- dwf := &DetectedWorkflow{
- EntryName: entry.Name(),
- TriggerEvent: evt,
- Content: content,
- }
- schedules = append(schedules, dwf)
- }
- } else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
dwf := &DetectedWorkflow{
EntryName: entry.Name(),
- TriggerEvent: evt,
+ TriggerEvent: evt.Name,
+ Content: content,
+ }
+ schedules = append(schedules, dwf)
+ }
+ if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
+ dwf := &DetectedWorkflow{
+ EntryName: entry.Name(),
+ TriggerEvent: evt.Name,
Content: content,
}
workflows = append(workflows, dwf)
@@ -156,11 +153,11 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
switch triggedEvent {
case // events with no activity types
+ webhook_module.HookEventSchedule,
webhook_module.HookEventCreate,
webhook_module.HookEventDelete,
webhook_module.HookEventFork,
- webhook_module.HookEventWiki,
- webhook_module.HookEventSchedule:
+ webhook_module.HookEventWiki:
if len(evt.Acts()) != 0 {
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
}
diff --git a/modules/auth/password/hash/pbkdf2.go b/modules/auth/password/hash/pbkdf2.go
index 9ff6d162fc..27382fedb8 100644
--- a/modules/auth/password/hash/pbkdf2.go
+++ b/modules/auth/password/hash/pbkdf2.go
@@ -4,12 +4,12 @@
package hash
import (
+ "crypto/sha256"
"encoding/hex"
"strings"
"code.gitea.io/gitea/modules/log"
- "github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2"
)
diff --git a/modules/avatar/hash.go b/modules/avatar/hash.go
index 4fc28a7739..50db9c1943 100644
--- a/modules/avatar/hash.go
+++ b/modules/avatar/hash.go
@@ -4,10 +4,9 @@
package avatar
import (
+ "crypto/sha256"
"encoding/hex"
"strconv"
-
- "github.com/minio/sha256-simd"
)
// HashAvatar will generate a unique string, which ensures that when there's a
diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go
index 9b7a2faf05..63926d5f19 100644
--- a/modules/avatar/identicon/identicon.go
+++ b/modules/avatar/identicon/identicon.go
@@ -7,11 +7,10 @@
package identicon
import (
+ "crypto/sha256"
"fmt"
"image"
"image/color"
-
- "github.com/minio/sha256-simd"
)
const minImageSize = 16
diff --git a/modules/base/tool.go b/modules/base/tool.go
index e9f4dfa279..b72f3a1930 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -5,6 +5,7 @@ package base
import (
"crypto/sha1"
+ "crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
@@ -22,7 +23,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/dustin/go-humanize"
- "github.com/minio/sha256-simd"
)
// EncodeSha1 string to sha1 hex value.
diff --git a/modules/context/api.go b/modules/context/api.go
index f41228ad76..f7aa088e65 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -11,6 +11,7 @@ import (
"net/url"
"strings"
+ issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -38,6 +39,7 @@ type APIContext struct {
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
Repo *Repository
+ Comment *issues_model.Comment
Org *APIOrganization
Package *Package
}
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 5d960e92f3..012ba975e8 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -515,6 +515,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
return fileStatus, nil
}
+func parseCommitRenames(renames *[][2]string, stdout io.Reader) {
+ rd := bufio.NewReader(stdout)
+ for {
+ // Skip (R || three digits || NULL byte)
+ _, err := rd.Discard(5)
+ if err != nil {
+ if err != io.EOF {
+ log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
+ }
+ return
+ }
+ oldFileName, err := rd.ReadString('\x00')
+ if err != nil {
+ if err != io.EOF {
+ log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
+ }
+ return
+ }
+ newFileName, err := rd.ReadString('\x00')
+ if err != nil {
+ if err != io.EOF {
+ log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
+ }
+ return
+ }
+ oldFileName = strings.TrimSuffix(oldFileName, "\x00")
+ newFileName = strings.TrimSuffix(newFileName, "\x00")
+ *renames = append(*renames, [2]string{oldFileName, newFileName})
+ }
+}
+
+// GetCommitFileRenames returns the renames that the commit contains.
+func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) {
+ renames := [][2]string{}
+ stdout, w := io.Pipe()
+ done := make(chan struct{})
+ go func() {
+ parseCommitRenames(&renames, stdout)
+ close(done)
+ }()
+
+ stderr := new(bytes.Buffer)
+ err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{
+ Dir: repoPath,
+ Stdout: w,
+ Stderr: stderr,
+ })
+ w.Close() // Close writer to exit parsing goroutine
+ if err != nil {
+ return nil, ConcatenateError(err, stderr.String())
+ }
+
+ <-done
+ return renames, nil
+}
+
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index e512eecc56..2de6feeb31 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
}
+
+func TestParseCommitRenames(t *testing.T) {
+ testcases := []struct {
+ output string
+ renames [][2]string
+ }{
+ {
+ output: "R090\x00renamed.txt\x00history.txt\x00",
+ renames: [][2]string{{"renamed.txt", "history.txt"}},
+ },
+ {
+ output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere",
+ renames: [][2]string{{"renamed.txt", "history.txt"}},
+ },
+ {
+ output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00",
+ renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}},
+ },
+ }
+
+ for _, testcase := range testcases {
+ renames := [][2]string{}
+ parseCommitRenames(&renames, strings.NewReader(testcase.output))
+
+ assert.Equal(t, testcase.renames, renames)
+ }
+}
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index 7c7baedd2f..5b62b90b27 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -4,12 +4,11 @@
package git
import (
+ "crypto/sha256"
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
-
- "github.com/minio/sha256-simd"
)
// Cache represents a caching interface
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 2b34f117f7..3c5a1429a9 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -291,7 +291,7 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
}
checker := &CheckAttributeReader{
- Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
+ Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"},
Repo: repo,
IndexFile: indexFilename,
WorkTree: worktree,
diff --git a/modules/git/repo_language_stats.go b/modules/git/repo_language_stats.go
index c40d6937b5..7ed2dc1587 100644
--- a/modules/git/repo_language_stats.go
+++ b/modules/git/repo_language_stats.go
@@ -13,6 +13,18 @@ const (
bigFileSize int64 = 1024 * 1024 // 1 MiB
)
+type LinguistBoolAttrib struct {
+ Value string
+}
+
+func (attrib *LinguistBoolAttrib) IsTrue() bool {
+ return attrib.Value == "set" || attrib.Value == "true"
+}
+
+func (attrib *LinguistBoolAttrib) IsFalse() bool {
+ return attrib.Value == "unset" || attrib.Value == "false"
+}
+
// mergeLanguageStats mergers language names with different cases. The name with most upper case letters is used.
func mergeLanguageStats(stats map[string]int64) map[string]int64 {
names := map[string]struct {
diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go
index 4c6fbd6c7e..558a83af74 100644
--- a/modules/git/repo_language_stats_gogit.go
+++ b/modules/git/repo_language_stats_gogit.go
@@ -1,4 +1,5 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build gogit
@@ -57,23 +58,25 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil
}
- notVendored := false
- notGenerated := false
+ isVendored := LinguistBoolAttrib{}
+ isGenerated := LinguistBoolAttrib{}
+ isDocumentation := LinguistBoolAttrib{}
+ isDetectable := LinguistBoolAttrib{}
if checker != nil {
attrs, err := checker.CheckPath(f.Name)
if err == nil {
if vendored, has := attrs["linguist-vendored"]; has {
- if vendored == "set" || vendored == "true" {
- return nil
- }
- notVendored = vendored == "false"
+ isVendored = LinguistBoolAttrib{Value: vendored}
}
if generated, has := attrs["linguist-generated"]; has {
- if generated == "set" || generated == "true" {
- return nil
- }
- notGenerated = generated == "false"
+ isGenerated = LinguistBoolAttrib{Value: generated}
+ }
+ if documentation, has := attrs["linguist-documentation"]; has {
+ isDocumentation = LinguistBoolAttrib{Value: documentation}
+ }
+ if detectable, has := attrs["linguist-detectable"]; has {
+ isDetectable = LinguistBoolAttrib{Value: detectable}
}
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
// group languages, such as Pug -> HTML; SCSS -> CSS
@@ -105,8 +108,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
}
- if (!notVendored && analyze.IsVendor(f.Name)) || enry.IsDotFile(f.Name) ||
- enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
+ if isDetectable.IsFalse() || isVendored.IsTrue() || isDocumentation.IsTrue() ||
+ (!isVendored.IsFalse() && analyze.IsVendor(f.Name)) ||
+ enry.IsDotFile(f.Name) ||
+ enry.IsConfiguration(f.Name) ||
+ (!isDocumentation.IsFalse() && enry.IsDocumentation(f.Name)) {
return nil
}
@@ -115,12 +121,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if f.Size <= bigFileSize {
content, _ = readFile(f, fileSizeLimit)
}
- if !notGenerated && enry.IsGenerated(f.Name, content) {
+ if !isGenerated.IsTrue() && enry.IsGenerated(f.Name, content) {
return nil
}
// TODO: Use .gitattributes file for linguist overrides
-
language := analyze.GetCodeLanguage(f.Name, content)
if language == enry.OtherLanguage || language == "" {
return nil
@@ -136,6 +141,13 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if !checked {
langtype := enry.GetLanguageType(language)
included = langtype == enry.Programming || langtype == enry.Markup
+ if !included {
+ if isDetectable.IsTrue() {
+ included = true
+ } else {
+ return nil
+ }
+ }
includedLanguage[language] = included
}
if included {
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index 1d94ad6c00..13876094cc 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -1,4 +1,5 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !gogit
@@ -90,23 +91,25 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
continue
}
- notVendored := false
- notGenerated := false
+ isVendored := LinguistBoolAttrib{}
+ isGenerated := LinguistBoolAttrib{}
+ isDocumentation := LinguistBoolAttrib{}
+ isDetectable := LinguistBoolAttrib{}
if checker != nil {
attrs, err := checker.CheckPath(f.Name())
if err == nil {
if vendored, has := attrs["linguist-vendored"]; has {
- if vendored == "set" || vendored == "true" {
- continue
- }
- notVendored = vendored == "false"
+ isVendored = LinguistBoolAttrib{Value: vendored}
}
if generated, has := attrs["linguist-generated"]; has {
- if generated == "set" || generated == "true" {
- continue
- }
- notGenerated = generated == "false"
+ isGenerated = LinguistBoolAttrib{Value: generated}
+ }
+ if documentation, has := attrs["linguist-documentation"]; has {
+ isDocumentation = LinguistBoolAttrib{Value: documentation}
+ }
+ if detectable, has := attrs["linguist-detectable"]; has {
+ isDetectable = LinguistBoolAttrib{Value: detectable}
}
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
// group languages, such as Pug -> HTML; SCSS -> CSS
@@ -139,8 +142,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
}
- if (!notVendored && analyze.IsVendor(f.Name())) || enry.IsDotFile(f.Name()) ||
- enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) {
+ if isDetectable.IsFalse() || isVendored.IsTrue() || isDocumentation.IsTrue() ||
+ (!isVendored.IsFalse() && analyze.IsVendor(f.Name())) ||
+ enry.IsDotFile(f.Name()) ||
+ enry.IsConfiguration(f.Name()) ||
+ (!isDocumentation.IsFalse() && enry.IsDocumentation(f.Name())) {
continue
}
@@ -173,7 +179,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err
}
}
- if !notGenerated && enry.IsGenerated(f.Name(), content) {
+ if !isGenerated.IsTrue() && enry.IsGenerated(f.Name(), content) {
continue
}
@@ -194,6 +200,13 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if !checked {
langType := enry.GetLanguageType(language)
included = langType == enry.Programming || langType == enry.Markup
+ if !included {
+ if isDetectable.IsTrue() {
+ included = true
+ } else {
+ continue
+ }
+ }
includedLanguage[language] = included
}
if included {
diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go
index 31d9f3d73b..e60c1f915b 100644
--- a/modules/git/tree_blob.go
+++ b/modules/git/tree_blob.go
@@ -1,9 +1,12 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package git
+import "strings"
+
// GetBlobByPath get the blob object according the path
func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
entry, err := t.GetTreeEntryByPath(relpath)
@@ -17,3 +20,21 @@ func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
return nil, ErrNotExist{"", relpath}
}
+
+// GetBlobByFoldedPath returns the blob object at relpath, regardless of the
+// case of relpath. If there are multiple files with the same case-insensitive
+// name, the first one found will be returned.
+func (t *Tree) GetBlobByFoldedPath(relpath string) (*Blob, error) {
+ entries, err := t.ListEntries()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, entry := range entries {
+ if strings.EqualFold(entry.Name(), relpath) {
+ return t.GetBlobByPath(entry.Name())
+ }
+ }
+
+ return nil, ErrNotExist{"", relpath}
+}
diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go
index daf8c6cfdd..0d9c0c98ac 100644
--- a/modules/lfs/content_store.go
+++ b/modules/lfs/content_store.go
@@ -4,6 +4,7 @@
package lfs
import (
+ "crypto/sha256"
"encoding/hex"
"errors"
"hash"
@@ -12,8 +13,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
-
- "github.com/minio/sha256-simd"
)
var (
diff --git a/modules/lfs/pointer.go b/modules/lfs/pointer.go
index 3e5bb8f91d..ebde20f826 100644
--- a/modules/lfs/pointer.go
+++ b/modules/lfs/pointer.go
@@ -4,6 +4,7 @@
package lfs
import (
+ "crypto/sha256"
"encoding/hex"
"errors"
"fmt"
@@ -12,8 +13,6 @@ import (
"regexp"
"strconv"
"strings"
-
- "github.com/minio/sha256-simd"
)
const (
diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go
index 4406803694..0e75e2adfd 100644
--- a/modules/markup/common/footnote.go
+++ b/modules/markup/common/footnote.go
@@ -29,12 +29,17 @@ func CleanValue(value []byte) []byte {
value = bytes.TrimSpace(value)
rs := bytes.Runes(value)
result := make([]rune, 0, len(rs))
+ needsDash := false
for _, r := range rs {
- if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '-' {
+ switch {
+ case unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_':
+ if needsDash && len(result) > 0 {
+ result = append(result, '-')
+ }
+ needsDash = false
result = append(result, unicode.ToLower(r))
- }
- if unicode.IsSpace(r) {
- result = append(result, '-')
+ default:
+ needsDash = true
}
}
return []byte(string(result))
diff --git a/modules/markup/common/footnote_test.go b/modules/markup/common/footnote_test.go
index 2327a7b14b..62763c5622 100644
--- a/modules/markup/common/footnote_test.go
+++ b/modules/markup/common/footnote_test.go
@@ -1,4 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
+// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
@@ -15,44 +16,45 @@ func TestCleanValue(t *testing.T) {
}{
// Github behavior test cases
{"", ""},
- {"test(0)", "test0"},
- {"test!1", "test1"},
- {"test:2", "test2"},
- {"test*3", "test3"},
- {"test!4", "test4"},
- {"test:5", "test5"},
- {"test*6", "test6"},
- {"test:6 a", "test6-a"},
- {"test:6 !b", "test6-b"},
- {"test:ad # df", "testad--df"},
- {"test:ad #23 df 2*/*", "testad-23-df-2"},
- {"test:ad 23 df 2*/*", "testad-23-df-2"},
- {"test:ad # 23 df 2*/*", "testad--23-df-2"},
+ {"test.0.1", "test-0-1"},
+ {"test(0)", "test-0"},
+ {"test!1", "test-1"},
+ {"test:2", "test-2"},
+ {"test*3", "test-3"},
+ {"test!4", "test-4"},
+ {"test:5", "test-5"},
+ {"test*6", "test-6"},
+ {"test:6 a", "test-6-a"},
+ {"test:6 !b", "test-6-b"},
+ {"test:ad # df", "test-ad-df"},
+ {"test:ad #23 df 2*/*", "test-ad-23-df-2"},
+ {"test:ad 23 df 2*/*", "test-ad-23-df-2"},
+ {"test:ad # 23 df 2*/*", "test-ad-23-df-2"},
{"Anchors in Markdown", "anchors-in-markdown"},
{"a_b_c", "a_b_c"},
{"a-b-c", "a-b-c"},
- {"a-b-c----", "a-b-c----"},
- {"test:6a", "test6a"},
- {"test:a6", "testa6"},
- {"tes a a a a", "tes-a-a---a--a"},
- {" tes a a a a ", "tes-a-a---a--a"},
+ {"a-b-c----", "a-b-c"},
+ {"test:6a", "test-6a"},
+ {"test:a6", "test-a6"},
+ {"tes a a a a", "tes-a-a-a-a"},
+ {" tes a a a a ", "tes-a-a-a-a"},
{"Header with \"double quotes\"", "header-with-double-quotes"},
- {"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-links-click"},
+ {"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-link-s-click"},
{"tes()", "tes"},
- {"tes(0)", "tes0"},
- {"tes{0}", "tes0"},
- {"tes[0]", "tes0"},
- {"test【0】", "test0"},
- {"tes…@a", "tesa"},
+ {"tes(0)", "tes-0"},
+ {"tes{0}", "tes-0"},
+ {"tes[0]", "tes-0"},
+ {"test【0】", "test-0"},
+ {"tes…@a", "tes-a"},
{"tes¥& a", "tes-a"},
{"tes= a", "tes-a"},
- {"tes|a", "tesa"},
- {"tes\\a", "tesa"},
- {"tes/a", "tesa"},
+ {"tes|a", "tes-a"},
+ {"tes\\a", "tes-a"},
+ {"tes/a", "tes-a"},
{"a啊啊b", "a啊啊b"},
- {"c🤔️🤔️d", "cd"},
- {"a⚡a", "aa"},
- {"e.~f", "ef"},
+ {"c🤔️🤔️d", "c-d"},
+ {"a⚡a", "a-a"},
+ {"e.~f", "e-f"},
}
for _, test := range tests {
assert.Equal(t, []byte(test.expect), CleanValue([]byte(test.param)), test.param)
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index b92b90561b..1db3cbad7e 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -137,6 +137,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
var base string
if ctx.IsWiki {
base = ctx.Links.WikiLink()
+ } else if ctx.Links.HasBranchInfo() {
+ base = ctx.Links.SrcLink()
} else {
base = ctx.Links.Base
}
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 957d773acd..d6d0e93eb2 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -524,6 +524,18 @@ func TestMathBlock(t *testing.T) {
"$$a$$",
`