diff --git a/.deadcode-out b/.deadcode-out index 026205e883..a552448ac7 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 diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 846cd55276..247d1d1f8f 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", @@ -669,6 +664,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 67030ef0f3..c873065c8d 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -410,6 +410,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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -492,11 +496,6 @@ INTERNAL_TOKEN= ;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. ;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security. ;SUCCESSFUL_TOKENS_CACHE_SIZE = 20 -;; -;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities -;; stemming from cached/logged plain-text API tokens. -;; In future releases, this will become the default behavior -;DISABLE_QUERY_AUTH_TOKEN = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -815,6 +814,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 @@ -1461,6 +1465,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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1770,9 +1776,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 beaa8cfb30..278335dab0 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -455,6 +455,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. @@ -514,6 +515,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 c08010a916..c9cf8c22e5 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 @@ -78,14 +77,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 @@ -101,7 +98,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 69b2556dd3..897a96ebd1 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,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= @@ -968,13 +966,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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/models/actions/run.go b/models/actions/run.go index 4656aa22a2..e84552682b 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -308,6 +308,17 @@ 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 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/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 8c84a2688c..6591b496d6 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,13 @@ 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"), + }) + } + SetDefaultEngine(ctx, xormEngine) return nil } @@ -297,3 +307,21 @@ 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 +} diff --git a/models/db/engine_test.go b/models/db/engine_test.go index c9ae5f1542..ba922821b0 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,37 @@ 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) +} 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..938f2cd7b7 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: 59 + 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 0104419550..b359f547b6 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: 59 + type: 1 + created_unix: 946684810 + +- + id: 1094 + repo_id: 59 + type: 2 + created_unix: 946684810 + +- + id: 1095 + repo_id: 59 + type: 3 + created_unix: 946684810 + +- + id: 1096 + repo_id: 59 + type: 4 + created_unix: 946684810 + +- + id: 1097 + repo_id: 59 + 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 a127b6bdf9..e86dac8228 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 @@ -1693,3 +1694,16 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + +- + id: 59 + 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 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index fd51379816..79fbb981f6 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: 14 + num_repos: 15 num_teams: 0 num_members: 0 visibility: 0 diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 58f158bd17..8ac2fb63f2 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,8 @@ 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), } // 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/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_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 89f28b8fca..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() { 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/unittest/mock_http.go b/models/unittest/mock_http.go new file mode 100644 index 0000000000..746f18ee2f --- /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.ReplaceAll(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 b1c46c14c9..1d0d17e886 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -223,6 +223,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 c0082f8927..24be6b55e7 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -550,3 +550,13 @@ func Test_ValidateUser(t *testing.T) { assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase)) } } + +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) +} diff --git a/modules/actions/github.go b/modules/actions/github.go index 71f81a8903..a988b2a124 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -22,6 +22,7 @@ const ( GithubEventRelease = "release" GithubEventPullRequestComment = "pull_request_comment" GithubEventGollum = "gollum" + GithubEventSchedule = "schedule" ) // canGithubEventMatch check if the input Github event can match any Gitea event. @@ -34,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, diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index c49cf3193a..00d83e06d7 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -153,6 +153,7 @@ 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, diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 2d57f19488..c8e1e553fe 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: gollum", expected: true, }, + { + desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + triggedEvent: webhook_module.HookEventSchedule, + payload: nil, + yamlOn: "on: schedule", + expected: true, + }, } for _, tc := range testCases { 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/doctor/push_mirror_consistency.go b/modules/doctor/push_mirror_consistency.go new file mode 100644 index 0000000000..68b96d6415 --- /dev/null +++ b/modules/doctor/push_mirror_consistency.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT + +package doctor + +import ( + "context" + "strings" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/log" + + "xorm.io/builder" +) + +func FixPushMirrorsWithoutGitRemote(ctx context.Context, logger log.Logger, autofix bool) error { + var missingMirrors []*repo_model.PushMirror + + err := db.Iterate(ctx, builder.Gt{"id": 0}, func(ctx context.Context, repo *repo_model.Repository) error { + pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{}) + if err != nil { + return err + } + + for i := 0; i < len(pushMirrors); i++ { + _, err = repo_model.GetPushMirrorRemoteAddress(repo.OwnerName, repo.Name, pushMirrors[i].RemoteName) + if err != nil { + if strings.Contains(err.Error(), "No such remote") { + missingMirrors = append(missingMirrors, pushMirrors[i]) + } else if logger != nil { + logger.Warn("Unable to retrieve the remote address of a mirror: %s", err) + } + } + } + + return nil + }) + if err != nil { + if logger != nil { + logger.Critical("Unable to iterate across repounits to fix push mirrors without a git remote: Error %v", err) + } + return err + } + + count := len(missingMirrors) + if !autofix { + if logger != nil { + if count == 0 { + logger.Info("Found no push mirrors with missing git remotes") + } else { + logger.Warn("Found %d push mirrors with missing git remotes", count) + } + } + return nil + } + + for i := 0; i < len(missingMirrors); i++ { + if logger != nil { + logger.Info("Removing push mirror #%d (remote: %s), for repo: %s/%s", + missingMirrors[i].ID, + missingMirrors[i].RemoteName, + missingMirrors[i].GetRepository(ctx).OwnerName, + missingMirrors[i].GetRepository(ctx).Name) + } + + err = repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ + ID: missingMirrors[i].ID, + RepoID: missingMirrors[i].RepoID, + RemoteName: missingMirrors[i].RemoteName, + }) + if err != nil { + if logger != nil { + logger.Critical("Error removing a push mirror (repo_id: %d, push_mirror: %d): %s", missingMirrors[i].Repo.ID, missingMirrors[i].ID, err) + } + return err + } + } + + return nil +} + +func init() { + Register(&Check{ + Title: "Check for push mirrors without a git remote configured", + Name: "fix-push-mirrors-without-git-remote", + IsDefault: false, + Run: FixPushMirrorsWithoutGitRemote, + Priority: 7, + }) +} 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/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/markdown_test.go b/modules/markup/markdown/markdown_test.go index 8f855f1b13..77ed126afa 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -510,6 +510,18 @@ func TestMathBlock(t *testing.T) { "$$a$$", `
a
` + nl, }, + { + `\[a b\]`, + `
a b
` + nl, + }, + { + `\[a b]`, + `

[a b]

` + nl, + }, + { + `$$a`, + `

$$a

` + nl, + }, } for _, test := range testcases { @@ -556,3 +568,201 @@ foo: bar assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) } } + +func TestFootnote(t *testing.T) { + testcases := []struct { + testcase string + expected string + }{ + { + `Citation needed[^0]. +[^0]: Source`, + `

Citation needed1.

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed[^0]`, + `

Citation needed[^0]

+`, + }, + { + `Citation needed[^1], Citation needed twice[^3] +[^3]: Source`, + `

Citation needed[^1], Citation needed twice1

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed[^0] +[^1]: Source`, + `

Citation needed[^0]

+`, + }, + { + `Citation needed[^0] +[^0]: Source 1 +[^0]: Source 2`, + `

Citation needed1

+
+
+
    +
  1. +

    Source 1 ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed![^0] +[^0]: Source`, + `

Citation needed1

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Trigger [^`, + `

Trigger [^

+`, + }, + { + `Trigger 2 [^0`, + `

Trigger 2 [^0

+`, + }, + { + `Citation needed[^0] +[^0]: Source with citation needed[^1] +[^1]: Source`, + `

Citation needed1

+
+
+
    +
  1. +

    Source with citation needed2 ↩︎

    +
  2. +
  3. +

    Source ↩︎

    +
  4. +
+
+`, + }, + { + `Citation needed[^#] +[^#]: Source`, + `

Citation needed1

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed[^0] + [^0]: Source`, + `

Citation needed[^0]
+[^0]: Source

+`, + }, + { + `[^0]: Source + +Citation needed[^0].`, + `

Citation needed1.

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed[^] +[^]: Source`, + `

Citation needed[^]
+[^]: Source

+`, + }, + { + `Citation needed[^0] +[^0] Source`, + `

Citation needed[^0]
+[^0] Source

+`, + }, + { + `Citation needed[^0] +[^0 Source`, + `

Citation needed[^0]
+[^0 Source

+`, + }, + { + `Citation needed[^0] [^0]: Source`, + `

Citation needed[^0] [^0]: Source

+`, + }, + { + `Citation needed[^Source here 0 # 9-3] +[^Source here 0 # 9-3]: Source`, + `

Citation needed1

+
+
+
    +
  1. +

    Source ↩︎

    +
  2. +
+
+`, + }, + { + `Citation needed[^0] +[^0]:`, + `

Citation needed1

+
+
+
    +
  1. + ↩︎
  2. +
+
+`, + }, + } + for _, test := range testcases { + res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) + assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) + assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) + } +} diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index 7f714d7239..f3262c82c0 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -55,10 +55,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex return node, parser.Close | parser.NoChildren } - reader.Advance(segment.Len() - 1) - segment.Start += 2 - node.Lines().Append(segment) - return node, parser.NoChildren + return nil, parser.NoChildren } // Continue parses the current line and returns a result of parsing. diff --git a/modules/secret/secret.go b/modules/secret/secret.go index 9c2ecd181d..e70ae1839c 100644 --- a/modules/secret/secret.go +++ b/modules/secret/secret.go @@ -7,13 +7,12 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "fmt" "io" - - "github.com/minio/sha256-simd" ) // AesEncrypt encrypts text and given key with AES. diff --git a/modules/setting/admin.go b/modules/setting/admin.go index 2d2dd26de9..d7f0ee827d 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -5,8 +5,9 @@ package setting // Admin settings var Admin struct { - DisableRegularOrgCreation bool - DefaultEmailNotification string + DisableRegularOrgCreation bool + DefaultEmailNotification string + SendNotificationEmailOnNewUser bool } func loadAdminFrom(rootCfg ConfigProvider) { diff --git a/modules/setting/database.go b/modules/setting/database.go index b68f250f78..b789611319 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -44,6 +44,7 @@ var ( ConnMaxLifetime time.Duration IterateBufferSize int AutoMigration bool + SlowQueryTreshold time.Duration }{ Timeout: 500, IterateBufferSize: 50, @@ -86,6 +87,7 @@ func loadDBSetting(rootCfg ConfigProvider) { Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true) + Database.SlowQueryTreshold = sec.Key("SLOW_QUERY_TRESHOLD").MustDuration(5 * time.Second) } // DBConnStr returns database connection string diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 9697a851d3..8add0a353b 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -7,6 +7,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "strings" "code.gitea.io/gitea/modules/log" @@ -19,6 +20,8 @@ const ( RepoCreatingPublic = "public" ) +var RecognisedRepositoryDownloadOrCloneMethods = []string{"download-zip", "download-targz", "download-bundle", "vscode-clone", "vscodium-clone", "cite"} + // ItemsPerPage maximum items per page in forks, watchers and stars of a repo const ItemsPerPage = 40 @@ -43,6 +46,7 @@ var ( DisabledRepoUnits []string DefaultRepoUnits []string DefaultForkRepoUnits []string + DownloadOrCloneMethods []string PrefixArchiveFiles bool DisableMigrations bool DisableStars bool `ini:"DISABLE_STARS"` @@ -150,7 +154,7 @@ var ( DefaultPrivate: RepoCreatingLastUserVisibility, DefaultPushCreatePrivate: true, MaxCreationLimit: -1, - PreferredLicenses: []string{"Apache License 2.0", "MIT License"}, + PreferredLicenses: []string{"Apache-2.0", "MIT"}, DisableHTTPGit: false, AccessControlAllowOrigin: "", UseCompatSSHURI: false, @@ -160,6 +164,7 @@ var ( DisabledRepoUnits: []string{}, DefaultRepoUnits: []string{}, DefaultForkRepoUnits: []string{}, + DownloadOrCloneMethods: []string{"download-zip", "download-targz", "download-bundle", "vscode-clone"}, PrefixArchiveFiles: true, DisableMigrations: false, DisableStars: false, @@ -358,4 +363,10 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { if err := loadRepoArchiveFrom(rootCfg); err != nil { log.Fatal("loadRepoArchiveFrom: %v", err) } + + for _, method := range Repository.DownloadOrCloneMethods { + if !slices.Contains(RecognisedRepositoryDownloadOrCloneMethods, method) { + log.Error("Unrecognised repository download or clone method: %s", method) + } + } } diff --git a/modules/setting/security.go b/modules/setting/security.go index 4adfe20635..92caa05fad 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -34,7 +34,6 @@ var ( PasswordHashAlgo string PasswordCheckPwn bool SuccessfulTokensCacheSize int - DisableQueryAuthToken bool CSRFCookieName = "_csrf" CSRFCookieHTTPOnly = true ) @@ -158,11 +157,4 @@ func loadSecurityFrom(rootCfg ConfigProvider) { PasswordComplexity = append(PasswordComplexity, name) } } - - // TODO: default value should be true in future releases - DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) - - if !DisableQueryAuthToken { - log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.") - } } diff --git a/modules/setting/service.go b/modules/setting/service.go index befb94b61b..afaee18101 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -68,6 +68,7 @@ var Service = struct { DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool DefaultUserIsRestricted bool + AllowDotsInUsernames bool EnableTimetracking bool DefaultEnableTimetracking bool DefaultEnableDependencies bool @@ -180,6 +181,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false) + Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) if Service.EnableTimetracking { Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 0babe84410..e8944c1130 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -402,6 +402,16 @@ func (p *PullRequestPayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } +type HookScheduleAction string + +const ( + HookScheduleCreated HookScheduleAction = "schedule" +) + +type SchedulePayload struct { + Action HookScheduleAction `json:"action"` +} + // ReviewPayload FIXME type ReviewPayload struct { Type string `json:"type"` diff --git a/modules/util/keypair.go b/modules/util/keypair.go index 97f2d9ebca..8b86c142af 100644 --- a/modules/util/keypair.go +++ b/modules/util/keypair.go @@ -7,10 +7,9 @@ import ( "crypto" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/x509" "encoding/pem" - - "github.com/minio/sha256-simd" ) // GenerateKeyPair generates a public and private keypair diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go index c9925f7988..c6f68c845a 100644 --- a/modules/util/keypair_test.go +++ b/modules/util/keypair_test.go @@ -7,12 +7,12 @@ import ( "crypto" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/x509" "encoding/pem" "regexp" "testing" - "github.com/minio/sha256-simd" "github.com/stretchr/testify/assert" ) diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index f6e00f3887..567ad867fe 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -117,13 +117,20 @@ func IsValidExternalTrackerURLFormat(uri string) bool { } var ( - validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`) - invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars + validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`) + validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`) + + // No consecutive or trailing non-alphanumeric chars, catches both cases + invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) ) // IsValidUsername checks if username is valid func IsValidUsername(name string) bool { // It is difficult to find a single pattern that is both readable and effective, // but it's easier to use positive and negative checks. - return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name) + if setting.Service.AllowDotsInUsernames { + return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name) + } + + return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name) } diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go index 52f383f698..a1bdf2a29c 100644 --- a/modules/validation/helpers_test.go +++ b/modules/validation/helpers_test.go @@ -155,7 +155,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) { } } -func TestIsValidUsername(t *testing.T) { +func TestIsValidUsernameAllowDots(t *testing.T) { + setting.Service.AllowDotsInUsernames = true tests := []struct { arg string want bool @@ -185,3 +186,31 @@ func TestIsValidUsername(t *testing.T) { }) } } + +func TestIsValidUsernameBanDots(t *testing.T) { + setting.Service.AllowDotsInUsernames = false + defer func() { + setting.Service.AllowDotsInUsernames = true + }() + + tests := []struct { + arg string + want bool + }{ + {arg: "a", want: true}, + {arg: "abc", want: true}, + {arg: "0.b-c", want: false}, + {arg: "a.b-c_d", want: false}, + {arg: ".abc", want: false}, + {arg: "abc.", want: false}, + {arg: "a..bc", want: false}, + {arg: "a...bc", want: false}, + {arg: "a.-bc", want: false}, + {arg: "a._bc", want: false}, + } + for _, tt := range tests { + t.Run(tt.arg, func(t *testing.T) { + assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername[AllowDotsInUsernames=false](%v)", tt.arg) + }) + } +} diff --git a/modules/web/handler.go b/modules/web/handler.go index 26b7428016..728cc5a160 100644 --- a/modules/web/handler.go +++ b/modules/web/handler.go @@ -147,6 +147,16 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler { } } + if hp, ok := handler.(func(next http.Handler) http.HandlerFunc); ok { + return func(next http.Handler) http.Handler { + h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + routing.UpdateFuncInfo(req.Context(), funcInfo) + h.ServeHTTP(resp, req) + }) + } + } + provider := func(next http.Handler) http.Handler { return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) { // wrap the response writer to check whether the response has been written diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index d9bcdf3b2a..4e7fca80e2 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -8,6 +8,7 @@ import ( "reflect" "strings" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" @@ -135,7 +136,11 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo case validation.ErrRegexPattern: data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) case validation.ErrUsername: - data["ErrorMsg"] = trName + l.Tr("form.username_error") + if setting.Service.AllowDotsInUsernames { + data["ErrorMsg"] = trName + l.Tr("form.username_error") + } else { + data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots") + } case validation.ErrInvalidGroupTeamMap: data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message) default: diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go index 08d83f94be..c1d1af8528 100644 --- a/modules/web/middleware/data.go +++ b/modules/web/middleware/data.go @@ -53,6 +53,7 @@ func CommonTemplateContextData() ContextData { "ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage, "ShowFooterVersion": setting.Other.ShowFooterVersion, "DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives, + "DownloadOrCloneMethods": setting.Repository.DownloadOrCloneMethods, "EnableSwagger": setting.API.EnableSwagger, "EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn, diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 7042d391b7..0013691c02 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -31,6 +31,7 @@ const ( HookEventRepository HookEventType = "repository" HookEventRelease HookEventType = "release" HookEventPackage HookEventType = "package" + HookEventSchedule HookEventType = "schedule" ) // Event returns the HookEventType as an event string diff --git a/options/locales/gitea_en-US.ini b/options/locales/gitea_en-US.ini index 996c80efd4..db11a18caf 100644 --- a/options/locales/gitea_en-US.ini +++ b/options/locales/gitea_en-US.ini @@ -295,6 +295,7 @@ default_allow_create_organization = Allow Creation of Organizations by Default default_allow_create_organization_popup = Allow new user accounts to create organizations by default. default_enable_timetracking = Enable Time Tracking by Default default_enable_timetracking_popup = Enable time tracking for new repositories by default. +allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts. no_reply_address = Hidden Email Domain no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. password_algorithm = Password Hash Algorithm @@ -367,7 +368,7 @@ forgot_password_title= Forgot Password forgot_password = Forgot password? sign_up_now = Need an account? Register now. sign_up_successful = Account was successfully created. Welcome! -confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. +confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. If the email is incorrect, you can log in, and request another confirmation email to be sent to a different address. must_change_password = Update your password allow_password_change = Require user to change password (recommended) reset_password_mail_sent_prompt = A confirmation email has been sent to %s. Please check your inbox within the next %s to complete the account recovery process. @@ -377,6 +378,9 @@ prohibit_login = Sign In Prohibited prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator. resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. +change_unconfirmed_email_summary = Change the email address activation mail is sent to. +change_unconfirmed_email = If you have given the wrong email address during registration, you can change it below, and a confirmation will be sent to the new address instead. +change_unconfirmed_email_error = Unable to change the email address: %v resend_mail = Click here to resend your activation email email_not_associate = The email address is not associated with any account. send_reset_mail = Send Account Recovery Email @@ -440,6 +444,10 @@ activate_email = Verify your email address activate_email.title = %s, please verify your email address activate_email.text = Please click the following link to verify your email address within %s: +admin.new_user.subject = New user %s just signed up +admin.new_user.user_info = User Information +admin.new_user.text = Please click here to manage the user from the admin panel. + register_notify = Welcome to Gitea register_notify.title = %[1]s, welcome to %[2]s register_notify.text_1 = this is your registration confirmation email for %s! @@ -534,6 +542,7 @@ include_error = ` must contain substring "%s".` glob_pattern_error = ` glob pattern is invalid: %s.` regex_pattern_error = ` regex pattern is invalid: %s.` username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` +username_error_no_dots = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-') and underscore ('_'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` invalid_group_team_map_error = ` mapping is invalid: %s` unknown_error = Unknown error: captcha_incorrect = The CAPTCHA code is incorrect. @@ -967,6 +976,7 @@ all_branches = All branches fork_no_valid_owners = This repository can not be forked because there are no valid owners. use_template = Use this template clone_in_vsc = Clone in VS Code +clone_in_vscodium = Clone in VS Codium download_zip = Download ZIP download_tar = Download TAR.GZ download_bundle = Download BUNDLE @@ -1253,6 +1263,7 @@ editor.new_branch_name_desc = New branch name… editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty. editor.filename_is_invalid = The filename is invalid: "%s". +editor.invalid_commit_mail = Invalid mail for creating a commit. editor.branch_does_not_exist = Branch "%s" does not exist in this repository. editor.branch_already_exists = Branch "%s" already exists in this repository. editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository. @@ -1291,6 +1302,8 @@ commits.find = Search commits.search_all = All Branches commits.author = Author commits.message = Message +commits.browse_further = Browse further +commits.renamed_from = Renamed from %s commits.date = Date commits.older = Older commits.newer = Newer @@ -1903,6 +1916,7 @@ wiki.page_title = Page title wiki.page_content = Page content wiki.default_commit_message = Write a note about this page update (optional). wiki.save_page = Save Page +wiki.cancel = Cancel wiki.last_commit_info = %s edited this page %s wiki.edit_page_button = Edit wiki.new_page_button = New Page @@ -2041,6 +2055,7 @@ settings.branches.update_default_branch = Update Default Branch settings.branches.add_new_rule = Add New Rule settings.advanced_settings = Advanced Settings settings.wiki_desc = Enable Repository Wiki +settings.wiki_globally_editable = Allow anyone to edit the Wiki settings.use_internal_wiki = Use Built-In Wiki settings.use_external_wiki = Use External Wiki settings.external_wiki_url = External Wiki URL diff --git a/routers/api/packages/chef/auth.go b/routers/api/packages/chef/auth.go index 3aef8281a4..a790e9a363 100644 --- a/routers/api/packages/chef/auth.go +++ b/routers/api/packages/chef/auth.go @@ -8,6 +8,7 @@ import ( "crypto" "crypto/rsa" "crypto/sha1" + "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" @@ -26,8 +27,6 @@ import ( chef_module "code.gitea.io/gitea/modules/packages/chef" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth" - - "github.com/minio/sha256-simd" ) const ( diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index 0b93382b01..5106395eb1 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -6,6 +6,7 @@ package maven import ( "crypto/md5" "crypto/sha1" + "crypto/sha256" "crypto/sha512" "encoding/hex" "encoding/xml" @@ -26,8 +27,6 @@ import ( maven_module "code.gitea.io/gitea/modules/packages/maven" "code.gitea.io/gitea/routers/api/packages/helper" packages_service "code.gitea.io/gitea/services/packages" - - "github.com/minio/sha256-simd" ) const ( diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c5e1a42394..738345d78b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -6,7 +6,7 @@ // // This documentation describes the Gitea API. // -// Schemes: http, https +// Schemes: https, http // BasePath: /api/v1 // Version: {{AppVer | JSEscape | Safe}} // License: MIT http://opensource.org/licenses/MIT @@ -35,12 +35,10 @@ // type: apiKey // name: token // in: query -// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead. // AccessToken: // type: apiKey // name: access_token // in: query -// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead. // AuthorizationHeaderToken: // type: apiKey // name: Authorization @@ -808,13 +806,6 @@ func individualPermsChecker(ctx *context.APIContext) { } } -// check for and warn against deprecated authentication options -func checkDeprecatedAuthMethods(ctx *context.APIContext) { - if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" { - ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.") - } -} - // Routes registers all v1 APIs routes to web application. func Routes() *web.Route { m := web.NewRoute() @@ -831,8 +822,6 @@ func Routes() *web.Route { } m.Use(context.APIContexter()) - m.Use(checkDeprecatedAuthMethods) - // Get user from session if logged in. m.Use(apiAuth(buildAuthGroup())) diff --git a/routers/install/install.go b/routers/install/install.go index 1dbfafcd14..cb7818bd33 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -358,6 +358,12 @@ func SubmitInstall(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form) return } + if len(form.AdminPasswd) < setting.MinPasswordLength { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminPasswd"] = true + ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplInstall, form) + return + } } // Init the engine with migration diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index a8f8d1bbe1..fe3d9782bb 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -32,6 +32,7 @@ import ( "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" + notify_service "code.gitea.io/gitea/services/notify" "github.com/markbates/goth" ) @@ -600,6 +601,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. } } + notify_service.NewUserSignUp(ctx, u) // update external user information if gothUser != nil { if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil { @@ -645,13 +647,22 @@ func Activate(ctx *context.Context) { } // Resend confirmation email. if setting.Service.RegisterEmailConfirm { - if ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName) { + var cacheKey string + if ctx.Cache.IsExist("MailChangedJustNow_" + ctx.Doer.LowerName) { + cacheKey = "MailChangedLimit_" + if err := ctx.Cache.Delete("MailChangedJustNow_" + ctx.Doer.LowerName); err != nil { + log.Error("Delete cache(MailChangedJustNow) fail: %v", err) + } + } else { + cacheKey = "MailResendLimit_" + } + if ctx.Cache.IsExist(cacheKey + ctx.Doer.LowerName) { ctx.Data["ResendLimited"] = true } else { ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale) mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer) - if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil { + if err := ctx.Cache.Put(cacheKey+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil { log.Error("Set cache(MailResendLimit) fail: %v", err) } } @@ -685,6 +696,43 @@ func Activate(ctx *context.Context) { func ActivatePost(ctx *context.Context) { code := ctx.FormString("code") if len(code) == 0 { + email := ctx.FormString("email") + if len(email) > 0 { + ctx.Data["IsActivatePage"] = true + if ctx.Doer == nil || ctx.Doer.IsActive { + ctx.NotFound("invalid user", nil) + return + } + // Change the primary email + if setting.Service.RegisterEmailConfirm { + if ctx.Cache.IsExist("MailChangeLimit_" + ctx.Doer.LowerName) { + ctx.Data["ResendLimited"] = true + } else { + ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale) + err := user_model.ReplaceInactivePrimaryEmail(ctx, ctx.Doer.Email, &user_model.EmailAddress{ + UID: ctx.Doer.ID, + Email: email, + }) + if err != nil { + ctx.Data["IsActivatePage"] = false + log.Error("Couldn't replace inactive primary email of user %d: %v", ctx.Doer.ID, err) + ctx.RenderWithErr(ctx.Tr("auth.change_unconfirmed_email_error", err), TplActivate, nil) + return + } + if err := ctx.Cache.Put("MailChangeLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil { + log.Error("Set cache(MailChangeLimit) fail: %v", err) + } + if err := ctx.Cache.Put("MailChangedJustNow_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil { + log.Error("Set cache(MailChangedJustNow) fail: %v", err) + } + + // Confirmation mail will be re-sent after the redirect to `/user/activate` below. + } + } else { + ctx.Data["ServiceNotEnabled"] = true + } + } + ctx.Redirect(setting.AppSubURL + "/user/activate") return } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 9968fbe6a6..60229bc873 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -951,10 +951,16 @@ func SignInOAuthCallback(ctx *context.Context) { return } else if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration { // create new user with details from oauth2 provider - var missingFields []string if gothUser.UserID == "" { - missingFields = append(missingFields, "sub") + log.Error("OAuth2 Provider %s returned empty or missing field: UserID", authSource.Name) + if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" { + log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields") + } + err = fmt.Errorf("OAuth2 Provider %s returned empty or missing field: UserID", authSource.Name) + ctx.ServerError("CreateUser", err) + return } + var missingFields []string if gothUser.Email == "" { missingFields = append(missingFields, "email") } @@ -962,12 +968,10 @@ func SignInOAuthCallback(ctx *context.Context) { missingFields = append(missingFields, "nickname") } if len(missingFields) > 0 { - log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields) - if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" { - log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields") - } - err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields) - ctx.ServerError("CreateUser", err) + // we don't have enough information to create an account automatically, + // so we prompt the user for the remaining bits + log.Trace("OAuth2 Provider %s returned empty or missing fields: %s, prompting the user for them", authSource.Name, missingFields) + showLinkingLogin(ctx, gothUser) return } u = &user_model.User{ diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go index 4d4918a8fd..c4fa68fd61 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/gorilla/feeds" + "github.com/jaytaylor/html2text" ) func toBranchLink(ctx *context.Context, act *activities_model.Action) string { @@ -239,8 +240,15 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio content = desc } + // It's a common practice for feed generators to use plain text titles. + // See https://codeberg.org/forgejo/forgejo/pulls/1595 + plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true}) + if err != nil { + return nil, err + } + items = append(items, &feeds.Item{ - Title: title, + Title: plainTitle, Link: link, Description: desc, Author: &feeds.Author{ diff --git a/routers/web/feed/render.go b/routers/web/feed/render.go index 8931dae8cc..41f9af1c8c 100644 --- a/routers/web/feed/render.go +++ b/routers/web/feed/render.go @@ -8,11 +8,12 @@ import ( ) // RenderBranchFeed render format for branch or file -func RenderBranchFeed(ctx *context.Context) { - _, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req) - if ctx.Repo.TreePath == "" { - ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType) - } else { - ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType) +func RenderBranchFeed(feedType string) func(ctx *context.Context) { + return func(ctx *context.Context) { + if ctx.Repo.TreePath == "" { + ShowBranchFeed(ctx, ctx.Repo.Repository, feedType) + } else { + ShowFileFeed(ctx, ctx.Repo.Repository, feedType) + } } } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 1cdae32a32..b6984567e5 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -46,6 +46,20 @@ func View(ctx *context_module.Context) { ctx.HTML(http.StatusOK, tplViewActions) } +func ViewLatest(ctx *context_module.Context) { + run, err := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID) + if err != nil { + ctx.NotFound("GetLatestRun", err) + return + } + err = run.LoadAttributes(ctx) + if err != nil { + ctx.ServerError("LoadAttributes", err) + return + } + ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect) +} + type ViewRequest struct { LogCursors []struct { Step int `json:"step"` diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index bd393e7fb7..a23ea39af6 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) { ctx.ServerError("CommitsByFileAndRange", err) return } + oldestCommit := commits[len(commits)-1] + + renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String()) + if err != nil { + ctx.ServerError("GetCommitFileRenames", err) + return + } + + for _, renames := range renamedFiles { + if renames[1] == fileName { + ctx.Data["OldFilename"] = renames[0] + ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0]) + break + } + } + ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository) ctx.Data["Username"] = ctx.Repo.Owner.Name diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 5e7cd1caa3..c19e0aa32d 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -14,6 +14,7 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" @@ -99,6 +100,27 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) { return treeNames, treePaths } +// getSelectableEmailAddresses returns which emails can be used by the user as +// email for a Git commiter. +func getSelectableEmailAddresses(ctx *context.Context) ([]*user_model.ActivatedEmailAddress, error) { + // Retrieve emails that the user could use for commiter identity. + commitEmails, err := user_model.GetActivatedEmailAddresses(ctx, ctx.Doer.ID) + if err != nil { + return nil, fmt.Errorf("GetActivatedEmailAddresses: %w", err) + } + + // Allow for the placeholder mail to be used. Use -1 as ID to identify + // this entry to be the placerholder mail of the user. + placeholderMail := &user_model.ActivatedEmailAddress{ID: -1, Email: ctx.Doer.GetPlaceholderEmail()} + if ctx.Doer.KeepEmailPrivate { + commitEmails = append([]*user_model.ActivatedEmailAddress{placeholderMail}, commitEmails...) + } else { + commitEmails = append(commitEmails, placeholderMail) + } + + return commitEmails, nil +} + func editFile(ctx *context.Context, isNewFile bool) { ctx.Data["PageIsEdit"] = true ctx.Data["IsNewFile"] = isNewFile @@ -177,6 +199,12 @@ func editFile(ctx *context.Context, isNewFile bool) { treeNames = append(treeNames, fileName) } + commitEmails, err := getSelectableEmailAddresses(ctx) + if err != nil { + ctx.ServerError("getSelectableEmailAddresses", err) + return + } + ctx.Data["TreeNames"] = treeNames ctx.Data["TreePaths"] = treePaths ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() @@ -192,6 +220,8 @@ func editFile(ctx *context.Context, isNewFile bool) { ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",") ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath) + ctx.Data["CommitMails"] = commitEmails + ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail() ctx.HTML(http.StatusOK, tplEditFile) } @@ -227,6 +257,12 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b branchName = form.NewBranchName } + commitEmails, err := getSelectableEmailAddresses(ctx) + if err != nil { + ctx.ServerError("getSelectableEmailAddresses", err) + return + } + ctx.Data["PageIsEdit"] = true ctx.Data["PageHasPosted"] = true ctx.Data["IsNewFile"] = isNewFile @@ -243,6 +279,8 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",") ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath) + ctx.Data["CommitMails"] = commitEmails + ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail() if ctx.HasError() { ctx.HTML(http.StatusOK, tplEditFile) @@ -277,6 +315,30 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b operation = "create" } + gitIdentity := &files_service.IdentityOptions{ + Name: ctx.Doer.Name, + } + + // -1 is defined as placeholder email. + if form.CommitMailID == -1 { + gitIdentity.Email = ctx.Doer.GetPlaceholderEmail() + } else { + // Check if the given email is activated. + email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, form.CommitMailID) + if err != nil { + ctx.ServerError("GetEmailAddressByID", err) + return + } + + if email == nil || !email.IsActivated { + ctx.Data["Err_CommitMailID"] = true + ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, &form) + return + } + + gitIdentity.Email = email.Email + } + if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ LastCommitID: form.LastCommit, OldBranch: ctx.Repo.BranchName, @@ -290,7 +352,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")), }, }, - Signoff: form.Signoff, + Signoff: form.Signoff, + Author: gitIdentity, + Committer: gitIdentity, }); err != nil { // This is where we handle all the errors thrown by files_service.ChangeRepoFiles if git.IsErrNotExist(err) { diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index f4836d14e4..6d458aaf79 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2485,7 +2485,8 @@ func UpdatePullReviewRequest(ctx *context.Context) { func SearchIssues(ctx *context.Context) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.Error(http.StatusUnprocessableEntity, err.Error()) + log.Error("GetQueryBeforeSince: %v", err) + ctx.Error(http.StatusUnprocessableEntity, "invalid before or since") return } @@ -2522,10 +2523,11 @@ func SearchIssues(ctx *context.Context) { if ctx.FormString("owner") != "" { owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner")) if err != nil { + log.Error("GetUserByName: %v", err) if user_model.IsErrUserNotExist(err) { ctx.Error(http.StatusBadRequest, "Owner not found", err.Error()) } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + ctx.Error(http.StatusInternalServerError) } return } @@ -2536,15 +2538,16 @@ func SearchIssues(ctx *context.Context) { } if ctx.FormString("team") != "" { if ctx.FormString("owner") == "" { - ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") + ctx.Error(http.StatusBadRequest, "Owner organisation is required for filtering on team") return } team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team")) if err != nil { + log.Error("GetTeam: %v", err) if organization.IsErrTeamNotExist(err) { - ctx.Error(http.StatusBadRequest, "Team not found", err.Error()) + ctx.Error(http.StatusBadRequest) } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + ctx.Error(http.StatusInternalServerError) } return } @@ -2557,7 +2560,8 @@ func SearchIssues(ctx *context.Context) { } repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err.Error()) + log.Error("SearchRepositoryIDs: %v", err) + ctx.Error(http.StatusInternalServerError) return } if len(repoIDs) == 0 { @@ -2591,7 +2595,8 @@ func SearchIssues(ctx *context.Context) { } includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetLabelIDsByNames", err.Error()) + log.Error("GetLabelIDsByNames: %v", err) + ctx.Error(http.StatusInternalServerError) return } } @@ -2605,7 +2610,8 @@ func SearchIssues(ctx *context.Context) { } includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetMilestoneIDsByNames", err.Error()) + log.Error("GetMilestoneIDsByNames: %v", err) + ctx.Error(http.StatusInternalServerError) return } } @@ -2672,12 +2678,14 @@ func SearchIssues(ctx *context.Context) { ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchIssues", err.Error()) + log.Error("SearchIssues: %v", err) + ctx.Error(http.StatusInternalServerError) return } issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err.Error()) + log.Error("GetIssuesByIDs: %v", err) + ctx.Error(http.StatusInternalServerError) return } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 37f3e666ef..da52840fa5 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -966,6 +966,18 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi return } + // determine if the user viewing the pull request can edit the head branch + if ctx.Doer != nil && pull.HeadRepo != nil && !pull.HasMerged { + headRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + ctx.Data["HeadBranchIsEditable"] = pull.HeadRepo.CanEnableEditor() && issues_model.CanMaintainerWriteToBranch(ctx, headRepoPerm, pull.HeadBranch, ctx.Doer) + ctx.Data["SourceRepoLink"] = pull.HeadRepo.Link() + ctx.Data["HeadBranch"] = pull.HeadBranch + } + if ctx.IsSigned && ctx.Doer != nil { if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil { ctx.ServerError("CanMarkConversation", err) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 595d599fe1..61eeb53c37 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -391,7 +391,14 @@ func NewReleasePost(ctx *context.Context) { return } - if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { + objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat() + if err != nil { + ctx.ServerError("GetCommit", err) + return + } + // form.Target can be a branch name or a full commitID. + if !ctx.Repo.GitRepo.IsBranchExist(form.Target) && + len(form.Target) == objectFormat.FullLength() && !ctx.Repo.GitRepo.IsCommitExist(form.Target) { ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form) return } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 5d61f4e0e5..a86a940b14 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -474,10 +474,17 @@ func SettingsPost(ctx *context.Context) { }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { + var wikiPermissions repo_model.UnitAccessMode + if form.GloballyWriteableWiki { + wikiPermissions = repo_model.UnitAccessModeWrite + } else { + wikiPermissions = repo_model.UnitAccessModeRead + } units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: new(repo_model.UnitConfig), + RepoID: repo.ID, + Type: unit_model.TypeWiki, + Config: new(repo_model.UnitConfig), + DefaultPermissions: wikiPermissions, }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) } else { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 9cf0dff5d8..576e17a4f9 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -166,7 +166,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { if ctx.Repo.TreePath != "" { ctx.Data["HideRepoInfo"] = true - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName) } subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true) @@ -348,7 +348,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } defer dataRc.Close() - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName) ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileName"] = blob.Name() ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) @@ -728,12 +728,19 @@ func Home(ctx *context.Context) { if setting.Other.EnableFeed { isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) if isFeed { - switch { - case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType): + if ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType) { feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType) - case ctx.Repo.TreePath == "": + return + } + + if ctx.Repo.Repository.IsEmpty { + ctx.NotFound("MustBeNotEmpty", nil) + return + } + + if ctx.Repo.TreePath == "" { feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType) - case ctx.Repo.TreePath != "": + } else { feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType) } return diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 204d4adbd4..ddb74443f1 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -710,12 +710,15 @@ func UsernameSubRoute(ctx *context.Context) { reloadParam := func(suffix string) (success bool) { ctx.SetParams("username", strings.TrimSuffix(username, suffix)) context_service.UserAssignmentWeb()(ctx) + if ctx.Written() { + return false + } // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name)) return false } - return !ctx.Written() + return true } switch { case strings.HasSuffix(username, ".png"): diff --git a/routers/web/web.go b/routers/web/web.go index 62526e2091..8d093b231f 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -49,17 +49,12 @@ import ( _ "code.gitea.io/gitea/modules/session" // to registers all internal adapters "gitea.com/go-chi/captcha" - "github.com/NYTimes/gziphandler" chi_middleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" + "github.com/klauspost/compress/gzhttp" "github.com/prometheus/client_golang/prometheus" ) -const ( - // GzipMinSize represents min size to compress for the body size of response - GzipMinSize = 1400 -) - // optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests. func optionsCorsHandler() func(next http.Handler) http.Handler { var corsHandler func(next http.Handler) http.Handler @@ -245,11 +240,11 @@ func Routes() *web.Route { var mid []any if setting.EnableGzip { - h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize)) + wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false)) if err != nil { - log.Fatal("GzipHandlerWithOpts failed: %v", err) + log.Fatal("gzhttp.NewWrapper failed: %v", err) } - mid = append(mid, h) + mid = append(mid, wrapper) } if setting.Service.EnableCaptcha { @@ -1245,7 +1240,7 @@ func registerRoutes(m *web.Route) { Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost) m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch). Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) - m.Combo("/_cherrypick/{sha:([a-f0-9]{7,40})}/*").Get(repo.CherryPick). + m.Combo("/_cherrypick/{sha:([a-f0-9]{4,40})}/*").Get(repo.CherryPick). Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost) }, repo.MustBeEditable) m.Group("", func() { @@ -1361,22 +1356,25 @@ func registerRoutes(m *web.Route) { m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) - m.Group("/runs/{run}", func() { - m.Combo(""). - Get(actions.View). - Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) - m.Group("/jobs/{job}", func() { + m.Group("/runs", func() { + m.Get("/latest", actions.ViewLatest) + m.Group("/{run}", func() { m.Combo(""). Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) + m.Group("/jobs/{job}", func() { + m.Combo(""). + Get(actions.View). + Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) + m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) + m.Get("/logs", actions.Logs) + }) + m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) + m.Post("/approve", reqRepoActionsWriter, actions.Approve) + m.Post("/artifacts", actions.ArtifactsView) + m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView) m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) - m.Get("/logs", actions.Logs) }) - m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) - m.Post("/approve", reqRepoActionsWriter, actions.Approve) - m.Post("/artifacts", actions.ArtifactsView) - m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView) - m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) }) }, reqRepoActionsReader, actions.MustEnableActions) @@ -1387,8 +1385,8 @@ func registerRoutes(m *web.Route) { m.Combo("/*"). Get(repo.Wiki). Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) - m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) - m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) + m.Get("/commit/{sha:[a-f0-9]{4,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) + m.Get("/commit/{sha:[a-f0-9]{4,40}}.{ext:patch|diff}", repo.RawDiff) }, repo.MustEnableWiki, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink() @@ -1448,7 +1446,7 @@ func registerRoutes(m *web.Route) { m.Group("/commits", func() { m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) @@ -1457,8 +1455,8 @@ func registerRoutes(m *web.Route) { m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) m.Group("/files", func() { m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) - m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) + m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) + m.Get("/{shaFrom:[a-f0-9]{4,40}}..{shaTo:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) m.Group("/reviews", func() { m.Get("/new_comment", repo.RenderNewCodeCommentForm) m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) @@ -1508,13 +1506,13 @@ func registerRoutes(m *web.Route) { m.Group("", func() { m.Get("/graph", repo.Graph) - m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) - m.Get("/commit/{sha:([a-f0-9]{7,40})$}/load-branches-and-tags", repo.LoadBranchesAndTags) - m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) + m.Get("/commit/{sha:([a-f0-9]{4,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) + m.Get("/commit/{sha:([a-f0-9]{4,40})$}/load-branches-and-tags", repo.LoadBranchesAndTags) + m.Get("/cherry-pick/{sha:([a-f0-9]{4,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) - m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) - m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) + m.Get("/rss/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("rss")) + m.Get("/atom/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("atom")) m.Group("/src", func() { m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) @@ -1527,7 +1525,7 @@ func registerRoutes(m *web.Route) { m.Group("", func() { m.Get("/forks", repo.Forks) }, context.RepoRef(), reqRepoCodeReader) - m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) + m.Get("/commit/{sha:([a-f0-9]{4,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) }, ignSignIn, context.RepoAssignment, context.UnitTypes()) m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit) diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 705661ae7a..964d14a19a 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -424,7 +424,11 @@ func handleSchedules( return nil } - p, err := json.Marshal(input.Payload) + payload := &api.SchedulePayload{ + Action: api.HookScheduleCreated, + } + + p, err := json.Marshal(payload) if err != nil { return fmt.Errorf("json.Marshal: %w", err) } @@ -449,26 +453,14 @@ func handleSchedules( OwnerID: input.Repo.OwnerID, WorkflowID: dwf.EntryName, TriggerUserID: input.Doer.ID, - Ref: ref, + Ref: input.Repo.DefaultBranch, CommitSHA: commit.ID.String(), - Event: input.Event, + Event: webhook_module.HookEventType(api.HookScheduleCreated), EventPayload: string(p), Specs: schedules, Content: dwf.Content, } - // cancel running jobs if the event is push - if run.Event == webhook_module.HookEventPush { - // cancel running jobs of the same workflow - if err := actions_model.CancelRunningJobs( - ctx, - run.RepoID, - run.Ref, - run.WorkflowID, - ); err != nil { - log.Error("CancelRunningJobs: %v", err) - } - } crons = append(crons, run) } diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index 8eef2b67bd..dfde34d994 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -112,6 +112,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) Ref: cron.Ref, CommitSHA: cron.CommitSHA, Event: cron.Event, + TriggerEvent: string(webhook_module.HookEventSchedule), EventPayload: cron.EventPayload, ScheduleID: cron.ID, Status: actions_model.StatusWaiting, diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index f2f7858a85..08a2a05539 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -14,7 +14,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/auth/source/oauth2" @@ -63,19 +62,14 @@ func (o *OAuth2) Name() string { // representing whether the token exists or not func parseToken(req *http.Request) (string, bool) { _ = req.ParseForm() - if !setting.DisableQueryAuthToken { - // Check token. - if token := req.Form.Get("token"); token != "" { - return token, true - } - // Check access token. - if token := req.Form.Get("access_token"); token != "" { - return token, true - } - } else if req.Form.Get("token") != "" || req.Form.Get("access_token") != "" { - log.Warn("API token sent in query string but DISABLE_QUERY_AUTH_TOKEN=true") + // Check token. + if token := req.Form.Get("token"); token != "" { + return token, true + } + // Check access token. + if token := req.Form.Get("access_token"); token != "" { + return token, true } - // check header token if auHead := req.Header.Get("Authorization"); auHead != "" { auths := strings.Fields(auHead) diff --git a/services/convert/pull_review.go b/services/convert/pull_review.go index 0332606285..85f2d703d2 100644 --- a/services/convert/pull_review.go +++ b/services/convert/pull_review.go @@ -64,7 +64,7 @@ func ToPullReviewList(ctx context.Context, rl []*issues_model.Review, doer *user result := make([]*api.PullReview, 0, len(rl)) for i := range rl { // show pending reviews only for the user who created them - if rl[i].Type == issues_model.ReviewTypePending && !(doer.IsAdmin || doer.ID == rl[i].ReviewerID) { + if rl[i].Type == issues_model.ReviewTypePending && (doer == nil || !(doer.IsAdmin || doer.ID == rl[i].ReviewerID)) { continue } r, err := ToPullReview(ctx, rl[i], doer) diff --git a/services/convert/pull_test.go b/services/convert/pull_test.go index e069fa4a68..66c7313f7d 100644 --- a/services/convert/pull_test.go +++ b/services/convert/pull_test.go @@ -12,6 +12,7 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" 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/git" "code.gitea.io/gitea/modules/structs" @@ -47,3 +48,30 @@ func TestPullRequest_APIFormat(t *testing.T) { assert.Nil(t, apiPullRequest.Head.Repository) assert.EqualValues(t, -1, apiPullRequest.Head.RepoID) } + +func TestPullReviewList(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Pending review", func(t *testing.T) { + reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 6, ReviewerID: reviewer.ID}) + rl := []*issues_model.Review{review} + + t.Run("Anonymous", func(t *testing.T) { + prList, err := ToPullReviewList(db.DefaultContext, rl, nil) + assert.NoError(t, err) + assert.Empty(t, prList) + }) + t.Run("Reviewer", func(t *testing.T) { + prList, err := ToPullReviewList(db.DefaultContext, rl, reviewer) + assert.NoError(t, err) + assert.Len(t, prList, 1) + }) + t.Run("Admin", func(t *testing.T) { + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true}, unittest.Cond("id != ?", reviewer.ID)) + prList, err := ToPullReviewList(db.DefaultContext, rl, adminUser) + assert.NoError(t, err) + assert.Len(t, prList, 1) + }) + }) +} diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 86599000a5..c1dfce0a87 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -140,6 +140,7 @@ type RepoSettingForm struct { // Advanced settings EnableCode bool EnableWiki bool + GloballyWriteableWiki bool EnableExternalWiki bool ExternalWikiURL string EnableIssues bool @@ -767,6 +768,7 @@ type EditRepoFileForm struct { CommitChoice string `binding:"Required;MaxSize(50)"` NewBranchName string `binding:"GitRefName;MaxSize(100)"` LastCommit string + CommitMailID int64 `binding:"Required"` Signoff bool } diff --git a/services/lfs/server.go b/services/lfs/server.go index 62134b7d60..56714120ad 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -5,6 +5,7 @@ package lfs import ( stdCtx "context" + "crypto/sha256" "encoding/base64" "encoding/hex" "errors" @@ -33,7 +34,6 @@ import ( "code.gitea.io/gitea/modules/storage" "github.com/golang-jwt/jwt/v5" - "github.com/minio/sha256-simd" ) // requestContext contain variables from the HTTP request. diff --git a/services/mailer/mail_admin_new_user.go b/services/mailer/mail_admin_new_user.go new file mode 100644 index 0000000000..e9610e626a --- /dev/null +++ b/services/mailer/mail_admin_new_user.go @@ -0,0 +1,81 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT +package mailer + +import ( + "bytes" + "context" + "strconv" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/translation" +) + +const ( + tplNewUserMail base.TplName = "notify/admin_new_user" +) + +var sa = SendAsync + +// MailNewUser sends notification emails on new user registrations to all admins +func MailNewUser(ctx context.Context, u *user_model.User) { + if !setting.Admin.SendNotificationEmailOnNewUser { + return + } + + if setting.MailService == nil { + // No mail service configured + return + } + + recipients, err := user_model.GetAllAdmins(ctx) + if err != nil { + log.Error("user_model.GetAllAdmins: %v", err) + return + } + + langMap := make(map[string][]string) + for _, r := range recipients { + langMap[r.Language] = append(langMap[r.Language], r.Email) + } + + for lang, tos := range langMap { + mailNewUser(ctx, u, lang, tos) + } +} + +func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) { + locale := translation.NewLocale(lang) + + manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(u.ID, 10) + subject := locale.Tr("mail.admin.new_user.subject", u.Name) + body := locale.Tr("mail.admin.new_user.text", manageUserURL) + mailMeta := map[string]any{ + "NewUser": u, + "NewUserUrl": u.HTMLURL(), + "Subject": subject, + "Body": body, + "Language": locale.Language(), + "Locale": locale, + "Str2html": templates.Str2html, + } + + var mailBody bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewUserMail), mailMeta); err != nil { + log.Error("ExecuteTemplate [%s]: %v", string(tplNewUserMail)+"/body", err) + return + } + + msgs := make([]*Message, 0, len(tos)) + for _, to := range tos { + msg := NewMessage(to, subject, mailBody.String()) + msg.Info = subject + msgs = append(msgs, msg) + } + sa(msgs...) +} diff --git a/services/mailer/mail_admin_new_user_test.go b/services/mailer/mail_admin_new_user_test.go new file mode 100644 index 0000000000..b89d888ee1 --- /dev/null +++ b/services/mailer/mail_admin_new_user_test.go @@ -0,0 +1,98 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package mailer + +import ( + "context" + "strconv" + "testing" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func getTestUsers(t *testing.T) []*user_model.User { + t.Helper() + admin := new(user_model.User) + admin.Name = "testadmin" + admin.IsAdmin = true + admin.Language = "en_US" + admin.Email = "admin@example.com" + require.NoError(t, user_model.CreateUser(db.DefaultContext, admin)) + + newUser := new(user_model.User) + newUser.Name = "new_user" + newUser.Language = "en_US" + newUser.IsAdmin = false + newUser.Email = "new_user@example.com" + newUser.LastLoginUnix = 1693648327 + newUser.CreatedUnix = 1693648027 + require.NoError(t, user_model.CreateUser(db.DefaultContext, newUser)) + + return []*user_model.User{admin, newUser} +} + +func cleanUpUsers(ctx context.Context, users []*user_model.User) { + for _, u := range users { + db.DeleteByID[user_model.User](ctx, u.ID) + } +} + +func TestAdminNotificationMail_test(t *testing.T) { + translation.InitLocales(context.Background()) + locale := translation.NewLocale("") + key := "mail.admin.new_user.user_info" + translatedKey := locale.Tr(key) + require.NotEqualValues(t, key, translatedKey) + + mailService := setting.Mailer{ + From: "test@example.com", + Protocol: "dummy", + } + + setting.MailService = &mailService + + // test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER enabled + setting.Admin.SendNotificationEmailOnNewUser = true + + ctx := context.Background() + NewContext(ctx) + + users := getTestUsers(t) + oldSendAsync := sa + defer func() { + sa = oldSendAsync + cleanUpUsers(ctx, users) + }() + + called := false + sa = func(msgs ...*Message) { + assert.Equal(t, len(msgs), 1, "Test provides only one admin user, so only one email must be sent") + assert.Equal(t, msgs[0].To, users[0].Email, "checks if the recipient is the admin of the instance") + manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(users[1].ID, 10) + assert.Contains(t, msgs[0].Body, manageUserURL) + assert.Contains(t, msgs[0].Body, users[1].HTMLURL()) + assert.Contains(t, msgs[0].Body, translatedKey, "the .Locale translates to nothing") + assert.Contains(t, msgs[0].Body, users[1].Name, "user name of the newly created user") + for _, untranslated := range []string{"mail.admin", "admin.users"} { + assert.NotContains(t, msgs[0].Body, untranslated, "this is an untranslated placeholder prefix") + } + called = true + } + MailNewUser(ctx, users[1]) + assert.True(t, called) + + // test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER disabled; emails shouldn't be sent + setting.Admin.SendNotificationEmailOnNewUser = false + sa = func(msgs ...*Message) { + assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since SEND_NOTIFICATION_EMAIL_ON_NEW_USER is disabled") + } + + MailNewUser(ctx, users[1]) +} diff --git a/services/mailer/notify.go b/services/mailer/notify.go index cc4e6baf0b..5c6e635b7a 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner * log.Error("SendRepoTransferNotifyMail: %v", err) } } + +func (m *mailNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) { + MailNewUser(ctx, newUser) +} diff --git a/services/mailer/token/token.go b/services/mailer/token/token.go index aa7b567188..8a5a762d6b 100644 --- a/services/mailer/token/token.go +++ b/services/mailer/token/token.go @@ -6,14 +6,13 @@ package token import ( "context" crypto_hmac "crypto/hmac" + "crypto/sha256" "encoding/base32" "fmt" "time" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" - - "github.com/minio/sha256-simd" ) // A token is a verifiable container describing an action. diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 23d855d615..44782cbd20 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -859,6 +859,11 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { } for _, comment := range review.Comments { + // Skip code comment if it doesn't have a diff it is commeting on. + if comment.DiffHunk == "" { + continue + } + line := comment.Line if line != 0 { comment.Position = 1 diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 1e0aa2b025..25324a087f 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/json" base "code.gitea.io/gitea/modules/migration" @@ -21,18 +22,15 @@ import ( ) func TestGitlabDownloadRepo(t *testing.T) { - // Skip tests if Gitlab token is not found + // If a GitLab access token is provided, this test will make HTTP requests to the live gitlab.com instance. + // When doing so, the responses from gitlab.com will be saved as test data files. + // If no access token is available, those cached responses will be used instead. gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN") - if gitlabPersonalAccessToken == "" { - t.Skip("skipped test because GITLAB_READ_TOKEN was not in the environment") - } + fixturePath := "./testdata/gitlab/full_download" + server := unittest.NewMockWebServer(t, "https://gitlab.com", fixturePath, gitlabPersonalAccessToken != "") + defer server.Close() - resp, err := http.Get("https://gitlab.com/gitea/test_repo") - if err != nil || resp.StatusCode != http.StatusOK { - t.Skipf("Can't access test repo, skipping %s", t.Name()) - } - - downloader, err := NewGitlabDownloader(context.Background(), "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken) + downloader, err := NewGitlabDownloader(context.Background(), server.URL, "gitea/test_repo", "", "", gitlabPersonalAccessToken) if err != nil { t.Fatalf("NewGitlabDownloader is nil: %v", err) } @@ -43,8 +41,8 @@ func TestGitlabDownloadRepo(t *testing.T) { Name: "test_repo", Owner: "", Description: "Test repository for testing migration from gitlab to gitea", - CloneURL: "https://gitlab.com/gitea/test_repo.git", - OriginalURL: "https://gitlab.com/gitea/test_repo", + CloneURL: server.URL + "/gitea/test_repo.git", + OriginalURL: server.URL + "/gitea/test_repo", DefaultBranch: "master", }, repo) @@ -281,17 +279,17 @@ func TestGitlabDownloadRepo(t *testing.T) { UserName: "real6543", Content: "tada", }}, - PatchURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2.patch", + PatchURL: server.URL + "/gitea/test_repo/-/merge_requests/2.patch", Head: base.PullRequestBranch{ Ref: "feat/test", - CloneURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2", + CloneURL: server.URL + "/gitea/test_repo/-/merge_requests/2", SHA: "9f733b96b98a4175276edf6a2e1231489c3bdd23", RepoName: "test_repo", OwnerName: "lafriks", }, Base: base.PullRequestBranch{ Ref: "master", - SHA: "", + SHA: "c59c9b451acca9d106cc19d61d87afe3fbbb8b83", OwnerName: "lafriks", RepoName: "test_repo", }, @@ -309,16 +307,16 @@ func TestGitlabDownloadRepo(t *testing.T) { assertReviewsEqual(t, []*base.Review{ { IssueIndex: 1, - ReviewerID: 4102996, - ReviewerName: "zeripath", - CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC), + ReviewerID: 527793, + ReviewerName: "axifive", + CreatedAt: time.Date(2019, 11, 28, 8, 54, 41, 34000000, time.UTC), State: "APPROVED", }, { IssueIndex: 1, - ReviewerID: 527793, - ReviewerName: "axifive", - CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC), + ReviewerID: 4102996, + ReviewerName: "zeripath", + CreatedAt: time.Date(2019, 11, 28, 8, 54, 41, 34000000, time.UTC), State: "APPROVED", }, }, rvs) @@ -330,12 +328,55 @@ func TestGitlabDownloadRepo(t *testing.T) { IssueIndex: 2, ReviewerID: 4575606, ReviewerName: "real6543", - CreatedAt: time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC), + CreatedAt: time.Date(2019, 11, 28, 15, 56, 54, 108000000, time.UTC), State: "APPROVED", }, }, rvs) } +func TestGitlabSkippedIssueNumber(t *testing.T) { + // If a GitLab access token is provided, this test will make HTTP requests to the live gitlab.com instance. + // When doing so, the responses from gitlab.com will be saved as test data files. + // If no access token is available, those cached responses will be used instead. + gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN") + fixturePath := "./testdata/gitlab/skipped_issue_number" + server := unittest.NewMockWebServer(t, "https://gitlab.com", fixturePath, gitlabPersonalAccessToken != "") + defer server.Close() + + downloader, err := NewGitlabDownloader(context.Background(), server.URL, "troyengel/archbuild", "", "", gitlabPersonalAccessToken) + if err != nil { + t.Fatalf("NewGitlabDownloader is nil: %v", err) + } + repo, err := downloader.GetRepoInfo() + assert.NoError(t, err) + assertRepositoryEqual(t, &base.Repository{ + Name: "archbuild", + Owner: "troyengel", + Description: "Arch packaging and build files", + CloneURL: server.URL + "/troyengel/archbuild.git", + OriginalURL: server.URL + "/troyengel/archbuild", + DefaultBranch: "master", + }, repo) + + issues, isEnd, err := downloader.GetIssues(1, 10) + assert.NoError(t, err) + assert.True(t, isEnd) + + // the only issue in this repository has number 2 + assert.EqualValues(t, 1, len(issues)) + assert.EqualValues(t, 2, issues[0].Number) + assert.EqualValues(t, "vpn unlimited errors", issues[0].Title) + + prs, _, err := downloader.GetPullRequests(1, 10) + assert.NoError(t, err) + // the only merge request in this repository has number 1, + // but we offset it by the maximum issue number so it becomes + // pull request 3 in Forgejo + assert.EqualValues(t, 1, len(prs)) + assert.EqualValues(t, 3, prs[0].Number) + assert.EqualValues(t, "Review", prs[0].Title) +} + func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) { // mux is the HTTP request multiplexer used with the test server. mux := http.NewServeMux() diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026 new file mode 100644 index 0000000000..4ecfe77e65 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026 @@ -0,0 +1,22 @@ +X-Frame-Options: SAMEORIGIN +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT +Cf-Cache-Status: MISS +Set-Cookie: _cfuvid=TkY5Br2q4C67LJ2jZWlgdQaosj3Z4aI81Qb27PNKXfo-1701333886606-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Etag: W/"3cacfe29f44a69e84a577337eac55d89" +X-Content-Type-Options: nosniff +Ratelimit-Remaining: 1996 +Gitlab-Lb: haproxy-main-29-lb-gprd +Cache-Control: max-age=0, private, must-revalidate +Referrer-Policy: strict-origin-when-cross-origin +X-Gitlab-Meta: {"correlation_id":"291f87cd975a51a7b756806ce7b53e2e","version":"1"} +Ratelimit-Observed: 4 +Vary: Origin, Accept-Encoding +Gitlab-Sv: localhost +Content-Security-Policy: default-src 'none' +Ratelimit-Reset: 1701333946 +Content-Type: application/json +X-Runtime: 0.101081 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Limit: 2000 + +{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2022-08-26T19:41:46.691Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues?page=1&per_page=2&sort=asc&state=all b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues?page=1&per_page=2&sort=asc&state=all new file mode 100644 index 0000000000..331a82720c --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues?page=1&per_page=2&sort=asc&state=all @@ -0,0 +1,29 @@ +Cache-Control: max-age=0, private, must-revalidate +Vary: Origin, Accept-Encoding +Gitlab-Lb: haproxy-main-24-lb-gprd +Content-Type: application/json +X-Per-Page: 2 +X-Prev-Page: +Set-Cookie: _cfuvid=N.nfy5eIdFH5lXhsnMyEbeBkoxabcl1SVeyyP0_NrdE-1701333887790-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Content-Security-Policy: default-src 'none' +X-Content-Type-Options: nosniff +X-Next-Page: +X-Total-Pages: 1 +Etag: W/"4c0531a3595f741f229f5a105e013b95" +X-Gitlab-Meta: {"correlation_id":"b2eca136986f016d946685fb99287f1c","version":"1"} +Ratelimit-Observed: 8 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT +X-Frame-Options: SAMEORIGIN +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Remaining: 1992 +Gitlab-Sv: localhost +Cf-Cache-Status: MISS +Strict-Transport-Security: max-age=31536000 +Ratelimit-Limit: 2000 +X-Total: 2 +X-Page: 1 +X-Runtime: 0.178587 +Ratelimit-Reset: 1701333947 +Link: ; rel="first", ; rel="last" + +[{"id":27687675,"iid":1,"project_id":15578026,"title":"Please add an animated gif icon to the merge button","description":"I just want the merge button to hurt my eyes a little. :stuck_out_tongue_closed_eyes:","state":"closed","created_at":"2019-11-28T08:43:35.459Z","updated_at":"2019-11-28T08:46:23.304Z","closed_at":"2019-11-28T08:46:23.275Z","closed_by":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"labels":["bug","discussion"],"milestone":{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"},"assignees":[],"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"type":"ISSUE","assignee":null,"user_notes_count":0,"merge_requests_count":0,"upvotes":1,"downvotes":0,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/gitea/test_repo/-/issues/1","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/15578026/issues/1","notes":"https://gitlab.com/api/v4/projects/15578026/issues/1/notes","award_emoji":"https://gitlab.com/api/v4/projects/15578026/issues/1/award_emoji","project":"https://gitlab.com/api/v4/projects/15578026","closed_as_duplicate_of":null},"references":{"short":"#1","relative":"#1","full":"gitea/test_repo#1"},"severity":"UNKNOWN","moved_to_id":null,"service_desk_reply_to":null},{"id":27687706,"iid":2,"project_id":15578026,"title":"Test issue","description":"This is test issue 2, do not touch!","state":"closed","created_at":"2019-11-28T08:44:46.277Z","updated_at":"2019-11-28T08:45:44.987Z","closed_at":"2019-11-28T08:45:44.959Z","closed_by":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"labels":["duplicate"],"milestone":{"id":1082927,"iid":2,"project_id":15578026,"title":"1.1.0","description":"","state":"active","created_at":"2019-11-28T08:42:44.575Z","updated_at":"2019-11-28T08:42:44.575Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/2"},"assignees":[],"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"type":"ISSUE","assignee":null,"user_notes_count":2,"merge_requests_count":0,"upvotes":1,"downvotes":1,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/gitea/test_repo/-/issues/2","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/15578026/issues/2","notes":"https://gitlab.com/api/v4/projects/15578026/issues/2/notes","award_emoji":"https://gitlab.com/api/v4/projects/15578026/issues/2/award_emoji","project":"https://gitlab.com/api/v4/projects/15578026","closed_as_duplicate_of":null},"references":{"short":"#2","relative":"#2","full":"gitea/test_repo#2"},"severity":"UNKNOWN","moved_to_id":null,"service_desk_reply_to":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=1&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=1&per_page=2 new file mode 100644 index 0000000000..d8ab294ee1 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=1&per_page=2 @@ -0,0 +1,29 @@ +X-Total-Pages: 1 +Referrer-Policy: strict-origin-when-cross-origin +Cache-Control: max-age=0, private, must-revalidate +X-Frame-Options: SAMEORIGIN +X-Runtime: 0.052748 +X-Total: 2 +Gitlab-Sv: localhost +Link: ; rel="first", ; rel="last" +X-Content-Type-Options: nosniff +X-Gitlab-Meta: {"correlation_id":"22fc215ac386644c9cb8736b652cf702","version":"1"} +X-Per-Page: 2 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Reset: 1701333947 +Content-Security-Policy: default-src 'none' +Ratelimit-Remaining: 1991 +Ratelimit-Observed: 9 +X-Next-Page: +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT +Ratelimit-Limit: 2000 +Gitlab-Lb: haproxy-main-43-lb-gprd +Cf-Cache-Status: MISS +Etag: W/"69c922434ed11248c864d157eb8eabfc" +Content-Type: application/json +Vary: Origin, Accept-Encoding +X-Page: 1 +X-Prev-Page: +Set-Cookie: _cfuvid=POpffkskz4lvLcv2Fhjp7lF3MsmIOugWDzFGtb3ZUig-1701333887995-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None + +[{"id":3009580,"name":"thumbsup","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:43:40.322Z","updated_at":"2019-11-28T08:43:40.322Z","awardable_id":27687675,"awardable_type":"Issue","url":null},{"id":3009585,"name":"open_mouth","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:44:01.902Z","updated_at":"2019-11-28T08:44:01.902Z","awardable_id":27687675,"awardable_type":"Issue","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=2&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=2&per_page=2 new file mode 100644 index 0000000000..248afeff8f --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji?page=2&per_page=2 @@ -0,0 +1,31 @@ +Content-Type: application/json +Content-Length: 2 +X-Next-Page: +X-Gitlab-Meta: {"correlation_id":"6b9bc368e2cdc69a1b8e7d2dff7546c5","version":"1"} +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Reset: 1701333948 +Strict-Transport-Security: max-age=31536000 +X-Per-Page: 2 +X-Total: 2 +Ratelimit-Limit: 2000 +Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" +Link: ; rel="first", ; rel="last" +X-Runtime: 0.069944 +Ratelimit-Remaining: 1990 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT +Gitlab-Sv: localhost +X-Prev-Page: +X-Total-Pages: 1 +Ratelimit-Observed: 10 +Gitlab-Lb: haproxy-main-17-lb-gprd +Content-Security-Policy: default-src 'none' +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Accept-Ranges: bytes +Cache-Control: max-age=0, private, must-revalidate +Vary: Origin, Accept-Encoding +X-Page: 2 +Cf-Cache-Status: MISS +Set-Cookie: _cfuvid=vcfsxezcg_2Kdh8xD5coOU_uxQIH1in.6BsRttrSIYg-1701333888217-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None + +[] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=1&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=1&per_page=2 new file mode 100644 index 0000000000..70c50c8fed --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=1&per_page=2 @@ -0,0 +1,29 @@ +Cf-Cache-Status: MISS +Etag: W/"5fdbcbf64f34ba0e74ce9dd8d6e0efe3" +X-Content-Type-Options: nosniff +X-Per-Page: 2 +Ratelimit-Limit: 2000 +Set-Cookie: _cfuvid=NLtUZdQlWvWiXr4L8Zfc555FowZOCxJlA0pAOAEkNvg-1701333888445-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Runtime: 0.075612 +Ratelimit-Reset: 1701333948 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT +X-Page: 1 +X-Prev-Page: +X-Total-Pages: 3 +X-Frame-Options: SAMEORIGIN +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: default-src 'none' +Ratelimit-Observed: 11 +Gitlab-Lb: haproxy-main-09-lb-gprd +Gitlab-Sv: localhost +Ratelimit-Remaining: 1989 +Cache-Control: max-age=0, private, must-revalidate +Link: ; rel="next", ; rel="first", ; rel="last" +X-Gitlab-Meta: {"correlation_id":"a69709cf552479bc5f5f3e4e1f808790","version":"1"} +X-Next-Page: 2 +Strict-Transport-Security: max-age=31536000 +Content-Type: application/json +Vary: Origin, Accept-Encoding +X-Total: 6 + +[{"id":3009627,"name":"thumbsup","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:46:42.657Z","updated_at":"2019-11-28T08:46:42.657Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009628,"name":"thumbsdown","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:46:43.471Z","updated_at":"2019-11-28T08:46:43.471Z","awardable_id":27687706,"awardable_type":"Issue","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=2&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=2&per_page=2 new file mode 100644 index 0000000000..1014d9dbc5 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=2&per_page=2 @@ -0,0 +1,29 @@ +Ratelimit-Limit: 2000 +X-Content-Type-Options: nosniff +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT +X-Total: 6 +Set-Cookie: _cfuvid=E5GyZy0rB2zrRH0ewMyrJd1wBrt7A2sGNmOHTiWwbYk-1701333888703-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Next-Page: 3 +X-Page: 2 +Link: ; rel="prev", ; rel="next", ; rel="first", ; rel="last" +X-Gitlab-Meta: {"correlation_id":"41e59fb2e78f5e68518b81bd4ff3bb1b","version":"1"} +X-Prev-Page: 1 +Cf-Cache-Status: MISS +Referrer-Policy: strict-origin-when-cross-origin +Gitlab-Lb: haproxy-main-26-lb-gprd +Cache-Control: max-age=0, private, must-revalidate +Etag: W/"d16c513b32212d9286fce6f53340c1cf" +Vary: Origin, Accept-Encoding +X-Per-Page: 2 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Reset: 1701333948 +Content-Type: application/json +X-Runtime: 0.105758 +Ratelimit-Remaining: 1988 +Content-Security-Policy: default-src 'none' +X-Frame-Options: SAMEORIGIN +Gitlab-Sv: localhost +X-Total-Pages: 3 +Ratelimit-Observed: 12 + +[{"id":3009632,"name":"laughing","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:14.381Z","updated_at":"2019-11-28T08:47:14.381Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009634,"name":"tada","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:18.254Z","updated_at":"2019-11-28T08:47:18.254Z","awardable_id":27687706,"awardable_type":"Issue","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=3&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=3&per_page=2 new file mode 100644 index 0000000000..9c42bfb9bf --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=3&per_page=2 @@ -0,0 +1,29 @@ +Content-Type: application/json +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Referrer-Policy: strict-origin-when-cross-origin +Link: ; rel="prev", ; rel="first", ; rel="last" +Cf-Cache-Status: MISS +Etag: W/"165d37bf09a54bb31f4619cca8722cb4" +Ratelimit-Observed: 13 +Ratelimit-Limit: 2000 +Set-Cookie: _cfuvid=W6F2uJSFkB3Iyl27_xtklVvP4Z_nSsjPqytClHPW9H8-1701333888913-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Page: 3 +X-Total-Pages: 3 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT +Cache-Control: max-age=0, private, must-revalidate +Vary: Origin, Accept-Encoding +X-Gitlab-Meta: {"correlation_id":"b805751aeb6f562dff684cec34dfdc0b","version":"1"} +X-Per-Page: 2 +X-Prev-Page: 2 +X-Runtime: 0.061943 +Gitlab-Lb: haproxy-main-11-lb-gprd +Gitlab-Sv: localhost +X-Total: 6 +Ratelimit-Remaining: 1987 +Content-Security-Policy: default-src 'none' +X-Next-Page: +Ratelimit-Reset: 1701333948 + +[{"id":3009636,"name":"confused","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:27.248Z","updated_at":"2019-11-28T08:47:27.248Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009640,"name":"hearts","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:33.059Z","updated_at":"2019-11-28T08:47:33.059Z","awardable_id":27687706,"awardable_type":"Issue","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=4&per_page=2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=4&per_page=2 new file mode 100644 index 0000000000..e550e4ad19 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji?page=4&per_page=2 @@ -0,0 +1,31 @@ +Ratelimit-Remaining: 1986 +Accept-Ranges: bytes +Set-Cookie: _cfuvid=DZQIMINjFohqKKjkWnojkq2xuUaqb42YEQg3BZXe68w-1701333889148-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Strict-Transport-Security: max-age=31536000 +X-Prev-Page: +Ratelimit-Reset: 1701333949 +Ratelimit-Limit: 2000 +Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" +Cache-Control: max-age=0, private, must-revalidate +X-Next-Page: +X-Page: 4 +Referrer-Policy: strict-origin-when-cross-origin +Gitlab-Sv: localhost +Content-Type: application/json +X-Content-Type-Options: nosniff +Link: ; rel="first", ; rel="last" +Vary: Origin, Accept-Encoding +Content-Security-Policy: default-src 'none' +X-Runtime: 0.081728 +X-Total: 6 +Content-Length: 2 +X-Frame-Options: SAMEORIGIN +X-Gitlab-Meta: {"correlation_id":"588184d2d9ad2c4a7e7c00a51575091c","version":"1"} +Cf-Cache-Status: MISS +X-Total-Pages: 3 +Ratelimit-Observed: 14 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT +Gitlab-Lb: haproxy-main-34-lb-gprd +X-Per-Page: 2 + +[] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions?page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions?page=1&per_page=100 new file mode 100644 index 0000000000..d74fc325a3 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions?page=1&per_page=100 @@ -0,0 +1,29 @@ +X-Runtime: 0.173849 +Content-Security-Policy: default-src 'none' +X-Per-Page: 100 +X-Prev-Page: +X-Total: 4 +Ratelimit-Observed: 15 +X-Page: 1 +Ratelimit-Remaining: 1985 +Cache-Control: max-age=0, private, must-revalidate +X-Gitlab-Meta: {"correlation_id":"1d2b49b1b17e0c2d58c800a1b6c7eca3","version":"1"} +X-Next-Page: +Referrer-Policy: strict-origin-when-cross-origin +Cf-Cache-Status: MISS +Content-Type: application/json +Etag: W/"bcc91e8a7b2eac98b4d96ae791e0649d" +Vary: Origin, Accept-Encoding +X-Frame-Options: SAMEORIGIN +Strict-Transport-Security: max-age=31536000 +Ratelimit-Reset: 1701333949 +Gitlab-Lb: haproxy-main-08-lb-gprd +Gitlab-Sv: localhost +Link: ; rel="first", ; rel="last" +X-Content-Type-Options: nosniff +X-Total-Pages: 1 +Ratelimit-Limit: 2000 +Set-Cookie: _cfuvid=yj.PGr9ftsz1kNpgtsmQhAcGpdMnklLE.NQ9h71hm5Q-1701333889475-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT + +[{"id":"617967369d98d8b73b6105a40318fe839f931a24","individual_note":true,"notes":[{"id":251637434,"type":null,"body":"This is a comment","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:44:52.501Z","updated_at":"2019-11-28T08:44:52.501Z","system":false,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"b92d74daee411a17d844041bcd3c267ade58f680","individual_note":true,"notes":[{"id":251637528,"type":null,"body":"changed milestone to %2","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:02.329Z","updated_at":"2019-11-28T08:45:02.335Z","system":true,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"6010f567d2b58758ef618070372c97891ac75349","individual_note":true,"notes":[{"id":251637892,"type":null,"body":"closed","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:45.007Z","updated_at":"2019-11-28T08:45:45.010Z","system":true,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"632d0cbfd6a1a08f38aaf9ef7715116f4b188ebb","individual_note":true,"notes":[{"id":251637999,"type":null,"body":"A second comment","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:53.501Z","updated_at":"2019-11-28T08:45:53.501Z","system":false,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels?page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels?page=1&per_page=100 new file mode 100644 index 0000000000..c79889fbf4 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels?page=1&per_page=100 @@ -0,0 +1,29 @@ +Cache-Control: max-age=0, private, must-revalidate +Strict-Transport-Security: max-age=31536000 +Gitlab-Lb: haproxy-main-28-lb-gprd +Link: ; rel="first", ; rel="last" +X-Per-Page: 100 +X-Runtime: 0.149464 +X-Total: 9 +Ratelimit-Observed: 6 +Gitlab-Sv: localhost +X-Frame-Options: SAMEORIGIN +X-Gitlab-Meta: {"correlation_id":"2ccddf20767704a98ed6b582db3b103e","version":"1"} +X-Next-Page: +X-Prev-Page: +X-Total-Pages: 1 +Ratelimit-Remaining: 1994 +Etag: W/"5a3fb9bc7b1018070943f4aa1353f8b6" +Ratelimit-Limit: 2000 +X-Page: 1 +Content-Type: application/json +Vary: Origin, Accept-Encoding +Set-Cookie: _cfuvid=geNpLvH8Cv5XeYfUVwtpaazw43v9lCcqHE.vyXGk3kU-1701333887126-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Content-Type-Options: nosniff +Ratelimit-Reset: 1701333947 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT +Content-Security-Policy: default-src 'none' +Referrer-Policy: strict-origin-when-cross-origin +Cf-Cache-Status: MISS + +[{"id":12959095,"name":"bug","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959097,"name":"confirmed","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959096,"name":"critical","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959100,"name":"discussion","description":null,"description_html":"","text_color":"#FFFFFF","color":"#428bca","subscribed":false,"priority":null,"is_project_label":true},{"id":12959098,"name":"documentation","description":null,"description_html":"","text_color":"#1F1E24","color":"#f0ad4e","subscribed":false,"priority":null,"is_project_label":true},{"id":12959554,"name":"duplicate","description":null,"description_html":"","text_color":"#FFFFFF","color":"#7F8C8D","subscribed":false,"priority":null,"is_project_label":true},{"id":12959102,"name":"enhancement","description":null,"description_html":"","text_color":"#FFFFFF","color":"#5cb85c","subscribed":false,"priority":null,"is_project_label":true},{"id":12959101,"name":"suggestion","description":null,"description_html":"","text_color":"#FFFFFF","color":"#428bca","subscribed":false,"priority":null,"is_project_label":true},{"id":12959099,"name":"support","description":null,"description_html":"","text_color":"#1F1E24","color":"#f0ad4e","subscribed":false,"priority":null,"is_project_label":true}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests?page=1&per_page=1&view=simple b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests?page=1&per_page=1&view=simple new file mode 100644 index 0000000000..7eec1ca917 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests?page=1&per_page=1&view=simple @@ -0,0 +1,29 @@ +Content-Type: application/json +X-Page: 1 +Link: ; rel="next", ; rel="first", ; rel="last" +Vary: Origin, Accept-Encoding +X-Runtime: 0.139912 +X-Gitlab-Meta: {"correlation_id":"002c20b78ace441f5585931fed7093ed","version":"1"} +Gitlab-Sv: localhost +Cache-Control: max-age=0, private, must-revalidate +X-Total: 2 +X-Total-Pages: 2 +Ratelimit-Observed: 16 +Ratelimit-Limit: 2000 +X-Content-Type-Options: nosniff +Ratelimit-Remaining: 1984 +Set-Cookie: _cfuvid=6nsOEFMJm7NgrvYZAMGwiJBdm5A5CU71S33zOdN8Kyo-1701333889768-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Per-Page: 1 +X-Prev-Page: +Strict-Transport-Security: max-age=31536000 +Ratelimit-Reset: 1701333949 +Gitlab-Lb: haproxy-main-09-lb-gprd +X-Next-Page: 2 +Cf-Cache-Status: MISS +Content-Security-Policy: default-src 'none' +Etag: W/"14f72c1f555b0e6348d338190e9e4839" +X-Frame-Options: SAMEORIGIN +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT + +[{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","web_url":"https://gitlab.com/gitea/test_repo/-/merge_requests/2"}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals new file mode 100644 index 0000000000..80df508bc3 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals @@ -0,0 +1,22 @@ +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:51 GMT +Ratelimit-Limit: 2000 +Content-Security-Policy: default-src 'none' +X-Gitlab-Meta: {"correlation_id":"0a1a7339f3527e175a537afe058a6ac7","version":"1"} +Ratelimit-Observed: 21 +Cache-Control: max-age=0, private, must-revalidate +Gitlab-Lb: haproxy-main-38-lb-gprd +X-Frame-Options: SAMEORIGIN +Strict-Transport-Security: max-age=31536000 +Cf-Cache-Status: MISS +Content-Type: application/json +Etag: W/"19aa54b7d4531bd5ab98282e0b772f20" +X-Content-Type-Options: nosniff +Set-Cookie: _cfuvid=J2edW64FLja6v0MZCh5tVbLYO42.VvIsTqQ.uj1Gr_k-1701333891171-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Ratelimit-Reset: 1701333951 +Gitlab-Sv: localhost +Vary: Origin, Accept-Encoding +Ratelimit-Remaining: 1979 +X-Runtime: 0.155712 +Referrer-Policy: strict-origin-when-cross-origin + +{"id":43486906,"iid":1,"project_id":15578026,"title":"Update README.md","description":"add warning to readme","state":"merged","created_at":"2019-11-28T08:54:41.034Z","updated_at":"2019-11-28T16:02:08.377Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":527793,"username":"axifive","name":"Alexey Terentyev","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/06683cd6b2e2c2ce0ab00fb80cc0729f?s=80\u0026d=identicon","web_url":"https://gitlab.com/axifive"}},{"user":{"id":4102996,"username":"zeripath","name":"zeripath","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/1ae18535c2b1aed798da090448997248?s=80\u0026d=identicon","web_url":"https://gitlab.com/zeripath"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2 new file mode 100644 index 0000000000..7119342f6c --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2 @@ -0,0 +1,22 @@ +Etag: W/"914149155d75f8d8f7ed2e5351f0fadb" +X-Runtime: 0.234286 +Ratelimit-Remaining: 1983 +Strict-Transport-Security: max-age=31536000 +Referrer-Policy: strict-origin-when-cross-origin +Gitlab-Sv: localhost +Set-Cookie: _cfuvid=aYLZ68TRL8gsnraUk.zZIxRvuv981nIhZNIO9vVpgbU-1701333890175-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Ratelimit-Observed: 17 +Content-Type: application/json +Cache-Control: max-age=0, private, must-revalidate +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT +Ratelimit-Limit: 2000 +Vary: Origin, Accept-Encoding +Content-Security-Policy: default-src 'none' +Gitlab-Lb: haproxy-main-33-lb-gprd +Cf-Cache-Status: MISS +Ratelimit-Reset: 1701333950 +X-Gitlab-Meta: {"correlation_id":"10d576ab8e82b745b7202a6daec3c5e1","version":"1"} + +{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","merged_by":null,"merge_user":null,"merged_at":null,"closed_by":null,"closed_at":null,"target_branch":"master","source_branch":"feat/test","user_notes_count":0,"upvotes":1,"downvotes":0,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"assignees":[],"assignee":null,"reviewers":[],"source_project_id":15578026,"target_project_id":15578026,"labels":["bug"],"draft":false,"work_in_progress":false,"milestone":{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"},"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","detailed_merge_status":"mergeable","sha":"9f733b96b98a4175276edf6a2e1231489c3bdd23","merge_commit_sha":null,"squash_commit_sha":null,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"prepared_at":"2019-11-28T15:56:54.104Z","reference":"!2","references":{"short":"!2","relative":"!2","full":"gitea/test_repo!2"},"web_url":"https://gitlab.com/gitea/test_repo/-/merge_requests/2","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":true,"squash_on_merge":true,"task_completion_status":{"count":0,"completed_count":0},"has_conflicts":false,"blocking_discussions_resolved":true,"approvals_before_merge":null,"subscribed":false,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"head_pipeline":null,"diff_refs":{"base_sha":"c59c9b451acca9d106cc19d61d87afe3fbbb8b83","head_sha":"9f733b96b98a4175276edf6a2e1231489c3bdd23","start_sha":"c59c9b451acca9d106cc19d61d87afe3fbbb8b83"},"merge_error":null,"first_contribution":false,"user":{"can_merge":false}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals new file mode 100644 index 0000000000..02a1deae29 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals @@ -0,0 +1,22 @@ +Vary: Origin, Accept-Encoding +Ratelimit-Remaining: 1978 +Gitlab-Lb: haproxy-main-04-lb-gprd +Set-Cookie: _cfuvid=cKUtcpJODQwk9SDRn91k1DY8CY5Tg238DXGgT0a2go0-1701333891493-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Content-Type: application/json +Cf-Cache-Status: MISS +Etag: W/"ce2f774c1b05c5c8a14ec9274444cba3" +Ratelimit-Limit: 2000 +X-Content-Type-Options: nosniff +Cache-Control: max-age=0, private, must-revalidate +X-Gitlab-Meta: {"correlation_id":"98ee1da556bdb3d764679ebab9ab5312","version":"1"} +Referrer-Policy: strict-origin-when-cross-origin +X-Frame-Options: SAMEORIGIN +Gitlab-Sv: localhost +Content-Security-Policy: default-src 'none' +X-Runtime: 0.165471 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Observed: 22 +Ratelimit-Reset: 1701333951 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:51 GMT + +{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[{"group":{"id":3181312,"web_url":"https://gitlab.com/groups/gitea","name":"gitea","path":"gitea","description":"Mirror of Gitea source code repositories","visibility":"public","share_with_group_lock":false,"require_two_factor_authentication":false,"two_factor_grace_period":48,"project_creation_level":"maintainer","auto_devops_enabled":null,"subgroup_creation_level":"owner","emails_disabled":false,"emails_enabled":true,"mentions_disabled":null,"lfs_enabled":true,"default_branch_protection":2,"default_branch_protection_defaults":{"allowed_to_push":[{"access_level":30}],"allow_force_push":true,"allowed_to_merge":[{"access_level":30}]},"avatar_url":"https://gitlab.com/uploads/-/system/group/avatar/3181312/gitea.png","request_access_enabled":true,"full_name":"gitea","full_path":"gitea","created_at":"2018-07-04T16:32:10.176Z","parent_id":null,"shared_runners_setting":"enabled","ldap_cn":null,"ldap_access":null,"wiki_access_level":"enabled"}}],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=1&per_page=1 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=1&per_page=1 new file mode 100644 index 0000000000..aab7a74df8 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=1&per_page=1 @@ -0,0 +1,29 @@ +X-Prev-Page: +X-Runtime: 0.066211 +Strict-Transport-Security: max-age=31536000 +Content-Security-Policy: default-src 'none' +X-Per-Page: 1 +X-Total: 2 +Ratelimit-Reset: 1701333950 +Ratelimit-Limit: 2000 +Cf-Cache-Status: MISS +X-Content-Type-Options: nosniff +X-Gitlab-Meta: {"correlation_id":"d0f8c843558e938161d7307b686f1cd9","version":"1"} +Gitlab-Sv: localhost +Set-Cookie: _cfuvid=WFMplweUX3zWl6uoteYRHeDcpElbTNYhWrIBbNEVC3A-1701333890399-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Content-Type: application/json +X-Total-Pages: 2 +Etag: W/"798718b23a2ec66b16cce20cb7155116" +X-Next-Page: 2 +Link: ; rel="next", ; rel="first", ; rel="last" +Vary: Origin, Accept-Encoding +Ratelimit-Observed: 18 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT +X-Frame-Options: SAMEORIGIN +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Remaining: 1982 +Gitlab-Lb: haproxy-main-31-lb-gprd +Cache-Control: max-age=0, private, must-revalidate +X-Page: 1 + +[{"id":5541414,"name":"thumbsup","user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"},"created_at":"2020-09-02T23:42:34.310Z","updated_at":"2020-09-02T23:42:34.310Z","awardable_id":43524600,"awardable_type":"MergeRequest","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=2&per_page=1 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=2&per_page=1 new file mode 100644 index 0000000000..5d85a567e8 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=2&per_page=1 @@ -0,0 +1,29 @@ +Cf-Cache-Status: MISS +Etag: W/"e6776aaa57e6a81bf8a2d8823272cc70" +X-Frame-Options: SAMEORIGIN +X-Gitlab-Meta: {"correlation_id":"db01aae11b0cf7febcf051706159faae","version":"1"} +X-Total-Pages: 2 +Strict-Transport-Security: max-age=31536000 +Gitlab-Lb: haproxy-main-51-lb-gprd +X-Total: 2 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Limit: 2000 +Cache-Control: max-age=0, private, must-revalidate +X-Next-Page: +Set-Cookie: _cfuvid=X0n26HdiufQTXsshb4pvCyuf0jDSstPZ8GnIiyx57YU-1701333890628-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Link: ; rel="prev", ; rel="first", ; rel="last" +Ratelimit-Observed: 19 +Ratelimit-Reset: 1701333950 +Content-Type: application/json +Vary: Origin, Accept-Encoding +X-Per-Page: 1 +X-Runtime: 0.067511 +Gitlab-Sv: localhost +X-Prev-Page: 1 +Ratelimit-Remaining: 1981 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT +Content-Security-Policy: default-src 'none' +X-Content-Type-Options: nosniff +X-Page: 2 + +[{"id":5541415,"name":"tada","user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"},"created_at":"2020-09-02T23:42:59.060Z","updated_at":"2020-09-02T23:42:59.060Z","awardable_id":43524600,"awardable_type":"MergeRequest","url":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=3&per_page=1 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=3&per_page=1 new file mode 100644 index 0000000000..aa5a50e45b --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji?page=3&per_page=1 @@ -0,0 +1,31 @@ +X-Page: 3 +Strict-Transport-Security: max-age=31536000 +Accept-Ranges: bytes +Set-Cookie: _cfuvid=CBSpRhUuajZbJ9Mc_r7SkVmZawoSi5ofuts2TGyHgRk-1701333890842-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Cache-Control: max-age=0, private, must-revalidate +X-Prev-Page: +X-Total-Pages: 2 +Ratelimit-Reset: 1701333950 +Gitlab-Sv: localhost +X-Content-Type-Options: nosniff +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT +Gitlab-Lb: haproxy-main-05-lb-gprd +Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" +Vary: Origin, Accept-Encoding +X-Frame-Options: SAMEORIGIN +X-Gitlab-Meta: {"correlation_id":"36817edd7cae21d5d6b875faf173ce80","version":"1"} +X-Per-Page: 1 +X-Total: 2 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: default-src 'none' +Link: ; rel="first", ; rel="last" +X-Next-Page: +X-Runtime: 0.061075 +Ratelimit-Limit: 2000 +Ratelimit-Observed: 20 +Cf-Cache-Status: MISS +Content-Type: application/json +Content-Length: 2 +Ratelimit-Remaining: 1980 + +[] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones?page=1&per_page=100&state=all b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones?page=1&per_page=100&state=all new file mode 100644 index 0000000000..78310e123d --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones?page=1&per_page=100&state=all @@ -0,0 +1,29 @@ +X-Content-Type-Options: nosniff +X-Next-Page: +Ratelimit-Observed: 5 +X-Frame-Options: SAMEORIGIN +X-Per-Page: 100 +Ratelimit-Limit: 2000 +Cf-Cache-Status: MISS +Strict-Transport-Security: max-age=31536000 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT +Content-Security-Policy: default-src 'none' +Link: ; rel="first", ; rel="last" +X-Gitlab-Meta: {"correlation_id":"4f72373995f8681ce127c62d384745a3","version":"1"} +Gitlab-Sv: localhost +X-Runtime: 0.073691 +Ratelimit-Remaining: 1995 +Ratelimit-Reset: 1701333946 +Content-Type: application/json +Etag: W/"c8e2d3a5f05ee29c58b665c86684f9f9" +X-Page: 1 +Gitlab-Lb: haproxy-main-47-lb-gprd +Vary: Origin, Accept-Encoding +X-Prev-Page: +X-Total: 2 +Cache-Control: max-age=0, private, must-revalidate +X-Total-Pages: 1 +Set-Cookie: _cfuvid=ZfjvK5rh2nTUjEDt1Guwzd8zrl6uCDplfE8NBPbdJ7c-1701333886832-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None + +[{"id":1082927,"iid":2,"project_id":15578026,"title":"1.1.0","description":"","state":"active","created_at":"2019-11-28T08:42:44.575Z","updated_at":"2019-11-28T08:42:44.575Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/2"},{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases?page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases?page=1&per_page=100 new file mode 100644 index 0000000000..9be0b74729 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases?page=1&per_page=100 @@ -0,0 +1,29 @@ +Strict-Transport-Security: max-age=31536000 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Reset: 1701333947 +X-Total: 1 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT +Ratelimit-Limit: 2000 +X-Runtime: 0.178411 +Gitlab-Lb: haproxy-main-15-lb-gprd +Vary: Origin, Accept-Encoding +X-Page: 1 +X-Frame-Options: SAMEORIGIN +Ratelimit-Observed: 7 +Cf-Cache-Status: MISS +Etag: W/"dccc7159dc4b46989d13128a7d6ee859" +X-Content-Type-Options: nosniff +Ratelimit-Remaining: 1993 +X-Gitlab-Meta: {"correlation_id":"0044a3de3ede2f913cabe6e464dd73c2","version":"1"} +X-Total-Pages: 1 +X-Next-Page: +X-Per-Page: 100 +Content-Type: application/json +Content-Security-Policy: default-src 'none' +Set-Cookie: _cfuvid=h1ayMNs6W_kFPoFe28IpiaFUz1ZAPvY6npUWxARRx4I-1701333887452-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Link: ; rel="first", ; rel="last" +Gitlab-Sv: localhost +Cache-Control: max-age=0, private, must-revalidate +X-Prev-Page: + +[{"name":"First Release","tag_name":"v0.9.99","description":"A test release","created_at":"2019-11-28T09:09:48.840Z","released_at":"2019-11-28T09:09:48.836Z","upcoming_release":false,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"commit":{"id":"0720a3ec57c1f843568298117b874319e7deee75","short_id":"0720a3ec","created_at":"2019-11-28T08:49:16.000+00:00","parent_ids":["93ea21ce45d35690c35e80961d239645139e872c"],"title":"Add new file","message":"Add new file","author_name":"Lauris BH","author_email":"lauris@nix.lv","authored_date":"2019-11-28T08:49:16.000+00:00","committer_name":"Lauris BH","committer_email":"lauris@nix.lv","committed_date":"2019-11-28T08:49:16.000+00:00","trailers":{},"extended_trailers":{},"web_url":"https://gitlab.com/gitea/test_repo/-/commit/0720a3ec57c1f843568298117b874319e7deee75"},"commit_path":"/gitea/test_repo/-/commit/0720a3ec57c1f843568298117b874319e7deee75","tag_path":"/gitea/test_repo/-/tags/v0.9.99","assets":{"count":4,"sources":[{"format":"zip","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.zip"},{"format":"tar.gz","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar.gz"},{"format":"tar.bz2","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar.bz2"},{"format":"tar","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar"}],"links":[]},"evidences":[{"sha":"89f1223473ee01f192a83d0cb89f4d1eac1de74f01ad","filepath":"https://gitlab.com/gitea/test_repo/-/releases/v0.9.99/evidences/52147.json","collected_at":"2019-11-28T09:09:48.888Z"}],"_links":{"closed_issues_url":"https://gitlab.com/gitea/test_repo/-/issues?release_tag=v0.9.99\u0026scope=all\u0026state=closed","closed_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=closed","merged_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=merged","opened_issues_url":"https://gitlab.com/gitea/test_repo/-/issues?release_tag=v0.9.99\u0026scope=all\u0026state=opened","opened_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=opened","self":"https://gitlab.com/gitea/test_repo/-/releases/v0.9.99"}}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo new file mode 100644 index 0000000000..07fed52a81 --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo @@ -0,0 +1,22 @@ +Content-Security-Policy: default-src 'none' +Vary: Origin, Accept-Encoding +Referrer-Policy: strict-origin-when-cross-origin +Gitlab-Sv: localhost +X-Content-Type-Options: nosniff +Ratelimit-Reset: 1701333946 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT +X-Runtime: 0.144599 +Content-Type: application/json +X-Gitlab-Meta: {"correlation_id":"919887b868b11ed34c5917e98b4be40d","version":"1"} +Ratelimit-Observed: 2 +Gitlab-Lb: haproxy-main-42-lb-gprd +Strict-Transport-Security: max-age=31536000 +Cf-Cache-Status: MISS +Ratelimit-Limit: 2000 +Set-Cookie: _cfuvid=BENIrMVlxs_tt.JplEkDKbUrMpOF_kjRRLJOifNTLqY-1701333886061-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Etag: W/"3cacfe29f44a69e84a577337eac55d89" +X-Frame-Options: SAMEORIGIN +Cache-Control: max-age=0, private, must-revalidate +Ratelimit-Remaining: 1998 + +{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2022-08-26T19:41:46.691Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_version b/services/migrations/testdata/gitlab/full_download/_api_v4_version new file mode 100644 index 0000000000..7c2cd320ad --- /dev/null +++ b/services/migrations/testdata/gitlab/full_download/_api_v4_version @@ -0,0 +1,22 @@ +Etag: W/"4e5c0a031c3aacb6ba0a3c19e67d7592" +Ratelimit-Observed: 1 +Content-Type: application/json +Ratelimit-Remaining: 1999 +Set-Cookie: _cfuvid=qtYbzv8YeGg4q7XaV0aAE2.YqWIp_xrYPGilXrlecsk-1701333885742-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Gitlab-Meta: {"correlation_id":"c7c75f0406e1b1b9705a6d7e9bdb06a5","version":"1"} +X-Runtime: 0.039264 +Cf-Cache-Status: MISS +Content-Security-Policy: default-src 'none' +Ratelimit-Reset: 1701333945 +Gitlab-Sv: localhost +Strict-Transport-Security: max-age=31536000 +Vary: Origin, Accept-Encoding +X-Content-Type-Options: nosniff +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:45 GMT +Gitlab-Lb: haproxy-main-23-lb-gprd +Cache-Control: max-age=0, private, must-revalidate +X-Frame-Options: SAMEORIGIN +Ratelimit-Limit: 2000 + +{"version":"16.7.0-pre","revision":"acd848a9228","kas":{"enabled":true,"externalUrl":"wss://kas.gitlab.com","version":"v16.7.0-rc2"},"enterprise":true} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996 b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996 new file mode 100644 index 0000000000..db8d596173 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996 @@ -0,0 +1,22 @@ +X-Runtime: 0.088022 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Observed: 3 +Cache-Control: max-age=0, private, must-revalidate +Etag: W/"03ce4f6ce1c1e8c5a31df8a44cf2fbdd" +Gitlab-Lb: haproxy-main-11-lb-gprd +Content-Security-Policy: default-src 'none' +Ratelimit-Limit: 2000 +X-Gitlab-Meta: {"correlation_id":"b57b226f741f9140a1fea54f65cb5cfd","version":"1"} +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Remaining: 1997 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:53 GMT +Set-Cookie: _cfuvid=V0ToiOTUW0XbtWq7BirwVNfL1_YP1POMrLBnDSEWS0M-1701332633965-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Gitlab-Sv: localhost +Content-Type: application/json +Vary: Origin, Accept-Encoding +Ratelimit-Reset: 1701332693 +Cf-Cache-Status: MISS + +{"id":6590996,"description":"Arch packaging and build files","name":"archbuild","name_with_namespace":"Troy Engel / archbuild","path":"archbuild","path_with_namespace":"troyengel/archbuild","created_at":"2018-06-03T22:53:17.388Z","default_branch":"master","tag_list":[],"topics":[],"ssh_url_to_repo":"git@gitlab.com:troyengel/archbuild.git","http_url_to_repo":"https://gitlab.com/troyengel/archbuild.git","web_url":"https://gitlab.com/troyengel/archbuild","readme_url":"https://gitlab.com/troyengel/archbuild/-/blob/master/README.md","forks_count":0,"avatar_url":null,"star_count":0,"last_activity_at":"2020-12-13T18:09:32.071Z","namespace":{"id":1452515,"name":"Troy Engel","path":"troyengel","kind":"user","full_path":"troyengel","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/b226c267929f1bcfcc446e75a025591c?s=80\u0026d=identicon","web_url":"https://gitlab.com/troyengel"},"container_registry_image_prefix":"registry.gitlab.com/troyengel/archbuild","_links":{"self":"https://gitlab.com/api/v4/projects/6590996","issues":"https://gitlab.com/api/v4/projects/6590996/issues","merge_requests":"https://gitlab.com/api/v4/projects/6590996/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/6590996/repository/branches","labels":"https://gitlab.com/api/v4/projects/6590996/labels","events":"https://gitlab.com/api/v4/projects/6590996/events","members":"https://gitlab.com/api/v4/projects/6590996/members","cluster_agents":"https://gitlab.com/api/v4/projects/6590996/cluster_agents"},"packages_enabled":null,"empty_repo":false,"archived":true,"visibility":"public","owner":{"id":1215848,"username":"troyengel","name":"Troy Engel","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/b226c267929f1bcfcc446e75a025591c?s=80\u0026d=identicon","web_url":"https://gitlab.com/troyengel"},"resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":false,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":false,"creator_id":1215848,"import_status":"finished","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:30\" dir=\"auto\"\u003eArch packaging and build files\u003c/p\u003e","updated_at":"2022-07-13T21:32:12.624Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":false,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":null,"printing_merge_request_link_enabled":true,"merge_method":"merge","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues?page=1&per_page=10&sort=asc&state=all b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues?page=1&per_page=10&sort=asc&state=all new file mode 100644 index 0000000000..99133d5f8d --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues?page=1&per_page=10&sort=asc&state=all @@ -0,0 +1,29 @@ +Link: ; rel="first", ; rel="last" +Ratelimit-Observed: 4 +Ratelimit-Remaining: 1996 +Gitlab-Lb: haproxy-main-04-lb-gprd +Vary: Origin, Accept-Encoding +Content-Security-Policy: default-src 'none' +X-Next-Page: +Ratelimit-Reset: 1701332694 +Etag: W/"f50a70d0fc1465a289d231f80806ced7" +X-Gitlab-Meta: {"correlation_id":"47afd74254dd7946d2b2bded87448c60","version":"1"} +X-Page: 1 +X-Prev-Page: +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:54 GMT +Cf-Cache-Status: MISS +X-Total: 1 +X-Total-Pages: 1 +Strict-Transport-Security: max-age=31536000 +Content-Type: application/json +X-Frame-Options: SAMEORIGIN +Ratelimit-Limit: 2000 +Gitlab-Sv: localhost +Set-Cookie: _cfuvid=YDWTZ5VoSuLBDZgKsBnXMyYxz.0rHJ9TBYXv5zBj24Q-1701332634294-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Cache-Control: max-age=0, private, must-revalidate +X-Content-Type-Options: nosniff +X-Per-Page: 10 +X-Runtime: 0.179458 + +[{"id":11201348,"iid":2,"project_id":6590996,"title":"vpn unlimited errors","description":"updated version to 2.8.0, build and tried running `vpnu-arch`:\n\n```\nvpn-unlimited: /usr/lib/libcurl.so.3: no version information available (required by /usr/lib/libvpnu_rpc.so.1)\nvpn-unlimited: /usr/lib/libssl.so.1.0.0: no version information available (required by /usr/lib/libvpnu_enc.so.1)\nvpn-unlimited: symbol lookup error: /usr/lib/libvpnu_rpc.so.1: undefined symbol: _ZNK4Json5Value8asStringEv\n```\n","state":"closed","created_at":"2016-03-26T16:41:12.000Z","updated_at":"2016-03-27T12:19:27.000Z","closed_at":null,"closed_by":null,"labels":[],"milestone":null,"assignees":[],"author":{"id":10273,"username":"brauliobo","name":"Bráulio Bhavamitra","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/cd3fcb7a417c8acb989fc320b604a2a8?s=80\u0026d=identicon","web_url":"https://gitlab.com/brauliobo"},"type":"ISSUE","assignee":null,"user_notes_count":1,"merge_requests_count":0,"upvotes":0,"downvotes":0,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/troyengel/archbuild/-/issues/2","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/6590996/issues/2","notes":"https://gitlab.com/api/v4/projects/6590996/issues/2/notes","award_emoji":"https://gitlab.com/api/v4/projects/6590996/issues/2/award_emoji","project":"https://gitlab.com/api/v4/projects/6590996","closed_as_duplicate_of":null},"references":{"short":"#2","relative":"#2","full":"troyengel/archbuild#2"},"severity":"UNKNOWN","moved_to_id":null,"service_desk_reply_to":null}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues_2_award_emoji?page=1&per_page=10 b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues_2_award_emoji?page=1&per_page=10 new file mode 100644 index 0000000000..8f829d08f0 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues_2_award_emoji?page=1&per_page=10 @@ -0,0 +1,31 @@ +Gitlab-Sv: localhost +X-Content-Type-Options: nosniff +Gitlab-Lb: haproxy-main-25-lb-gprd +X-Total-Pages: 1 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Observed: 5 +Ratelimit-Remaining: 1995 +Content-Security-Policy: default-src 'none' +X-Gitlab-Meta: {"correlation_id":"eeab46d836341bd4cb18e3d2e82abf97","version":"1"} +Ratelimit-Limit: 2000 +Accept-Ranges: bytes +Content-Type: application/json +X-Page: 1 +X-Frame-Options: SAMEORIGIN +X-Prev-Page: +Cf-Cache-Status: MISS +X-Total: 0 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:54 GMT +Link: ; rel="first", ; rel="last" +X-Per-Page: 10 +Set-Cookie: _cfuvid=c5HuTPxOuSXdHSuVrXQALS.uV7WvAYfc5Mc_143EAB8-1701332634513-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Content-Length: 2 +Vary: Origin, Accept-Encoding +Cache-Control: max-age=0, private, must-revalidate +Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" +X-Runtime: 0.069269 +Strict-Transport-Security: max-age=31536000 +Ratelimit-Reset: 1701332694 +X-Next-Page: + +[] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests?page=1&per_page=10&view=simple b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests?page=1&per_page=10&view=simple new file mode 100644 index 0000000000..5339392008 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests?page=1&per_page=10&view=simple @@ -0,0 +1,29 @@ +Vary: Origin, Accept-Encoding +Strict-Transport-Security: max-age=31536000 +Gitlab-Sv: localhost +X-Content-Type-Options: nosniff +X-Prev-Page: +Ratelimit-Reset: 1701332694 +Cache-Control: max-age=0, private, must-revalidate +Ratelimit-Limit: 2000 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Observed: 6 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:54 GMT +Cf-Cache-Status: MISS +Content-Type: application/json +Content-Security-Policy: default-src 'none' +Etag: W/"1a50811aa3cccb2e6a404a976422a83a" +X-Total: 1 +Ratelimit-Remaining: 1994 +Set-Cookie: _cfuvid=u.zumTkG1ayCnh_OwrT9Q1Fl3MXV9Gh98W.ma4WN2Xs-1701332634745-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Link: ; rel="first", ; rel="last" +X-Frame-Options: SAMEORIGIN +X-Page: 1 +X-Total-Pages: 1 +Gitlab-Lb: haproxy-main-05-lb-gprd +X-Gitlab-Meta: {"correlation_id":"907f9e1f94131ea7a6d1405100a8cc4b","version":"1"} +X-Next-Page: +X-Per-Page: 10 +X-Runtime: 0.078413 + +[{"id":10518914,"iid":1,"project_id":6590996,"title":"Review","description":"*Created by: cgtx*\n\n### remove patch from makedepends\n- patch is in base-devel\n- The group base-devel is assumed to be already installed when building with makepkg. Members of \"base-devel\" should not be included in makedepends arrays.\n- https://wiki.archlinux.org/index.php/Pkgbuild#makedepends\n### remove python2 from makedepends\n- python2 is a dependency of python2-setuptools. It is redundant to list it again.\n- You do not need to list packages that your software depends on if other packages your software depends on already have those packages listed in their dependency.\n- https://wiki.archlinux.org/index.php/Pkgbuild#depends\n### more simple find/delete command\n- just because\n","state":"merged","created_at":"2014-12-12T15:01:32.000Z","updated_at":"2014-12-12T15:28:38.000Z","web_url":"https://gitlab.com/troyengel/archbuild/-/merge_requests/1"}] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1 b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1 new file mode 100644 index 0000000000..18e8a8583f --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1 @@ -0,0 +1,22 @@ +Ratelimit-Observed: 7 +Set-Cookie: _cfuvid=_b9GQEo3CBPMs9QmGE89dBdOmbSTfnYjZlzValULQPs-1701332635000-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Strict-Transport-Security: max-age=31536000 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:54 GMT +Gitlab-Lb: haproxy-main-50-lb-gprd +Gitlab-Sv: localhost +X-Gitlab-Meta: {"correlation_id":"da44cd0303a4e62cc52ed8de3b2adf14","version":"1"} +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Remaining: 1993 +Etag: W/"f6299e7e884cb8df8109256c086eb4e7" +X-Runtime: 0.107573 +Content-Type: application/json +Ratelimit-Reset: 1701332694 +X-Frame-Options: SAMEORIGIN +Cache-Control: max-age=0, private, must-revalidate +X-Content-Type-Options: nosniff +Ratelimit-Limit: 2000 +Cf-Cache-Status: MISS +Content-Security-Policy: default-src 'none' +Vary: Origin, Accept-Encoding + +{"id":10518914,"iid":1,"project_id":6590996,"title":"Review","description":"*Created by: cgtx*\n\n### remove patch from makedepends\n- patch is in base-devel\n- The group base-devel is assumed to be already installed when building with makepkg. Members of \"base-devel\" should not be included in makedepends arrays.\n- https://wiki.archlinux.org/index.php/Pkgbuild#makedepends\n### remove python2 from makedepends\n- python2 is a dependency of python2-setuptools. It is redundant to list it again.\n- You do not need to list packages that your software depends on if other packages your software depends on already have those packages listed in their dependency.\n- https://wiki.archlinux.org/index.php/Pkgbuild#depends\n### more simple find/delete command\n- just because\n","state":"merged","created_at":"2014-12-12T15:01:32.000Z","updated_at":"2014-12-12T15:28:38.000Z","merged_by":null,"merge_user":null,"merged_at":null,"closed_by":null,"closed_at":null,"target_branch":"master","source_branch":"cgtx:review","user_notes_count":1,"upvotes":0,"downvotes":0,"author":{"id":1215848,"username":"troyengel","name":"Troy Engel","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/b226c267929f1bcfcc446e75a025591c?s=80\u0026d=identicon","web_url":"https://gitlab.com/troyengel"},"assignees":[],"assignee":null,"reviewers":[],"source_project_id":6590996,"target_project_id":6590996,"labels":[],"draft":false,"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"cannot_be_merged","detailed_merge_status":"not_open","sha":"9006fee398299beed8f5d5086f8e6008ffc02280","merge_commit_sha":null,"squash_commit_sha":null,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":null,"prepared_at":"2014-12-12T15:01:32.000Z","reference":"!1","references":{"short":"!1","relative":"!1","full":"troyengel/archbuild!1"},"web_url":"https://gitlab.com/troyengel/archbuild/-/merge_requests/1","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"squash_on_merge":false,"task_completion_status":{"count":0,"completed_count":0},"has_conflicts":true,"blocking_discussions_resolved":true,"approvals_before_merge":null,"subscribed":false,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"head_pipeline":null,"diff_refs":{"base_sha":"6edcf8fc09f6c44213c892f5108d34a5255a47e1","head_sha":"9006fee398299beed8f5d5086f8e6008ffc02280","start_sha":"6edcf8fc09f6c44213c892f5108d34a5255a47e1"},"merge_error":null,"first_contribution":false,"user":{"can_merge":false}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1_award_emoji?page=1&per_page=10 b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1_award_emoji?page=1&per_page=10 new file mode 100644 index 0000000000..d6f8dd4941 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1_award_emoji?page=1&per_page=10 @@ -0,0 +1,31 @@ +Link: ; rel="first", ; rel="last" +Set-Cookie: _cfuvid=qK29tijoyp0AdVoHf9Lqjc8Y28h4jplJDW9hOFLfq28-1701332635229-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Cache-Control: max-age=0, private, must-revalidate +Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" +Ratelimit-Observed: 8 +Gitlab-Sv: localhost +Content-Length: 2 +Gitlab-Lb: haproxy-main-16-lb-gprd +X-Total: 0 +Ratelimit-Remaining: 1992 +Ratelimit-Reset: 1701332695 +Ratelimit-Limit: 2000 +Vary: Origin, Accept-Encoding +X-Frame-Options: SAMEORIGIN +Content-Type: application/json +X-Content-Type-Options: nosniff +X-Next-Page: +X-Page: 1 +Strict-Transport-Security: max-age=31536000 +Accept-Ranges: bytes +Content-Security-Policy: default-src 'none' +X-Per-Page: 10 +X-Total-Pages: 1 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:55 GMT +Cf-Cache-Status: MISS +X-Gitlab-Meta: {"correlation_id":"eb59d63fed23cdbec69308570cc49c3e","version":"1"} +X-Runtime: 0.065972 +X-Prev-Page: + +[] \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_troyengel%2Farchbuild b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_troyengel%2Farchbuild new file mode 100644 index 0000000000..a8c2882c26 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_troyengel%2Farchbuild @@ -0,0 +1,22 @@ +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:53 GMT +Gitlab-Lb: haproxy-main-41-lb-gprd +Cache-Control: max-age=0, private, must-revalidate +Referrer-Policy: strict-origin-when-cross-origin +Cf-Cache-Status: MISS +X-Content-Type-Options: nosniff +Set-Cookie: _cfuvid=r78xThY2IPR6QvHnea1t_L7DbvuQp4.HWOiG1cKTWUg-1701332633720-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Ratelimit-Limit: 2000 +Strict-Transport-Security: max-age=31536000 +Vary: Origin, Accept-Encoding +X-Gitlab-Meta: {"correlation_id":"4c3e0f8b5858454b6e138ecae9902a8d","version":"1"} +X-Runtime: 0.097047 +Ratelimit-Observed: 2 +Ratelimit-Remaining: 1998 +X-Frame-Options: SAMEORIGIN +Content-Security-Policy: default-src 'none' +Etag: W/"03ce4f6ce1c1e8c5a31df8a44cf2fbdd" +Content-Type: application/json +Gitlab-Sv: localhost +Ratelimit-Reset: 1701332693 + +{"id":6590996,"description":"Arch packaging and build files","name":"archbuild","name_with_namespace":"Troy Engel / archbuild","path":"archbuild","path_with_namespace":"troyengel/archbuild","created_at":"2018-06-03T22:53:17.388Z","default_branch":"master","tag_list":[],"topics":[],"ssh_url_to_repo":"git@gitlab.com:troyengel/archbuild.git","http_url_to_repo":"https://gitlab.com/troyengel/archbuild.git","web_url":"https://gitlab.com/troyengel/archbuild","readme_url":"https://gitlab.com/troyengel/archbuild/-/blob/master/README.md","forks_count":0,"avatar_url":null,"star_count":0,"last_activity_at":"2020-12-13T18:09:32.071Z","namespace":{"id":1452515,"name":"Troy Engel","path":"troyengel","kind":"user","full_path":"troyengel","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/b226c267929f1bcfcc446e75a025591c?s=80\u0026d=identicon","web_url":"https://gitlab.com/troyengel"},"container_registry_image_prefix":"registry.gitlab.com/troyengel/archbuild","_links":{"self":"https://gitlab.com/api/v4/projects/6590996","issues":"https://gitlab.com/api/v4/projects/6590996/issues","merge_requests":"https://gitlab.com/api/v4/projects/6590996/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/6590996/repository/branches","labels":"https://gitlab.com/api/v4/projects/6590996/labels","events":"https://gitlab.com/api/v4/projects/6590996/events","members":"https://gitlab.com/api/v4/projects/6590996/members","cluster_agents":"https://gitlab.com/api/v4/projects/6590996/cluster_agents"},"packages_enabled":null,"empty_repo":false,"archived":true,"visibility":"public","owner":{"id":1215848,"username":"troyengel","name":"Troy Engel","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/b226c267929f1bcfcc446e75a025591c?s=80\u0026d=identicon","web_url":"https://gitlab.com/troyengel"},"resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":false,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":false,"creator_id":1215848,"import_status":"finished","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:30\" dir=\"auto\"\u003eArch packaging and build files\u003c/p\u003e","updated_at":"2022-07-13T21:32:12.624Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":false,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":null,"printing_merge_request_link_enabled":true,"merge_method":"merge","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}} \ No newline at end of file diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_version b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_version new file mode 100644 index 0000000000..eb6df2ffb1 --- /dev/null +++ b/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_version @@ -0,0 +1,22 @@ +Ratelimit-Observed: 1 +X-Gitlab-Meta: {"correlation_id":"aa75720bd9c597c7f2f886a4042d1f80","version":"1"} +Etag: W/"4e5c0a031c3aacb6ba0a3c19e67d7592" +X-Content-Type-Options: nosniff +Ratelimit-Limit: 2000 +Ratelimit-Resettime: Thu, 30 Nov 2023 08:24:53 GMT +X-Runtime: 0.039899 +Ratelimit-Remaining: 1999 +Set-Cookie: _cfuvid=7OAEitQ3J0BOxrXk2pMBApFg1KFnz5aBVqOY7mHwLRk-1701332633452-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None +Content-Security-Policy: default-src 'none' +Gitlab-Sv: localhost +Cf-Cache-Status: MISS +Vary: Origin, Accept-Encoding +X-Frame-Options: SAMEORIGIN +Cache-Control: max-age=0, private, must-revalidate +Strict-Transport-Security: max-age=31536000 +Referrer-Policy: strict-origin-when-cross-origin +Ratelimit-Reset: 1701332693 +Gitlab-Lb: haproxy-main-39-lb-gprd +Content-Type: application/json + +{"version":"16.7.0-pre","revision":"acd848a9228","kas":{"enabled":true,"externalUrl":"wss://kas.gitlab.com","version":"v16.7.0-rc2"},"enterprise":true} \ No newline at end of file diff --git a/services/notify/notifier.go b/services/notify/notifier.go index ed053a812a..3230a5e5f5 100644 --- a/services/notify/notifier.go +++ b/services/notify/notifier.go @@ -59,6 +59,8 @@ type Notifier interface { EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) + NewUserSignUp(ctx context.Context, newUser *user_model.User) + NewRelease(ctx context.Context, rel *repo_model.Release) UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) diff --git a/services/notify/notify.go b/services/notify/notify.go index 16fbb6325d..9cb329d302 100644 --- a/services/notify/notify.go +++ b/services/notify/notify.go @@ -347,6 +347,13 @@ func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, r } } +// NewUserSignUp notifies about a newly signed up user to notifiers +func NewUserSignUp(ctx context.Context, newUser *user_model.User) { + for _, notifier := range notifiers { + notifier.NewUserSignUp(ctx, newUser) + } +} + // PackageCreate notifies creation of a package to notifiers func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { for _, notifier := range notifiers { diff --git a/services/notify/null.go b/services/notify/null.go index dddd421bef..894d118eac 100644 --- a/services/notify/null.go +++ b/services/notify/null.go @@ -197,6 +197,9 @@ func (*NullNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, r func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) { } +func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) { +} + // PackageCreate places a place holder function func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { } diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index 39d60380ff..94acd504ef 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -6,6 +6,8 @@ package pull import ( "context" + "errors" + "fmt" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -15,7 +17,6 @@ import ( "code.gitea.io/gitea/modules/structs" "github.com/gobwas/glob" - "github.com/pkg/errors" ) // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts @@ -95,7 +96,7 @@ func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) { pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) if err != nil { - return false, errors.Wrap(err, "GetLatestCommitStatus") + return false, fmt.Errorf("GetFirstMatchProtectedBranchRule: %w", err) } if pb == nil || !pb.EnableStatusCheck { return true, nil @@ -112,21 +113,21 @@ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) ( func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) { // Ensure HeadRepo is loaded if err := pr.LoadHeadRepo(ctx); err != nil { - return "", errors.Wrap(err, "LoadHeadRepo") + return "", fmt.Errorf("LoadHeadRepo: %w", err) } // check if all required status checks are successful headGitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.HeadRepo.RepoPath()) if err != nil { - return "", errors.Wrap(err, "OpenRepository") + return "", fmt.Errorf("RepositoryFromContextOrOpen: %w", err) } defer closer.Close() if pr.Flow == issues_model.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) { - return "", errors.New("Head branch does not exist, can not merge") + return "", errors.New("head branch does not exist, can not merge") } if pr.Flow == issues_model.PullRequestFlowAGit && !git.IsReferenceExist(ctx, headGitRepo.Path, pr.GetGitRefName()) { - return "", errors.New("Head branch does not exist, can not merge") + return "", errors.New("head branch does not exist, can not merge") } var sha string @@ -140,17 +141,17 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR } if err := pr.LoadBaseRepo(ctx); err != nil { - return "", errors.Wrap(err, "LoadBaseRepo") + return "", fmt.Errorf("LoadBaseRepo: %w", err) } commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true}) if err != nil { - return "", errors.Wrap(err, "GetLatestCommitStatus") + return "", fmt.Errorf("GetLatestCommitStatus: %w", err) } pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) if err != nil { - return "", errors.Wrap(err, "LoadProtectedBranch") + return "", fmt.Errorf("GetFirstMatchProtectedBranchRule: %w", err) } var requiredContexts []string if pb != nil { diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 30d62fbcdf..8a3219bb18 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -219,7 +219,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref } } // Handle links - if entry.IsRegular() || entry.IsLink() { + if entry.IsRegular() || entry.IsLink() || entry.IsExecutable() { downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + url.PathEscape(string(refType)) + "/" + util.PathEscapeSegments(ref) + "/" + util.PathEscapeSegments(treePath)) if err != nil { return nil, err diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 16783f5b5f..852cca0371 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -123,6 +123,8 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m if committer.Name != "" { committerUser.FullName = committer.Name } + // Use the provided email and not revert to placeholder mail. + committerUser.KeepEmailPrivate = false } else { committerUser = &user_model.User{ FullName: committer.Name, @@ -136,6 +138,8 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m if authorUser.Name != "" { authorUser.FullName = author.Name } + // Use the provided email and not revert to placeholder mail. + authorUser.KeepEmailPrivate = false } else { authorUser = &user_model.User{ FullName: author.Name, diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 8f728d3aa6..935981d29c 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -7,6 +7,7 @@ import ( "context" "crypto/hmac" "crypto/sha1" + "crypto/sha256" "crypto/tls" "encoding/hex" "fmt" @@ -29,7 +30,6 @@ import ( webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/gobwas/glob" - "github.com/minio/sha256-simd" ) // Deliver deliver hook task diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 1cc4b7bb09..ce6edf8a97 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -157,6 +157,8 @@
{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}
{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
+
{{ctx.Locale.Tr "admin.config.allow_dots_in_usernames"}}
+
{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{ctx.Locale.Tr "admin.config.enable_timetracking"}}
{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{if .Service.EnableTimetracking}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 876b42d512..18964f374d 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -2,7 +2,8 @@ - {{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} + {{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}} + {{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}} {{if .ManifestData}}{{end}} diff --git a/templates/mail/notify/admin_new_user.tmpl b/templates/mail/notify/admin_new_user.tmpl new file mode 100644 index 0000000000..34d1584f60 --- /dev/null +++ b/templates/mail/notify/admin_new_user.tmpl @@ -0,0 +1,21 @@ + + + + + {{.Subject}} + + + + + + +
    +

    {{.Locale.Tr "mail.admin.new_user.user_info" | Str2html}}: @{{.NewUser.Name}}

    +
  • {{.Locale.Tr "admin.users.created" | Str2html}}: {{DateTime "full" .NewUser.CreatedUnix}}
  • +
+

{{.Body | Str2html}}

+ + diff --git a/templates/package/content/maven.tmpl b/templates/package/content/maven.tmpl index b2cd567e16..100c12e180 100644 --- a/templates/package/content/maven.tmpl +++ b/templates/package/content/maven.tmpl @@ -7,7 +7,7 @@
<repositories>
 	<repository>
 		<id>gitea</id>
-			<url></url>
+		<url></url>
 	</repository>
 </repositories>
 
diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl
index 0797b400d8..9ff826bc93 100644
--- a/templates/repo/clone_script.tmpl
+++ b/templates/repo/clone_script.tmpl
@@ -38,5 +38,8 @@
 		for (const el of document.getElementsByClassName('js-clone-url-vsc')) {
 			el['href'] = 'vscode://vscode.git/clone?url=' + encodeURIComponent(link);
 		}
+		for (const el of document.getElementsByClassName('js-clone-url-vscodium')) {
+			el['href'] = 'vscodium://vscode.git/clone?url=' + encodeURIComponent(link);
+		}
 	})();
 
diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl
index 42004c2610..7b3b27af1d 100644
--- a/templates/repo/commits.tmpl
+++ b/templates/repo/commits.tmpl
@@ -13,6 +13,11 @@
 			
{{template "repo/commits_table" .}} + {{if .OldFilename}} +
+ {{ctx.Locale.Tr "repo.commits.renamed_from" .OldFilename}} ({{ctx.Locale.Tr "repo.commits.browse_further"}}) +
+ {{end}} {{template "base/footer" .}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 1224bbe84c..a5fecce7c1 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -166,6 +166,9 @@ {{ctx.Locale.Tr "repo.diff.view_file"}} {{else}} {{ctx.Locale.Tr "repo.diff.view_file"}} + {{if and $.HeadBranchIsEditable (not $file.IsLFSFile)}} + {{ctx.Locale.Tr "repo.editor.edit_this_file"}} + {{end}} {{end}} {{end}} {{if $isReviewFile}} diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index 34dde576a1..6fd240da62 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -66,6 +66,14 @@ {{end}} +
+ + +
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl index 3cc853b351..9c11dfc121 100644 --- a/templates/repo/issue/card.tmpl +++ b/templates/repo/issue/card.tmpl @@ -33,7 +33,7 @@ {{if .MilestoneID}}
- + {{svg "octicon-milestone" 16 "gt-mr-2 gt-vm"}} {{.Milestone.Name}} @@ -42,7 +42,7 @@ {{if $.Page.LinkedPRs}} {{range index $.Page.LinkedPRs .ID}}
- + {{svg "octicon-git-merge" 16 "gt-mr-2 gt-vm"}} {{.Title}} #{{.Index}} @@ -61,7 +61,7 @@ {{if or .Labels .Assignees}}
{{range .Labels}} - {{RenderLabel ctx .}} + {{RenderLabel ctx .}} {{end}}
{{range .Assignees}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 3bbf4b0d52..5c2da5ebbd 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -318,6 +318,16 @@
+ {{if (not .Repository.IsPrivate)}} +
+
+
+ + +
+
+
+ {{end}}
@@ -976,20 +986,23 @@ {{end}}
-

+

{{if .Repository.IsArchived}} {{ctx.Locale.Tr "repo.settings.unarchive.text"}} {{else}} {{ctx.Locale.Tr "repo.settings.archive.text"}} {{end}} -

-
-
- {{.CsrfTokenHtml}} - - - {{template "base/modal_actions_confirm" .}} +
+ + {{.CsrfTokenHtml}} + + +
+ + +
+
{{end}} {{end}} diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl index ff31df0c32..0d0e78c7ad 100644 --- a/templates/repo/wiki/new.tmpl +++ b/templates/repo/wiki/new.tmpl @@ -39,6 +39,7 @@ + {{ctx.Locale.Tr "repo.wiki.cancel"}}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 6aa077b9f0..07e05caa80 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -8,8 +8,8 @@ "text/html" ], "schemes": [ - "http", - "https" + "https", + "http" ], "swagger": "2.0", "info": { @@ -24449,7 +24449,6 @@ }, "securityDefinitions": { "AccessToken": { - "description": "This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.", "type": "apiKey", "name": "access_token", "in": "query" @@ -24482,7 +24481,6 @@ "in": "header" }, "Token": { - "description": "This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.", "type": "apiKey", "name": "token", "in": "query" diff --git a/templates/user/auth/activate.tmpl b/templates/user/auth/activate.tmpl index 1b06719753..d8a737e996 100644 --- a/templates/user/auth/activate.tmpl +++ b/templates/user/auth/activate.tmpl @@ -39,6 +39,16 @@ {{else}}

{{ctx.Locale.Tr "auth.has_unconfirmed_mail" (.SignedUser.Name|Escape) (.SignedUser.Email|Escape) | Str2html}}

+
+ {{ctx.Locale.Tr "auth.change_unconfirmed_email_summary"}} + +

{{ctx.Locale.Tr "auth.change_unconfirmed_email"}}

+
+ + +
+
+
diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl index dd8b8a4e59..aaffe8d4d5 100644 --- a/templates/user/settings/security/webauthn.tmpl +++ b/templates/user/settings/security/webauthn.tmpl @@ -11,7 +11,7 @@
{{.Name}}
- {{TimeSinceUnix .CreatedUnix ctx.Locale}} + {{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix) | Safe}}