Changes
450 changed files (+12566/-1637)
-
-
@@ -25,7 +25,7 @@ commands:- make deps-frontend - name: deps-backend image: golang:1.19 image: golang:1.20 pull: always commands: - make deps-backend
-
@@ -88,7 +88,7 @@ - make checks-frontenddepends_on: [deps-frontend] - name: checks-backend image: golang:1.19 image: golang:1.20 commands: - make --always-make checks-backend # ensure the 'go-licenses' make target runs depends_on: [deps-backend]
-
@@ -109,7 +109,7 @@ - make frontenddepends_on: [deps-frontend] - name: build-backend-no-gcc image: golang:1.18 # this step is kept as the lowest version of golang that we support image: golang:1.19 # this step is kept as the lowest version of golang that we support pull: always environment: GO111MODULE: on
-
@@ -122,7 +122,7 @@ - name: depspath: /go - name: build-backend-arm64 image: golang:1.19 image: golang:1.20 environment: GO111MODULE: on GOPROXY: https://goproxy.io
-
@@ -138,7 +138,7 @@ - name: depspath: /go - name: build-backend-windows image: golang:1.19 image: golang:1.20 environment: GO111MODULE: on GOPROXY: https://goproxy.io
-
@@ -153,7 +153,7 @@ - name: depspath: /go - name: build-backend-386 image: golang:1.19 image: golang:1.20 environment: GO111MODULE: on GOPROXY: https://goproxy.io
-
@@ -247,7 +247,7 @@ exclude:- pull_request - name: deps-backend image: golang:1.19 image: golang:1.20 pull: always commands: - make deps-backend
-
@@ -364,7 +364,7 @@ - name: depspath: /go - name: generate-coverage image: golang:1.19 image: golang:1.20 commands: - make coverage environment:
-
@@ -440,7 +440,7 @@ exclude:- pull_request - name: deps-backend image: golang:1.19 image: golang:1.20 pull: always commands: - make deps-backend
-
@@ -557,7 +557,7 @@ # TODO: We should probably build all dependencies into a test image- name: test-e2e image: mcr.microsoft.com/playwright:v1.29.2-focal commands: - curl -sLO https://go.dev/dl/go1.19.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz - curl -sLO https://go.dev/dl/go1.20.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.20.linux-amd64.tar.gz - groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea - apt-get -qq update && apt-get -qqy install build-essential - export TEST_PGSQL_SCHEMA=''
-
@@ -656,7 +656,7 @@ - update_gitignore_and_licensessteps: - name: download image: golang:1.19 image: golang:1.20 pull: always commands: - timeout -s ABRT 40m make generate-license generate-gitignore
-
@@ -720,7 +720,7 @@ commands:- make deps-frontend - name: deps-backend image: golang:1.19 image: golang:1.20 pull: always commands: - make deps-backend
-
@@ -729,7 +729,7 @@ - name: depspath: /go - name: static image: techknowlogick/xgo:go-1.19.x image: techknowlogick/xgo:go-1.20.x pull: always commands: # Upgrade to node 18 once https://github.com/techknowlogick/xgo/issues/163 is resolved
-
@@ -841,7 +841,7 @@ commands:- make deps-frontend - name: deps-backend image: golang:1.19 image: golang:1.20 pull: always commands: - make deps-backend
-
@@ -850,7 +850,7 @@ - name: depspath: /go - name: static image: techknowlogick/xgo:go-1.19.x image: techknowlogick/xgo:go-1.20.x pull: always commands: # Upgrade to node 18 once https://github.com/techknowlogick/xgo/issues/163 is resolved
-
@@ -932,7 +932,7 @@ - pull_requeststeps: - name: build-docs image: golang:1.19 image: golang:1.20 commands: - cd docs - make trans-copy clean build
-
-
-
@@ -28,7 +28,7 @@ disable-all: truefast: false run: go: 1.19 go: 1.20 timeout: 10m skip-dirs: - node_modules
-
@@ -74,7 +74,7 @@ - name: duplicated-imports- name: modifies-value-receiver gofumpt: extra-rules: true lang-version: "1.19" lang-version: "1.20" depguard: list-type: denylist # Check the list against standard lib.
-
@@ -84,6 +84,7 @@ - encoding/json: "use gitea's modules/json instead of encoding/json"- github.com/unknwon/com: "use gitea's util and replacements" - io/ioutil: "use os or io instead" - golang.org/x/exp: "it's experimental and unreliable." - code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead" issues: max-issues-per-linter: 0
-
-
-
@@ -267,26 +267,10 @@ with the rest of the summary matching the original PR. Similarly for frontports--- The below is a script that may be helpful in creating backports. YMMV. A command to help create backports can be found in `contrib/backport` and can be installed (from inside the gitea repo root directory) using: ```bash #!/bin/sh PR="$1" SHA="$2" VERSION="$3" if [ -z "$SHA" ]; then SHA=$(gh api /repos/go-gitea/gitea/pulls/$PR -q '.merge_commit_sha') fi if [ -z "$VERSION" ]; then VERSION="v1.16" fi echo git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION git cherry-pick $SHA && git commit --amend && git push zeripath backport-$PR-$VERSION && xdg-open https://github.com/go-gitea/gitea/compare/release/"$VERSION"...zeripath:backport-$PR-$VERSION go install contrib/backport/backport.go ``` ## Developer Certificate of Origin (DCO)
-
-
-
@@ -1,5 +1,5 @@#Build stage FROM golang:1.19-alpine3.17 AS build-env FROM golang:1.20-alpine3.17 AS build-env ARG GOPROXY ENV GOPROXY ${GOPROXY:-direct}
-
-
-
@@ -1,5 +1,5 @@#Build stage FROM golang:1.19-alpine3.17 AS build-env FROM golang:1.20-alpine3.17 AS build-env ARG GOPROXY ENV GOPROXY ${GOPROXY:-direct}
-
-
-
@@ -23,13 +23,13 @@ SHASUM ?= shasum -a 256HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) COMMA := , XGO_VERSION := go-1.19.x XGO_VERSION := go-1.20.x AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.6.0 ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.2 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.3
-
-
-
@@ -5,6 +5,11 @@ "path": "cloud.google.com/go/compute/metadata/LICENSE","licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "code.gitea.io/actions-proto-go", "path": "code.gitea.io/actions-proto-go/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2022 The Gitea 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": "code.gitea.io/gitea/modules/lfs", "path": "code.gitea.io/gitea/modules/lfs/LICENSE", "licenseText": "Copyright (c) 2016 The Gitea Authors\nCopyright (c) GitHub, Inc. and LFS Test Server contributors\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"
-
@@ -68,6 +73,11 @@ {"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", "licenseText": "Copyright (c) 2009 The 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" }, { "name": "github.com/RoaringBitmap/roaring",
-
@@ -200,6 +210,11 @@ "path": "github.com/bradfitz/gomemcache/memcache/LICENSE","licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/bufbuild/connect-go", "path": "github.com/bufbuild/connect-go/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 2021-2022 Buf Technologies, Inc.\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/buildkite/terminal-to-html/v3", "path": "github.com/buildkite/terminal-to-html/v3/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2019 Keith Pitt, Tim Lucas, Michael Pearson\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"
-
@@ -225,6 +240,11 @@ "path": "github.com/chi-middleware/proxy/LICENSE","licenseText": "Copyright (c) 2020 Lauris BH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, { "name": "github.com/cloudflare/circl", "path": "github.com/cloudflare/circl/LICENSE", "licenseText": "Copyright (c) 2019 Cloudflare. 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 Cloudflare 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\nCopyright (c) 2009 The 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" }, { "name": "github.com/couchbase/go-couchbase", "path": "github.com/couchbase/go-couchbase/LICENSE", "licenseText": "Copyright (c) 2013 Couchbase, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, 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"
-
@@ -303,6 +323,11 @@ {"name": "github.com/emersion/go-sasl", "path": "github.com/emersion/go-sasl/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 emersion\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/emirpasic/gods", "path": "github.com/emirpasic/gods/LICENSE", "licenseText": "Copyright (c) 2015, Emir Pasic\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, this\n list of conditions and the following disclaimer.\n\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\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 HOLDER 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\n-------------------------------------------------------------------------------\n\nAVL Tree:\n\nCopyright (c) 2017 Benjamin Scher Purcell \u003cbenjapurcell@gmail.com\u003e\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" }, { "name": "github.com/ethantkoenig/rupture",
-
@@ -310,6 +335,11 @@ "path": "github.com/ethantkoenig/rupture/LICENSE","licenseText": "MIT License\n\nCopyright (c) 2018 Ethan Koenig\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/fatih/color", "path": "github.com/fatih/color/LICENSE.md", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2013 Fatih Arslan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { "name": "github.com/felixge/fgprof", "path": "github.com/felixge/fgprof/LICENSE.txt", "licenseText": "The MIT License (MIT)\nCopyright © 2020 Felix Geisendörfer \u003cfelix@felixge.de\u003e\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"
-
@@ -370,6 +400,26 @@ "path": "github.com/go-fed/httpsig/LICENSE","licenseText": "BSD 3-Clause License\n\nCopyright (c) 2018, go-fed\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, this\n list of conditions and the following disclaimer.\n\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\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software 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 HOLDER 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/go-git/gcfg", "path": "github.com/go-git/gcfg/LICENSE", "licenseText": "Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go\nAuthors. 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" }, { "name": "github.com/go-git/go-billy/v5", "path": "github.com/go-git/go-billy/v5/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 2017 Sourced Technologies S.L.\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/go-git/go-git/v5", "path": "github.com/go-git/go-git/v5/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 2018 Sourced Technologies, S.L.\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/go-ini/ini", "path": "github.com/go-ini/ini/LICENSE", "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright 2014 Unknwon\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/go-ldap/ldap/v3", "path": "github.com/go-ldap/ldap/v3/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)\nPortions copyright (c) 2015-2016 go-ldap Authors\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"
-
@@ -540,9 +590,19 @@ "path": "github.com/huandu/xstrings/LICENSE","licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Huan Du\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" }, { "name": "github.com/imdario/mergo", "path": "github.com/imdario/mergo/LICENSE", "licenseText": "Copyright (c) 2013 Dario Castañé. All rights reserved.\nCopyright (c) 2012 The 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" }, { "name": "github.com/jaytaylor/html2text", "path": "github.com/jaytaylor/html2text/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Jay Taylor\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" }, { "name": "github.com/jbenet/go-context/io", "path": "github.com/jbenet/go-context/io/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Juan Batiz-Benet\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, { "name": "github.com/jhillyerd/enmime",
-
@@ -563,6 +623,11 @@ {"name": "github.com/kballard/go-shellquote", "path": "github.com/kballard/go-shellquote/LICENSE", "licenseText": "Copyright (C) 2014 Kevin Ballard\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\nOR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { "name": "github.com/kevinburke/ssh_config", "path": "github.com/kevinburke/ssh_config/LICENSE", "licenseText": "Copyright (c) 2017 Kevin Burke.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n===================\n\nThe lexer and parser borrow heavily from github.com/pelletier/go-toml. The\nlicense for that project is copied below.\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/keybase/go-crypto",
-
@@ -625,6 +690,11 @@ "path": "github.com/markbates/goth/LICENSE.txt","licenseText": "Copyright (c) 2014 Mark Bates\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { "name": "github.com/mattn/go-colorable", "path": "github.com/mattn/go-colorable/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Yasuhiro Matsumoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/mattn/go-isatty", "path": "github.com/mattn/go-isatty/LICENSE", "licenseText": "Copyright (c) Yasuhiro MATSUMOTO \u003cmattn.jp@gmail.com\u003e\n\nMIT License (Expat)\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"
-
@@ -690,6 +760,11 @@ "path": "github.com/modern-go/reflect2/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 [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/nektos/act/pkg", "path": "github.com/nektos/act/pkg/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2022 The Gitea Authors\nCopyright (c) 2019\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/nfnt/resize", "path": "github.com/nfnt/resize/LICENSE", "licenseText": "Copyright (c) 2012, Jan Schlicht \u003cjan.schlicht@gmail.com\u003e\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n"
-
@@ -765,11 +840,21 @@ "path": "github.com/prometheus/procfs/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 [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/rhysd/actionlint", "path": "github.com/rhysd/actionlint/LICENSE.txt", "licenseText": "the MIT License\n\nCopyright (c) 2021 rhysd\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 copies\nof the Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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 IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR\nTHE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n" }, { "name": "github.com/rivo/uniseg", "path": "github.com/rivo/uniseg/LICENSE.txt", "licenseText": "MIT License\n\nCopyright (c) 2019 Oliver Kuederle\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/robfig/cron", "path": "github.com/robfig/cron/LICENSE", "licenseText": "Copyright (C) 2012 Rob Figueiredo\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { "name": "github.com/rs/xid", "path": "github.com/rs/xid/LICENSE", "licenseText": "Copyright (c) 2015 Olivier Poitrey \u003crs@dailymotion.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto 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\nTHE SOFTWARE.\n"
-
@@ -788,6 +873,16 @@ {"name": "github.com/sergi/go-diff/diffmatchpatch", "path": "github.com/sergi/go-diff/diffmatchpatch/LICENSE", "licenseText": "Copyright (c) 2012-2016 The go-diff Authors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, 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\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n\n" }, { "name": "github.com/sirupsen/logrus", "path": "github.com/sirupsen/logrus/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Simon Eskildsen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, { "name": "github.com/skeema/knownhosts", "path": "github.com/skeema/knownhosts/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 {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/ssor/bom",
-
@@ -845,6 +940,11 @@ "path": "github.com/xanzy/go-gitlab/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 {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/xanzy/ssh-agent", "path": "github.com/xanzy/ssh-agent/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 {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" }, { "name": "github.com/yohcop/openid-go", "path": "github.com/yohcop/openid-go/LICENSE", "licenseText": "Copyright 2015 Yohann Coppel\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
-
@@ -868,16 +968,6 @@ {"name": "go.etcd.io/bbolt", "path": "go.etcd.io/bbolt/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2013 Ben Johnson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { "name": "go.jolheiser.com/hcaptcha", "path": "go.jolheiser.com/hcaptcha/LICENSE", "licenseText": "Copyright 2020 John Olheiser\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." }, { "name": "go.jolheiser.com/pwn", "path": "go.jolheiser.com/pwn/LICENSE", "licenseText": "Copyright 2020 John Olheiser\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." }, { "name": "go.uber.org/atomic",
-
@@ -915,6 +1005,11 @@ "path": "golang.org/x/oauth2/LICENSE","licenseText": "Copyright (c) 2009 The 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" }, { "name": "golang.org/x/sync", "path": "golang.org/x/sync/LICENSE", "licenseText": "Copyright (c) 2009 The 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" }, { "name": "golang.org/x/sys", "path": "golang.org/x/sys/LICENSE", "licenseText": "Copyright (c) 2009 The 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"
-
@@ -930,6 +1025,16 @@ "path": "golang.org/x/time/rate/LICENSE","licenseText": "Copyright (c) 2009 The 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" }, { "name": "google.golang.org/genproto/googleapis/rpc/status", "path": "google.golang.org/genproto/googleapis/rpc/status/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "google.golang.org/grpc", "path": "google.golang.org/grpc/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "google.golang.org/protobuf", "path": "google.golang.org/protobuf/LICENSE", "licenseText": "Copyright (c) 2018 The 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"
-
@@ -943,6 +1048,11 @@ {"name": "gopkg.in/ini.v1", "path": "gopkg.in/ini.v1/LICENSE", "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright 2014 Unknwon\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": "gopkg.in/warnings.v0", "path": "gopkg.in/warnings.v0/LICENSE", "licenseText": "Copyright (c) 2016 Péter Surányi.\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\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" }, { "name": "gopkg.in/yaml.v2",
-
-
-
@@ -9,6 +9,7 @@ import ("encoding/json" "io/fs" "os" goPath "path" "path/filepath" "regexp" "sort"
-
@@ -47,13 +48,15 @@ sort.Strings(paths)entries := []LicenseEntry{} for _, path := range paths { path := filepath.ToSlash(path) licenseText, err := os.ReadFile(path) if err != nil { panic(err) } path := strings.Replace(path, base+string(os.PathSeparator), "", 1) name := filepath.Dir(path) path = strings.Replace(path, base+"/", "", 1) name := goPath.Dir(path) // There might be a bug somewhere in go-licenses that sometimes interprets the // root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
-
-
-
@@ -180,6 +180,11 @@ cli.BoolFlag{Name: "raw", Usage: "Display only the token value", }, cli.StringFlag{ Name: "scopes", Value: "", Usage: "Comma separated list of scopes to apply to access token", }, }, Action: runGenerateAccessToken, }
-
@@ -698,9 +703,15 @@ if err != nil {return err } accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize() if err != nil { return err } t := &auth_model.AccessToken{ Name: c.String("token-name"), UID: user.ID, Name: c.String("token-name"), UID: user.ID, Scope: accessTokenScope, } if err := auth_model.NewAccessToken(t); err != nil {
-
-
-
@@ -185,6 +185,7 @@ reponame := os.Getenv(repo_module.EnvRepoName)userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64) actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64) hookOptions := private.HookOptions{ UserID: userID,
-
@@ -194,6 +195,7 @@ GitQuarantinePath: os.Getenv(private.GitQuarantinePath),GitPushOptions: pushOptions(), PullRequestID: prID, DeployKeyID: deployKeyID, ActionPerm: int(actionPerm), } scanner := bufio.NewScanner(os.Stdin)
-
-
contrib/backport/README (new)
-
@@ -0,0 +1,41 @@`backport` ========== `backport` is a command to help create backports of PRs. It backports a provided PR from main on to a released version. It will create a backport branch, cherry-pick the PR's merge commit, adjust the commit message and then push this back up to your fork's remote. The default version will read from `docs/config.yml`. You can override this using the option `--version`. The upstream branches will be fetched, using the remote `origin`. This can be overrided using `--upstream`, and fetching can be avoided using `--no-fetch`. By default the branch created will be called `backport-$PR-$VERSION`. You can override this using the option `--backport-branch`. This branch will be created from `--release-branch` which is `release/$(VERSION)` by default and will be pulled from `$(UPSTREAM)`. The merge-commit as determined by the github API will be used as the SHA to cherry-pick. You can override this using `--cherry-pick`. The commit message will be amended to add the `Backport` header. `--no-amend-message` can be set to stop this from happening. If cherry-pick is successful the backported branch will be pushed up to your fork using your remote. These will be determined using `git remote -v`. You can set your fork name using `--fork-user` and your remote name using `--remote`. You can avoid pushing using `--no-push`. If the push is successful, `xdg-open` will be called to open a backport url. You can stop this using `--no-xdg-open`. Installation ============ ```bash go install contrib/backport/backport.go ```
-
-
-
@@ -0,0 +1,438 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package main import ( "context" "fmt" "log" "net/http" "os" "os/exec" "os/signal" "path" "strconv" "strings" "syscall" "github.com/google/go-github/v45/github" "github.com/urfave/cli" "gopkg.in/yaml.v3" ) const defaultVersion = "v1.18" // to backport to func main() { app := cli.NewApp() app.Name = "backport" app.Usage = "Backport provided PR-number on to the current or previous released version" app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version` app.ArgsUsage = "<PR-to-backport>" app.Flags = []cli.Flag{ cli.StringFlag{ Name: "version", Usage: "Version branch to backport on to", }, cli.StringFlag{ Name: "upstream", Value: "origin", Usage: "Upstream remote for the Gitea upstream", }, cli.StringFlag{ Name: "release-branch", Value: "", Usage: "Release branch to backport on. Will default to release/<version>", }, cli.StringFlag{ Name: "cherry-pick", Usage: "SHA to cherry-pick as backport", }, cli.StringFlag{ Name: "backport-branch", Usage: "Backport branch to backport on to (default: backport-<pr>-<version>", }, cli.StringFlag{ Name: "remote", Value: "", Usage: "Remote for your fork of the Gitea upstream", }, cli.StringFlag{ Name: "fork-user", Value: "", Usage: "Forked user name on Github", }, cli.BoolFlag{ Name: "no-fetch", Usage: "Set this flag to prevent fetch of remote branches", }, cli.BoolFlag{ Name: "no-amend-message", Usage: "Set this flag to prevent automatic amendment of the commit message", }, cli.BoolFlag{ Name: "no-push", Usage: "Set this flag to prevent pushing the backport up to your fork", }, cli.BoolFlag{ Name: "no-xdg-open", Usage: "Set this flag to not use xdg-open to open the PR URL", }, } cli.AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if len .Authors}} AUTHOR: {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` app.Action = runBackport if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) } } func runBackport(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() version := c.String("version") if version == "" { version = readVersion() } if version == "" { version = defaultVersion } upstream := c.String("upstream") if upstream == "" { upstream = "origin" } forkUser := c.String("fork-user") remote := c.String("remote") if remote == "" && !c.Bool("--no-push") { var err error remote, forkUser, err = determineRemote(ctx, forkUser) if err != nil { return err } } upstreamReleaseBranch := c.String("release-branch") if upstreamReleaseBranch == "" { upstreamReleaseBranch = path.Join("release", version) } localReleaseBranch := path.Join(upstream, upstreamReleaseBranch) args := c.Args() if len(args) == 0 { return fmt.Errorf("no PR number provided\nProvide a PR number to backport") } else if len(args) != 1 { return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args) } pr := args[0] backportBranch := c.String("backport-branch") if backportBranch == "" { backportBranch = "backport-" + pr + "-" + version } fmt.Printf("* Backporting %s to %s as %s\n", pr, localReleaseBranch, backportBranch) sha := c.String("cherry-pick") if sha == "" { var err error sha, err = determineSHAforPR(ctx, pr) if err != nil { return err } } if sha == "" { return fmt.Errorf("unable to determine sha for cherry-pick of %s", pr) } if !c.Bool("no-fetch") { if err := fetchRemoteAndMain(ctx, upstream, upstreamReleaseBranch); err != nil { return err } } if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil { return err } if err := cherrypick(ctx, sha); err != nil { return err } if !c.Bool("no-amend-message") { if err := amendCommit(ctx, pr); err != nil { return err } } if !c.Bool("no-push") { url := "https://github.com/go-gitea/gitea/compare/" + upstreamReleaseBranch + "..." + forkUser + ":" + backportBranch if err := gitPushUp(ctx, remote, backportBranch); err != nil { return err } if !c.Bool("no-xdg-open") { if err := xdgOpen(ctx, url); err != nil { return err } } else { fmt.Printf("* Navigate to %s to open PR\n", url) } } return nil } func xdgOpen(ctx context.Context, url string) error { fmt.Printf("* `xdg-open %s`\n", url) out, err := exec.CommandContext(ctx, "xdg-open", url).Output() if err != nil { fmt.Fprintf(os.Stderr, "%s", string(out)) return fmt.Errorf("unable to xdg-open to %s: %w", url, err) } return nil } func gitPushUp(ctx context.Context, remote, backportBranch string) error { fmt.Printf("* `git push -u %s %s`\n", remote, backportBranch) out, err := exec.CommandContext(ctx, "git", "push", "-u", remote, backportBranch).Output() if err != nil { fmt.Fprintf(os.Stderr, "%s", string(out)) return fmt.Errorf("unable to push up to %s: %w", remote, err) } return nil } func amendCommit(ctx context.Context, pr string) error { fmt.Printf("* Amending commit to prepend `Backport #%s` to body\n", pr) out, err := exec.CommandContext(ctx, "git", "log", "-1", "--pretty=format:%B").Output() if err != nil { fmt.Fprintf(os.Stderr, "%s", string(out)) return fmt.Errorf("unable to get last log message: %w", err) } parts := strings.SplitN(string(out), "\n", 2) if len(parts) != 2 { return fmt.Errorf("unable to interpret log message:\n%s", string(out)) } subject, body := parts[0], parts[1] if !strings.HasSuffix(subject, " (#"+pr+")") { subject = subject + " (#" + pr + ")" } out, err = exec.CommandContext(ctx, "git", "commit", "--amend", "-m", subject+"\n\nBackport #"+pr+"\n"+body).Output() if err != nil { fmt.Fprintf(os.Stderr, "%s", string(out)) return fmt.Errorf("unable to amend last log message: %w", err) } return nil } func cherrypick(ctx context.Context, sha string) error { // Check if a CHERRY_PICK_HEAD exists if _, err := os.Stat(".git/CHERRY_PICK_HEAD"); err == nil { // Assume that we are in the middle of cherry-pick - continue it fmt.Println("* Attempting git cherry-pick --continue") out, err := exec.CommandContext(ctx, "git", "cherry-pick", "--continue").Output() if err != nil { fmt.Fprintf(os.Stderr, "git cherry-pick --continue failed:\n%s\n", string(out)) return fmt.Errorf("unable to continue cherry-pick: %w", err) } return nil } fmt.Printf("* Attempting git cherry-pick %s\n", sha) out, err := exec.CommandContext(ctx, "git", "cherry-pick", sha).Output() if err != nil { fmt.Fprintf(os.Stderr, "git cherry-pick %s failed:\n%s\n", sha, string(out)) return fmt.Errorf("git cherry-pick %s failed: %w", sha, err) } return nil } func checkoutBackportBranch(ctx context.Context, backportBranch, releaseBranch string) error { out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output() if err != nil { return fmt.Errorf("unable to check current branch %w", err) } currentBranch := strings.TrimSpace(string(out)) fmt.Printf("* Current branch is %s\n", currentBranch) if currentBranch == backportBranch { fmt.Printf("* Current branch is %s - not checking out\n", currentBranch) return nil } if _, err := exec.CommandContext(ctx, "git", "rev-list", "-1", backportBranch).Output(); err == nil { fmt.Printf("* Branch %s already exists. Checking it out...\n", backportBranch) return exec.CommandContext(ctx, "git", "checkout", "-f", backportBranch).Run() } fmt.Printf("* `git checkout -b %s %s`\n", backportBranch, releaseBranch) return exec.CommandContext(ctx, "git", "checkout", "-b", backportBranch, releaseBranch).Run() } func fetchRemoteAndMain(ctx context.Context, remote, releaseBranch string) error { fmt.Printf("* `git fetch %s main`\n", remote) out, err := exec.CommandContext(ctx, "git", "fetch", remote, "main").Output() if err != nil { fmt.Println(string(out)) return fmt.Errorf("unable to fetch %s from %s: %w", "main", remote, err) } fmt.Println(string(out)) fmt.Printf("* `git fetch %s %s`\n", remote, releaseBranch) out, err = exec.CommandContext(ctx, "git", "fetch", remote, releaseBranch).Output() if err != nil { fmt.Println(string(out)) return fmt.Errorf("unable to fetch %s from %s: %w", releaseBranch, remote, err) } fmt.Println(string(out)) return nil } func determineRemote(ctx context.Context, forkUser string) (string, string, error) { out, err := exec.CommandContext(ctx, "git", "remote", "-v").Output() if err != nil { fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out)) return "", "", fmt.Errorf("unable to determine forked remote: %w", err) } lines := strings.Split(string(out), "\n") for _, line := range lines { fields := strings.Split(line, "\t") name, remote := fields[0], fields[1] // only look at pushers if !strings.HasSuffix(remote, " (push)") { continue } // only look at github.com pushes if !strings.Contains(remote, "github.com") { continue } // ignore go-gitea/gitea if strings.Contains(remote, "go-gitea/gitea") { continue } if !strings.Contains(remote, forkUser) { continue } if strings.HasPrefix(remote, "git@github.com:") { forkUser = strings.TrimPrefix(remote, "git@github.com:") } else if strings.HasPrefix(remote, "https://github.com/") { forkUser = strings.TrimPrefix(remote, "https://github.com/") } else if strings.HasPrefix(remote, "https://www.github.com/") { forkUser = strings.TrimPrefix(remote, "https://www.github.com/") } else if forkUser == "" { return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote) } idx := strings.Index(forkUser, "/") if idx >= 0 { forkUser = forkUser[:idx] } return name, forkUser, nil } return "", "", fmt.Errorf("unable to find appropriate remote in:\n%s", string(out)) } func readVersion() string { bs, err := os.ReadFile("docs/config.yaml") if err != nil { if err == os.ErrNotExist { log.Println("`docs/config.yaml` not present") return "" } fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err) return "" } type params struct { Version string } type docConfig struct { Params params } dc := &docConfig{} if err := yaml.Unmarshal(bs, dc); err != nil { fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err) return "" } if dc.Params.Version == "" { fmt.Fprintf(os.Stderr, "No version in `docs/config.yaml`") return "" } version := dc.Params.Version if version[0] != 'v' { version = "v" + version } split := strings.SplitN(version, ".", 3) return strings.Join(split[:2], ".") } func determineSHAforPR(ctx context.Context, prStr string) (string, error) { prNum, err := strconv.Atoi(prStr) if err != nil { return "", err } client := github.NewClient(http.DefaultClient) pr, _, err := client.PullRequests.Get(ctx, "go-gitea", "gitea", prNum) if err != nil { return "", err } if pr.Merged == nil || !*pr.Merged { return "", fmt.Errorf("PR #%d is not yet merged - cannot determine sha to backport", prNum) } if pr.MergeCommitSHA != nil { return *pr.MergeCommitSHA, nil } return "", nil } func installSignals() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) go func() { // install notify signalChannel := make(chan os.Signal, 1) signal.Notify( signalChannel, syscall.SIGINT, syscall.SIGTERM, ) select { case <-signalChannel: case <-ctx.Done(): } cancel() signal.Reset() }() return ctx, cancel }
-
-
-
@@ -927,14 +927,18 @@ ;; Force ssh:// clone url instead of scp-style uri when default SSH port is used;USE_COMPAT_SSH_URI = false ;; ;; Close issues as long as a commit on any branch marks it as fixed ;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects ;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages ;DISABLED_REPO_UNITS = ;; ;; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects. ;; Comma separated list of default new repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects, repo.packages. ;; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. ;; External wiki and issue tracker can't be enabled by default as it requires additional settings. ;; Disabled repo units will not be added to new repositories regardless if it is in the default list. ;DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects ;DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages ;; ;; Comma separated list of default forked repo units. ;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. ;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls ;; ;; Prefix archive files by placing them in a directory named after the repository ;PREFIX_ARCHIVE_FILES = true
-
@@ -1218,10 +1222,6 @@ ;SEARCH_REPO_DESCRIPTION = true;; ;; Whether to enable a Service Worker to cache frontend assets ;USE_SERVICE_WORKER = false ;; ;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. ;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). ;ONLY_SHOW_RELEVANT_REPOS = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
@@ -2458,6 +2458,8 @@ ;; Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`);LIMIT_SIZE_COMPOSER = -1 ;; Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_CONAN = -1 ;; Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_CONDA = -1 ;; Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_CONTAINER = -1 ;; Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
-
@@ -2550,3 +2552,19 @@ ;; Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy/no_proxy;PROXY_URL = ;; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts. ;PROXY_HOSTS = ; [actions] ;; Enable/Disable actions capabilities ;ENABLED = false ;; Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3" ;DEFAULT_ACTIONS_URL = https://gitea.com ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; settings for action logs, will override storage setting ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[storage.actions_log] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; storage type ;STORAGE_TYPE = local
-
-
-
@@ -37,7 +37,7 @@ PROVIDER_CONFIG = $GITEA_WORK_DIR/data/sessions[picture] AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/avatars REPOSITORY_AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/gitea/repo-avatars REPOSITORY_AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/repo-avatars [attachment] PATH = $GITEA_WORK_DIR/data/attachments
-
-
-
@@ -19,8 +19,8 @@ description: Git with a cup of teaauthor: The Gitea Authors website: https://docs.gitea.io version: 1.18.1 minGoVersion: 1.18 goVersion: 1.19 minGoVersion: 1.19 goVersion: 1.20 minNodeVersion: 16 search: nav repo: "https://github.com/go-gitea/gitea"
-
-
-
@@ -104,7 +104,8 @@ - `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed.- `ENABLE_PUSH_CREATE_USER`: **false**: Allow users to push local repositories to Gitea and have them automatically created for a user. - `ENABLE_PUSH_CREATE_ORG`: **false**: Allow users to push local repositories to Gitea and have them automatically created for an org. - `DISABLED_REPO_UNITS`: **_empty_**: Comma separated list of globally disabled repo units. Allowed values: \[repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects\] - `DEFAULT_REPO_UNITS`: **repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects**: Comma separated list of default repo units. Allowed values: \[repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects\]. Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. External wiki and issue tracker can't be enabled by default as it requires additional settings. Disabled repo units will not be added to new repositories regardless if it is in the default list. - `DEFAULT_REPO_UNITS`: **repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages**: Comma separated list of default new repo units. Allowed values: \[repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects\]. Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. External wiki and issue tracker can't be enabled by default as it requires additional settings. Disabled repo units will not be added to new repositories regardless if it is in the default list. - `DEFAULT_FORK_REPO_UNITS`: **repo.code,repo.pulls**: Comma separated list of default forked repo units. The set of allowed values and rules is the same as `DEFAULT_REPO_UNITS`. - `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository. - `DISABLE_MIGRATIONS`: **false**: Disable migrating feature. - `DISABLE_STARS`: **false**: Disable stars feature.
-
@@ -230,8 +231,6 @@ add it to this config.- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. - `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page. - `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets. - `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). ### UI - Admin (`ui.admin`)
-
@@ -1214,6 +1213,7 @@ - `LIMIT_TOTAL_OWNER_COUNT`: **-1**: Maximum count of package versions a single owner can have (`-1` means no limits)- `LIMIT_TOTAL_OWNER_SIZE`: **-1**: Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_COMPOSER`: **-1**: Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONAN`: **-1**: Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONDA`: **-1**: Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONTAINER`: **-1**: Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_GENERIC`: **-1**: Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_HELM`: **-1**: Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
-
@@ -1313,6 +1313,41 @@ PROXY_ENABLED = truePROXY_URL = socks://127.0.0.1:1080 PROXY_HOSTS = *.github.com ``` ## Actions (`actions`) - `ENABLED`: **false**: Enable/Disable actions capabilities - `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3" `DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like ```yaml name: versions on: push: branches: - main - releases/* jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ``` Now we need to know how to get actions/checkout, this configuration is the default git server to get it. That means we will get the repository via git clone ${DEFAULT_ACTIONS_URL}/actions/checkout and fetch tag v3. To help people who don't want to mirror these actions in their git instances, the default value is https://gitea.com To help people run actions totally in their network, they can change the value and copy all necessary action repositories into their git server. Of course we should support the form in future PRs like ```yaml steps: - uses: gitea.com/actions/checkout@v3 ``` although Github don't support this form. ## Other (`other`)
-
-
-
@@ -54,7 +54,7 @@ - `templates`: Golang templates for generating the html output.### Package Dependencies Since Golang don't support import cycles, we have to decide the package dependencies carefully. There are some levels between those packages. Below is the ideal package dependencies direction. Since Golang doesn't support import cycles, we have to decide the package dependencies carefully. There are some levels between those packages. Below is the ideal package dependencies direction. `cmd` -> `routers` -> `services` -> `models` -> `modules`
-
-
-
@@ -329,3 +329,22 @@ - The SPN contains only the hostname, without the port- You have added the URL of the web app to the `Local intranet zone` - The clocks of the server and client should not differ with more than 5 minutes (depends on group policy) - `Integrated Windows Authentication` should be enabled in Internet Explorer (under `Advanced settings`) ## Reverse Proxy Gitea supports Reverse Proxy Header authentication, it will read headers as a trusted login user name or user email address. This hasn't been enabled by default, you can enable it with ```ini [service] ENABLE_REVERSE_PROXY_AUTHENTICATION = true ``` The default login user name is in the `X-WEBAUTH-USER` header, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini. If the user doesn't exist, you can enable automatic registration with `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true`. The default login user email is `X-WEBAUTH-EMAIL`, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_EMAIL` in app.ini, this could also be disabled with `ENABLE_REVERSE_PROXY_EMAIL` If set `ENABLE_REVERSE_PROXY_FULL_NAME=true`, a user full name expected in `X-WEBAUTH-FULLNAME` will be assigned to the user when auto creating the user. You can also change the header name with `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`. You can also limit the reverse proxy's IP address range with `REVERSE_PROXY_TRUSTED_PROXIES` which default value is `127.0.0.0/8,::1/128`. By `REVERSE_PROXY_LIMIT`, you can limit trusted proxies level. Notice: Reverse Proxy Auth doesn't support the API. You still need an access token or basic auth to make API requests.
-
-
-
@@ -15,4 +15,21 @@ ---# 认证 ## TBD ## 反向代理认证 Gitea 支持通过读取反向代理传递的 HTTP 头中的登录名或者 email 地址来支持反向代理来认证。默认是不启用的,你可以用以下配置启用。 ```ini [service] ENABLE_REVERSE_PROXY_AUTHENTICATION = true ``` 默认的登录用户名的 HTTP 头是 `X-WEBAUTH-USER`,你可以通过修改 `REVERSE_PROXY_AUTHENTICATION_USER` 来变更它。如果用户不存在,可以自动创建用户,当然你需要修改 `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true` 来启用它。 默认的登录用户 Email 的 HTTP 头是 `X-WEBAUTH-EMAIL`,你可以通过修改 `REVERSE_PROXY_AUTHENTICATION_EMAIL` 来变更它。如果用户不存在,可以自动创建用户,当然你需要修改 `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true` 来启用它。你也可以通过修改 `ENABLE_REVERSE_PROXY_EMAIL` 来启用或停用这个 HTTP 头。 如果设置了 `ENABLE_REVERSE_PROXY_FULL_NAME=true`,则用户的全名会从 `X-WEBAUTH-FULLNAME` 读取,这样在自动创建用户时将使用这个字段作为用户全名,你也可以通过修改 `REVERSE_PROXY_AUTHENTICATION_FULL_NAME` 来变更 HTTP 头。 你也可以通过修改 `REVERSE_PROXY_TRUSTED_PROXIES` 来设置反向代理的IP地址范围,加强安全性,默认值是 `127.0.0.0/8,::1/128`。 通过 `REVERSE_PROXY_LIMIT`, 可以设置最多信任几级反向代理。 注意:反向代理认证不支持认证 API,API 仍旧需要用 access token 来进行认证。
-
-
-
@@ -56,7 +56,7 @@ | Repo-specific wiki (as a repo itself) | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ || Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | RSS Feeds | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ✘ | | Built-in CI/CD | [✘](https://github.com/go-gitea/gitea/issues/13539) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Built-in CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Subgroups: groups within groups | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | | Interaction with other instances | [/](https://github.com/go-gitea/gitea/issues/18240) | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | | Mermaid diagrams in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
-
-
-
@@ -49,7 +49,7 @@ | 仓库写权限令牌 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ || 内置容器 Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 外部 Git 镜像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? | | 内置 CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 内置 CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 子组织:组织内的组织 | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | #### 代码管理
-
-
-
@@ -51,7 +51,7 @@ | 有寫入權限的儲存庫 Token | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ || 內建 Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | 對外部 Git 鏡像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | FIDO (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | 內建 CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 內建 CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 子群組: 群組中的群組 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | ## 程式碼管理
-
-
-
@@ -0,0 +1,85 @@--- date: "2022-12-28T00:00:00+00:00" title: "Conda Packages Repository" slug: "packages/conda" draft: false toc: false menu: sidebar: parent: "packages" name: "Conda" weight: 25 identifier: "conda" --- # Conda Packages Repository Publish [Conda](https://docs.conda.io/en/latest/) packages for your user or organization. **Table of Contents** {{< toc >}} ## Requirements To work with the Conda package registry, you need to use [conda](https://docs.conda.io/projects/conda/en/stable/user-guide/install/index.html). ## Configuring the package registry To register the package registry and provide credentials, edit your `.condarc` file: ```yaml channel_alias: https://gitea.example.com/api/packages/{owner}/conda channels: - https://gitea.example.com/api/packages/{owner}/conda default_channels: - https://gitea.example.com/api/packages/{owner}/conda ``` | Placeholder | Description | | ------------ | ----------- | | `owner` | The owner of the package. | See the [official documentation](https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html) for explanations of the individual settings. If you need to provide credentials, you may embed them as part of the channel url (`https://user:password@gitea.example.com/...`). ## Publish a package To publish a package, perform a HTTP PUT operation with the package content in the request body. ``` PUT https://gitea.example.com/api/packages/{owner}/conda/{channel}/{filename} ``` | Placeholder | Description | | ------------ | ----------- | | `owner` | The owner of the package. | | `channel` | The [channel](https://conda.io/projects/conda/en/latest/user-guide/concepts/channels.html) of the package. (optional) | | `filename` | The name of the file. | Example request using HTTP Basic authentication: ```shell curl --user your_username:your_password_or_token \ --upload-file path/to/package-1.0.conda \ https://gitea.example.com/api/packages/testuser/conda/package-1.0.conda ``` You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. ## Install a package To install a package from the package registry, execute one of the following commands: ```shell conda install {package_name} conda install {package_name}={package_version} conda install -c {channel} {package_name} ``` | Parameter | Description | | ----------------- | ----------- | | `package_name` | The package name. | | `package_version` | The package version. | | `channel` | The channel of the package. (optional) |
-
-
-
@@ -28,6 +28,7 @@ | Name | Language | Package client || ---- | -------- | -------------- | | [Composer]({{< relref "doc/packages/composer.en-us.md" >}}) | PHP | `composer` | | [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` | | [Conda]({{< relref "doc/packages/conda.en-us.md" >}}) | - | `conda` | | [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client | | [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client | | [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` |
-
-
-
@@ -71,7 +71,7 @@ |`package/v.+\|other/release`|Match versions of the package `package` that start with `v` or the version `release` of the package `other`. This needs the setting *Apply pattern to full package name* enabled.|### How the cleanup rules work The cleanup rules are part of the [clean up job]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#cron---cleanup-expired-packages-croncleanup_packages" >}}) and run periodicly. The cleanup rules are part of the [clean up job]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#cron---cleanup-expired-packages-croncleanup_packages" >}}) and run periodically. The cleanup rule:
-
-
-
@@ -1,6 +1,6 @@--- date: "2022-12-19T21:26:00+08:00" title: "Encrypted secrets" title: "Secrets" slug: "secrets/overview" draft: false toc: false
-
@@ -12,24 +12,24 @@ weight: 1identifier: "overview" --- # Encrypted secrets # Secrets Encrypted secrets allow you to store sensitive information in your organization or repository. Secrets allow you to store sensitive information in your user, organization or repository. Secrets are available on Gitea 1.19+. # Naming your secrets The following rules apply to secret names: Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed. - Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed. Secret names must not start with the `GITHUB_` and `GITEA_` prefix. - Secret names must not start with the `GITHUB_` and `GITEA_` prefix. Secret names must not start with a number. - Secret names must not start with a number. Secret names are not case-sensitive. - Secret names are not case-sensitive. Secret names must be unique at the level they are created at. - Secret names must be unique at the level they are created at. For example, a secret created at the repository level must have a unique name in that repository, and a secret created at the organization level must have a unique name at that level.
-
-
-
@@ -382,7 +382,7 @@ when there is any public key added or changed on your Gitea instance.Sometimes if you moved or renamed your Gitea binary when upgrade and you haven't run `Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)` on your Admin Panel. Then all pull/push via SSH will not be work. This check will help you to check if it works well. For contributors, if you want to add more checks, you can wrie ad new function like `func(ctx *cli.Context) ([]string, error)` and For contributors, if you want to add more checks, you can write a new function like `func(ctx *cli.Context) ([]string, error)` and append it to `doctor.go`. ```go
-
-
-
@@ -24,7 +24,7 @@ in search for references. These references will be shown as links in the Issue Viewand, in some cases, produce certain _actions_. Likewise, commit messages are parsed when they are listed, and _actions_ are can be triggered when they are pushed to the main branch. can be triggered when they are pushed to the main branch. To prevent the creation of unintended references, there are certain rules for them to be recognized. For example, they should not be included inside code
-
-
-
@@ -1,8 +1,9 @@module code.gitea.io/gitea go 1.18 go 1.19 require ( code.gitea.io/actions-proto-go v0.2.0 code.gitea.io/gitea-vet v0.2.2 code.gitea.io/sdk/gitea v0.15.1 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
-
@@ -17,6 +18,7 @@ github.com/NYTimes/gziphandler v1.1.1github.com/PuerkitoBio/goquery v1.8.0 github.com/alecthomas/chroma/v2 v2.4.0 github.com/blevesearch/bleve/v2 v2.3.6 github.com/bufbuild/connect-go v1.3.1 github.com/buildkite/terminal-to-html/v3 v3.7.0 github.com/caddyserver/certmagic v0.17.2 github.com/chi-middleware/proxy v1.1.1
-
@@ -24,6 +26,7 @@ github.com/denisenkom/go-mssqldb v0.12.2github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/dustin/go-humanize v1.0.0 github.com/editorconfig/editorconfig-core-go/v2 v2.5.1 github.com/emersion/go-imap v1.2.1
-
@@ -74,6 +77,7 @@ github.com/mholt/archiver/v3 v3.5.1github.com/microcosm-cc/bluemonday v1.0.21 github.com/minio/minio-go/v7 v7.0.46 github.com/msteinert/pam v1.1.0 github.com/nektos/act v0.0.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/niklasfasching/go-org v1.6.5 github.com/oliamb/cutter v0.2.2
-
@@ -97,14 +101,14 @@ github.com/yohcop/openid-go v1.0.0github.com/yuin/goldmark v1.5.3 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 github.com/yuin/goldmark-meta v1.1.0 go.jolheiser.com/hcaptcha v0.0.4 go.jolheiser.com/pwn v0.0.3 golang.org/x/crypto v0.4.0 golang.org/x/net v0.4.0 golang.org/x/oauth2 v0.3.0 golang.org/x/sys v0.3.0 golang.org/x/text v0.5.0 golang.org/x/tools v0.1.12 google.golang.org/grpc v1.47.0 google.golang.org/protobuf v1.28.1 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1
-
@@ -159,14 +163,15 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirectgithub.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/errors v0.20.3 // indirect github.com/go-openapi/inflect v0.19.0 // indirect
-
@@ -208,6 +213,7 @@ github.com/libdns/libdns v0.2.1 // indirectgithub.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/markbates/going v1.0.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mholt/acmez v1.0.4 // indirect
-
@@ -232,7 +238,9 @@ github.com/pmezard/go-difflib v1.0.0 // indirectgithub.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rhysd/actionlint v1.6.22 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rs/xid v1.4.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
-
@@ -261,9 +269,10 @@ go.uber.org/atomic v1.10.0 // indirectgo.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
-
@@ -276,6 +285,8 @@replace github.com/go-ap/activitypub => gitea.com/xy/activitypub v0.0.0-20221126171442-81405e14ea3b replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix replace github.com/nektos/act => gitea.com/gitea/act v0.234.2-0.20230131074955-e46ede1b1744 exclude github.com/gofrs/uuid v3.2.0+incompatible
-
-
-
@@ -58,6 +58,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= code.gitea.io/actions-proto-go v0.2.0 h1:nYh9nhhfk67YA4wVNLsCzd//RCvXnljwXClJ33+HPVk= code.gitea.io/actions-proto-go v0.2.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk= code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
-
@@ -68,6 +70,8 @@ codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= gitea.com/gitea/act v0.234.2-0.20230131074955-e46ede1b1744 h1:cqzKmGlX0wynSXO04NILpL25eBGwogDrKpkkbwmIpj4= gitea.com/gitea/act v0.234.2-0.20230131074955-e46ede1b1744/go.mod h1:2C/WbTalu1VPNgbVaZJaZDzlOtAKqkXJhdOClxkMy14= gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681 h1:MMSPgnVULVwV9kEBgvyEUhC9v/uviZ55hPJEMjpbNR4= gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
-
@@ -227,6 +231,8 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bufbuild/connect-go v1.3.1 h1:doJP6Q8Ypg6haUT2IAZJPWHUN9rAUp+F9MfK7yhu1zs= github.com/bufbuild/connect-go v1.3.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/buildkite/terminal-to-html/v3 v3.7.0 h1:chdLUSpiOj/A4v3dzxyOqixXI6aw7IDA6Dk77FXsvNU= github.com/buildkite/terminal-to-html/v3 v3.7.0/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-
@@ -354,6 +360,7 @@ github.com/ethantkoenig/rupture v1.0.1 h1:6aAXghmvtnngMgQzy7SMGdicMvkV86V4n9fT0meE5E4=github.com/ethantkoenig/rupture v1.0.1/go.mod h1:Sjqo/nbffZp1pVVXNGhpugIjsWmuS9KiIB4GtpEBur4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-
@@ -404,6 +411,8 @@ github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI=github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
-
@@ -881,7 +890,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-
@@ -889,6 +900,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-
@@ -1082,11 +1095,15 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rhysd/actionlint v1.6.22 h1:cAEf2PGNwJXhdcTVF2xS/0ORqWS+ueUHwjQYsqFsGSk= github.com/rhysd/actionlint v1.6.22/go.mod h1:gIKOdxtV40mBOcD0ZR8EBa8NqjEXToAZioroS3oedMg= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-
@@ -1267,10 +1284,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.jolheiser.com/hcaptcha v0.0.4 h1:RrDERcr/Tz/kWyJenjVtI+V09RtLinXxlAemiwN5F+I= go.jolheiser.com/hcaptcha v0.0.4/go.mod h1:aw32WQOxnQZ6E06C0LypCf+sxNxPACyOnq+ZGnrIYho= go.jolheiser.com/pwn v0.0.3 h1:MQowb3QvCL5r5NmHmCPxw93SdjfgJ0q6rAwYn4i1Hjg= go.jolheiser.com/pwn v0.0.3/go.mod h1:/j5Dl8ftNqqJ8Dlx3YTrJV1wIR2lWOTyrNU3Qe7rk6I= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
-
@@ -1483,8 +1496,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-
@@ -1828,6 +1842,7 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-
@@ -1865,6 +1880,7 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-
-
models/actions/run.go (new)
-
@@ -0,0 +1,274 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "strings" "time" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/nektos/act/pkg/jobparser" "xorm.io/builder" ) // ActionRun represents a run of a workflow file type ActionRun struct { ID int64 Title string RepoID int64 `xorm:"index unique(repo_index)"` Repo *repo_model.Repository `xorm:"-"` OwnerID int64 `xorm:"index"` WorkflowID string `xorm:"index"` // the name of workflow file Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository TriggerUserID int64 TriggerUser *user_model.User `xorm:"-"` Ref string CommitSHA string IsForkPullRequest bool Event webhook_module.HookEventType EventPayload string `xorm:"LONGTEXT"` Status Status `xorm:"index"` Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } func init() { db.RegisterModel(new(ActionRun)) db.RegisterModel(new(ActionRunIndex)) } func (run *ActionRun) HTMLURL() string { if run.Repo == nil { return "" } return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index) } func (run *ActionRun) Link() string { if run.Repo == nil { return "" } return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) } // RefLink return the url of run's ref func (run *ActionRun) RefLink() string { refName := git.RefName(run.Ref) if refName.RefGroup() == "pull" { return run.Repo.Link() + "/pulls/" + refName.ShortName() } return git.RefURL(run.Repo.Link(), run.Ref) } // PrettyRef return #id for pull ref or ShortName for others func (run *ActionRun) PrettyRef() string { refName := git.RefName(run.Ref) if refName.RefGroup() == "pull" { return "#" + strings.TrimSuffix(strings.TrimPrefix(run.Ref, git.PullPrefix), "/head") } return refName.ShortName() } // LoadAttributes load Repo TriggerUser if not loaded func (run *ActionRun) LoadAttributes(ctx context.Context) error { if run == nil { return nil } if run.Repo == nil { repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) if err != nil { return err } run.Repo = repo } if err := run.Repo.LoadAttributes(ctx); err != nil { return err } if run.TriggerUser == nil { u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID) if err != nil { return err } run.TriggerUser = u } return nil } func (run *ActionRun) Duration() time.Duration { return calculateDuration(run.Started, run.Stopped, run.Status) } func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) { if run.Event == webhook_module.HookEventPush { var payload api.PushPayload if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { return nil, err } return &payload, nil } return nil, fmt.Errorf("event %s is not a push event", run.Event) } func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { _, err := db.GetEngine(ctx).ID(repo.ID). SetExpr("num_action_runs", builder.Select("count(*)").From("action_run"). Where(builder.Eq{"repo_id": repo.ID}), ). SetExpr("num_closed_action_runs", builder.Select("count(*)").From("action_run"). Where(builder.Eq{ "repo_id": repo.ID, }.And( builder.In("status", StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped, ), ), ), ). Update(repo) return err } // InsertRun inserts a run func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error { ctx, commiter, err := db.TxContext(ctx) if err != nil { return err } defer commiter.Close() index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID) if err != nil { return err } run.Index = index if run.Status.IsUnknown() { run.Status = StatusWaiting } if err := db.Insert(ctx, run); err != nil { return err } if run.Repo == nil { repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) if err != nil { return err } run.Repo = repo } if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { return err } runJobs := make([]*ActionRunJob, 0, len(jobs)) for _, v := range jobs { id, job := v.Job() needs := job.Needs() job.EraseNeeds() payload, _ := v.Marshal() status := StatusWaiting if len(needs) > 0 { status = StatusBlocked } runJobs = append(runJobs, &ActionRunJob{ RunID: run.ID, RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA, IsForkPullRequest: run.IsForkPullRequest, Name: job.Name, WorkflowPayload: payload, JobID: id, Needs: needs, RunsOn: job.RunsOn(), Status: status, }) } if err := db.Insert(ctx, runJobs); err != nil { return err } return commiter.Commit() } func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) { var run ActionRun has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist) } return &run, nil } func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) { run := &ActionRun{ RepoID: repoID, Index: index, } has, err := db.GetEngine(ctx).Get(run) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("run with index %d %d: %w", repoID, index, util.ErrNotExist) } return run, nil } func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error { sess := db.GetEngine(ctx).ID(run.ID) if len(cols) > 0 { sess.Cols(cols...) } _, err := sess.Update(run) if run.Status != 0 || util.SliceContains(cols, "status") { if run.RepoID == 0 { run, err = GetRunByID(ctx, run.ID) if err != nil { return err } } if run.Repo == nil { repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) if err != nil { return err } run.Repo = repo } if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { return err } } return err } type ActionRunIndex db.ResourceIndex
-
-
-
@@ -0,0 +1,163 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) // ActionRunJob represents a job of a run type ActionRunJob struct { ID int64 RunID int64 `xorm:"index"` Run *ActionRun `xorm:"-"` RepoID int64 `xorm:"index"` OwnerID int64 `xorm:"index"` CommitSHA string `xorm:"index"` IsForkPullRequest bool Name string `xorm:"VARCHAR(255)"` Attempt int64 WorkflowPayload []byte JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id Needs []string `xorm:"JSON TEXT"` RunsOn []string `xorm:"JSON TEXT"` TaskID int64 // the latest task of the job Status Status `xorm:"index"` Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated index"` } func init() { db.RegisterModel(new(ActionRunJob)) } func (job *ActionRunJob) Duration() time.Duration { return calculateDuration(job.Started, job.Stopped, job.Status) } func (job *ActionRunJob) LoadRun(ctx context.Context) error { if job.Run == nil { run, err := GetRunByID(ctx, job.RunID) if err != nil { return err } job.Run = run } return nil } // LoadAttributes load Run if not loaded func (job *ActionRunJob) LoadAttributes(ctx context.Context) error { if job == nil { return nil } if err := job.LoadRun(ctx); err != nil { return err } return job.Run.LoadAttributes(ctx) } func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) { var job ActionRunJob has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("run job with id %d: %w", id, util.ErrNotExist) } return &job, nil } func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*ActionRunJob, error) { var jobs []*ActionRunJob if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil { return nil, err } return jobs, nil } func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, cols ...string) (int64, error) { e := db.GetEngine(ctx) sess := e.ID(job.ID) if len(cols) > 0 { sess.Cols(cols...) } if cond != nil { sess.Where(cond) } affected, err := sess.Update(job) if err != nil { return 0, err } if affected == 0 || (!util.SliceContains(cols, "status") && job.Status == 0) { return affected, nil } if job.RunID == 0 { var err error if job, err = GetRunJobByID(ctx, job.ID); err != nil { return affected, err } } jobs, err := GetRunJobsByRunID(ctx, job.RunID) if err != nil { return affected, err } runStatus := aggregateJobStatus(jobs) run := &ActionRun{ ID: job.RunID, Status: runStatus, } if runStatus.IsDone() { run.Stopped = timeutil.TimeStampNow() } return affected, UpdateRun(ctx, run) } func aggregateJobStatus(jobs []*ActionRunJob) Status { allDone := true allWaiting := true hasFailure := false for _, job := range jobs { if !job.Status.IsDone() { allDone = false } if job.Status != StatusWaiting { allWaiting = false } if job.Status == StatusFailure || job.Status == StatusCancelled { hasFailure = true } } if allDone { if hasFailure { return StatusFailure } return StatusSuccess } if allWaiting { return StatusWaiting } return StatusRunning }
-
-
-
@@ -0,0 +1,99 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) type ActionJobList []*ActionRunJob func (jobs ActionJobList) GetRunIDs() []int64 { ids := make(container.Set[int64], len(jobs)) for _, j := range jobs { if j.RunID == 0 { continue } ids.Add(j.RunID) } return ids.Values() } func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error { runIDs := jobs.GetRunIDs() runs := make(map[int64]*ActionRun, len(runIDs)) if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil { return err } for _, j := range jobs { if j.RunID > 0 && j.Run == nil { j.Run = runs[j.RunID] } } if withRepo { var runsList RunList = make([]*ActionRun, 0, len(runs)) for _, r := range runs { runsList = append(runsList, r) } return runsList.LoadRepos() } return nil } func (jobs ActionJobList) LoadAttributes(ctx context.Context, withRepo bool) error { return jobs.LoadRuns(ctx, withRepo) } type FindRunJobOptions struct { db.ListOptions RunID int64 RepoID int64 OwnerID int64 CommitSHA string Statuses []Status UpdatedBefore timeutil.TimeStamp } func (opts FindRunJobOptions) toConds() builder.Cond { cond := builder.NewCond() if opts.RunID > 0 { cond = cond.And(builder.Eq{"run_id": opts.RunID}) } if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) } if opts.OwnerID > 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } if opts.CommitSHA != "" { cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA}) } if len(opts.Statuses) > 0 { cond = cond.And(builder.In("status", opts.Statuses)) } if opts.UpdatedBefore > 0 { cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore}) } return cond } func FindRunJobs(ctx context.Context, opts FindRunJobOptions) (ActionJobList, int64, error) { e := db.GetEngine(ctx).Where(opts.toConds()) if opts.PageSize > 0 && opts.Page >= 1 { e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } var tasks ActionJobList total, err := e.FindAndCount(&tasks) return tasks, total, err } func CountRunJobs(ctx context.Context, opts FindRunJobOptions) (int64, error) { return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionRunJob)) }
-
-
-
@@ -0,0 +1,107 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) type RunList []*ActionRun // GetUserIDs returns a slice of user's id func (runs RunList) GetUserIDs() []int64 { ids := make(container.Set[int64], len(runs)) for _, run := range runs { ids.Add(run.TriggerUserID) } return ids.Values() } func (runs RunList) GetRepoIDs() []int64 { ids := make(container.Set[int64], len(runs)) for _, run := range runs { ids.Add(run.RepoID) } return ids.Values() } func (runs RunList) LoadTriggerUser(ctx context.Context) error { userIDs := runs.GetUserIDs() users := make(map[int64]*user_model.User, len(userIDs)) if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil { return err } for _, run := range runs { if run.TriggerUserID == user_model.ActionsUserID { run.TriggerUser = user_model.NewActionsUser() } else { run.TriggerUser = users[run.TriggerUserID] } } return nil } func (runs RunList) LoadRepos() error { repoIDs := runs.GetRepoIDs() repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) if err != nil { return err } for _, run := range runs { run.Repo = repos[run.RepoID] } return nil } type FindRunOptions struct { db.ListOptions RepoID int64 OwnerID int64 IsClosed util.OptionalBool WorkflowFileName string } func (opts FindRunOptions) toConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) } if opts.OwnerID > 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } if opts.IsClosed.IsFalse() { cond = cond.And(builder.Eq{"status": StatusWaiting}.Or( builder.Eq{"status": StatusRunning})) } else if opts.IsClosed.IsTrue() { cond = cond.And( builder.Neq{"status": StatusWaiting}.And( builder.Neq{"status": StatusRunning})) } if opts.WorkflowFileName != "" { cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowFileName}) } return cond } func FindRuns(ctx context.Context, opts FindRunOptions) (RunList, int64, error) { e := db.GetEngine(ctx).Where(opts.toConds()) if opts.PageSize > 0 && opts.Page >= 1 { e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } var runs RunList total, err := e.Desc("id").FindAndCount(&runs) return runs, total, err } func CountRuns(ctx context.Context, opts FindRunOptions) (int64, error) { return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionRun)) }
-
-
models/actions/runner.go (new)
-
@@ -0,0 +1,252 @@// Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "strings" "time" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "xorm.io/builder" ) // ActionRunner represents runner machines type ActionRunner struct { ID int64 UUID string `xorm:"CHAR(36) UNIQUE"` Name string `xorm:"VARCHAR(255)"` OwnerID int64 `xorm:"index"` // org level runner, 0 means system Owner *user_model.User `xorm:"-"` RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global Repo *repo_model.Repository `xorm:"-"` Description string `xorm:"TEXT"` Base int // 0 native 1 docker 2 virtual machine RepoRange string // glob match which repositories could use this runner Token string `xorm:"-"` TokenHash string `xorm:"UNIQUE"` // sha256 of token TokenSalt string // TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token LastOnline timeutil.TimeStamp `xorm:"index"` LastActive timeutil.TimeStamp `xorm:"index"` // Store OS and Artch. AgentLabels []string // Store custom labes use defined. CustomLabels []string Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` Deleted timeutil.TimeStamp `xorm:"deleted"` } func (r *ActionRunner) OwnType() string { if r.RepoID != 0 { return fmt.Sprintf("Repo(%s)", r.Repo.FullName()) } if r.OwnerID != 0 { return fmt.Sprintf("Org(%s)", r.Owner.Name) } return "Global" } func (r *ActionRunner) Status() runnerv1.RunnerStatus { if time.Since(r.LastOnline.AsTime()) > time.Minute { return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE } if time.Since(r.LastActive.AsTime()) > 10*time.Second { return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE } return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE } func (r *ActionRunner) StatusName() string { return strings.ToLower(strings.TrimPrefix(r.Status().String(), "RUNNER_STATUS_")) } func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string { return lang.Tr("actions.runners.status." + r.StatusName()) } func (r *ActionRunner) IsOnline() bool { status := r.Status() if status == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE || status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE { return true } return false } // AllLabels returns agent and custom labels func (r *ActionRunner) AllLabels() []string { return append(r.AgentLabels, r.CustomLabels...) } // Editable checks if the runner is editable by the user func (r *ActionRunner) Editable(ownerID, repoID int64) bool { if ownerID == 0 && repoID == 0 { return true } if ownerID > 0 && r.OwnerID == ownerID { return true } return repoID > 0 && r.RepoID == repoID } // LoadAttributes loads the attributes of the runner func (r *ActionRunner) LoadAttributes(ctx context.Context) error { if r.OwnerID > 0 { var user user_model.User has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user) if err != nil { return err } if has { r.Owner = &user } } if r.RepoID > 0 { var repo repo_model.Repository has, err := db.GetEngine(ctx).ID(r.RepoID).Get(&repo) if err != nil { return err } if has { r.Repo = &repo } } return nil } func (r *ActionRunner) GenerateToken() (err error) { r.Token, r.TokenSalt, r.TokenHash, _, err = generateSaltedToken() return err } func init() { db.RegisterModel(&ActionRunner{}) } type FindRunnerOptions struct { db.ListOptions RepoID int64 OwnerID int64 Sort string Filter string WithAvailable bool // not only runners belong to, but also runners can be used } func (opts FindRunnerOptions) toCond() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID}) if opts.WithAvailable { c = c.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": opts.RepoID})}) c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0}) } cond = cond.And(c) } if opts.OwnerID > 0 { c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID}) if opts.WithAvailable { c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0}) } cond = cond.And(c) } if opts.Filter != "" { cond = cond.And(builder.Like{"name", opts.Filter}) } return cond } func (opts FindRunnerOptions) toOrder() string { switch opts.Sort { case "online": return "last_online DESC" case "offline": return "last_online ASC" case "alphabetically": return "name ASC" } return "last_online DESC" } func CountRunners(ctx context.Context, opts FindRunnerOptions) (int64, error) { return db.GetEngine(ctx). Where(opts.toCond()). Count(ActionRunner{}) } func FindRunners(ctx context.Context, opts FindRunnerOptions) (runners RunnerList, err error) { sess := db.GetEngine(ctx). Where(opts.toCond()). OrderBy(opts.toOrder()) if opts.Page > 0 { sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } return runners, sess.Find(&runners) } // GetRunnerByUUID returns a runner via uuid func GetRunnerByUUID(ctx context.Context, uuid string) (*ActionRunner, error) { var runner ActionRunner has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(&runner) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("runner with uuid %s: %w", uuid, util.ErrNotExist) } return &runner, nil } // GetRunnerByID returns a runner via id func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) { var runner ActionRunner has, err := db.GetEngine(ctx).Where("id=?", id).Get(&runner) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("runner with id %d: %w", id, util.ErrNotExist) } return &runner, nil } // UpdateRunner updates runner's information. func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error { e := db.GetEngine(ctx) var err error if len(cols) == 0 { _, err = e.ID(r.ID).AllCols().Update(r) } else { _, err = e.ID(r.ID).Cols(cols...).Update(r) } return err } // DeleteRunner deletes a runner by given ID. func DeleteRunner(ctx context.Context, id int64) error { if _, err := GetRunnerByID(ctx, id); err != nil { return err } _, err := db.GetEngine(ctx).Delete(&ActionRunner{ID: id}) return err } // CreateRunner creates new runner. func CreateRunner(ctx context.Context, t *ActionRunner) error { _, err := db.GetEngine(ctx).Insert(t) return err }
-
-
-
@@ -0,0 +1,77 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" ) type RunnerList []*ActionRunner // GetUserIDs returns a slice of user's id func (runners RunnerList) GetUserIDs() []int64 { ids := make(container.Set[int64], len(runners)) for _, runner := range runners { if runner.OwnerID == 0 { continue } ids.Add(runner.OwnerID) } return ids.Values() } func (runners RunnerList) LoadOwners(ctx context.Context) error { userIDs := runners.GetUserIDs() users := make(map[int64]*user_model.User, len(userIDs)) if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil { return err } for _, runner := range runners { if runner.OwnerID > 0 && runner.Owner == nil { runner.Owner = users[runner.OwnerID] } } return nil } func (runners RunnerList) getRepoIDs() []int64 { repoIDs := make(container.Set[int64], len(runners)) for _, runner := range runners { if runner.RepoID == 0 { continue } if _, ok := repoIDs[runner.RepoID]; !ok { repoIDs[runner.RepoID] = struct{}{} } } return repoIDs.Values() } func (runners RunnerList) LoadRepos(ctx context.Context) error { repoIDs := runners.getRepoIDs() repos := make(map[int64]*repo_model.Repository, len(repoIDs)) if err := db.GetEngine(ctx).In("id", repoIDs).Find(&repos); err != nil { return err } for _, runner := range runners { if runner.RepoID > 0 && runner.Repo == nil { runner.Repo = repos[runner.RepoID] } } return nil } func (runners RunnerList) LoadAttributes(ctx context.Context) error { if err := runners.LoadOwners(ctx); err != nil { return err } return runners.LoadRepos(ctx) }
-
-
-
@@ -0,0 +1,86 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) // ActionRunnerToken represents runner tokens type ActionRunnerToken struct { ID int64 Token string `xorm:"UNIQUE"` OwnerID int64 `xorm:"index"` // org level runner, 0 means system Owner *user_model.User `xorm:"-"` RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global Repo *repo_model.Repository `xorm:"-"` IsActive bool Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` Deleted timeutil.TimeStamp `xorm:"deleted"` } func init() { db.RegisterModel(new(ActionRunnerToken)) } // GetRunnerToken returns a action runner via token func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, error) { var runnerToken ActionRunnerToken has, err := db.GetEngine(ctx).Where("token=?", token).Get(&runnerToken) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist) } return &runnerToken, nil } // UpdateRunnerToken updates runner token information. func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string) (err error) { e := db.GetEngine(ctx) if len(cols) == 0 { _, err = e.ID(r.ID).AllCols().Update(r) } else { _, err = e.ID(r.ID).Cols(cols...).Update(r) } return err } // NewRunnerToken creates a new runner token func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { token, err := util.CryptoRandomString(40) if err != nil { return nil, err } runnerToken := &ActionRunnerToken{ OwnerID: ownerID, RepoID: repoID, IsActive: false, Token: token, } _, err = db.GetEngine(ctx).Insert(runnerToken) return runnerToken, err } // GetUnactivatedRunnerToken returns a unactivated runner token func GetUnactivatedRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { var runnerToken ActionRunnerToken has, err := db.GetEngine(ctx).Where("owner_id=? AND repo_id=? AND is_active=?", ownerID, repoID, false).OrderBy("id DESC").Get(&runnerToken) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("runner token: %w", util.ErrNotExist) } return &runnerToken, nil }
-
-
models/actions/status.go (new)
-
@@ -0,0 +1,100 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "code.gitea.io/gitea/modules/translation" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" ) // Status represents the status of ActionRun, ActionRunJob, ActionTask, or ActionTaskStep type Status int const ( StatusUnknown Status = iota // 0, consistent with runnerv1.Result_RESULT_UNSPECIFIED StatusSuccess // 1, consistent with runnerv1.Result_RESULT_SUCCESS StatusFailure // 2, consistent with runnerv1.Result_RESULT_FAILURE StatusCancelled // 3, consistent with runnerv1.Result_RESULT_CANCELLED StatusSkipped // 4, consistent with runnerv1.Result_RESULT_SKIPPED StatusWaiting // 5, isn't a runnerv1.Result StatusRunning // 6, isn't a runnerv1.Result StatusBlocked // 7, isn't a runnerv1.Result ) var statusNames = map[Status]string{ StatusUnknown: "unknown", StatusWaiting: "waiting", StatusRunning: "running", StatusSuccess: "success", StatusFailure: "failure", StatusCancelled: "cancelled", StatusSkipped: "skipped", StatusBlocked: "blocked", } // String returns the string name of the Status func (s Status) String() string { return statusNames[s] } // LocaleString returns the locale string name of the Status func (s Status) LocaleString(lang translation.Locale) string { return lang.Tr("actions.status." + s.String()) } // IsDone returns whether the Status is final func (s Status) IsDone() bool { return s.In(StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped) } // HasRun returns whether the Status is a result of running func (s Status) HasRun() bool { return s.In(StatusSuccess, StatusFailure) } func (s Status) IsUnknown() bool { return s == StatusUnknown } func (s Status) IsSuccess() bool { return s == StatusSuccess } func (s Status) IsFailure() bool { return s == StatusFailure } func (s Status) IsCancelled() bool { return s == StatusCancelled } func (s Status) IsSkipped() bool { return s == StatusSkipped } func (s Status) IsWaiting() bool { return s == StatusWaiting } func (s Status) IsRunning() bool { return s == StatusRunning } // In returns whether s is one of the given statuses func (s Status) In(statuses ...Status) bool { for _, v := range statuses { if s == v { return true } } return false } func (s Status) AsResult() runnerv1.Result { if s.IsDone() { return runnerv1.Result(s) } return runnerv1.Result_RESULT_UNSPECIFIED }
-
-
models/actions/task.go (new)
-
@@ -0,0 +1,504 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "crypto/subtle" "fmt" "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" lru "github.com/hashicorp/golang-lru" "github.com/nektos/act/pkg/jobparser" "google.golang.org/protobuf/types/known/timestamppb" "xorm.io/builder" ) // ActionTask represents a distribution of job type ActionTask struct { ID int64 JobID int64 Job *ActionRunJob `xorm:"-"` Steps []*ActionTaskStep `xorm:"-"` Attempt int64 RunnerID int64 `xorm:"index"` Status Status `xorm:"index"` Started timeutil.TimeStamp `xorm:"index"` Stopped timeutil.TimeStamp RepoID int64 `xorm:"index"` OwnerID int64 `xorm:"index"` CommitSHA string `xorm:"index"` IsForkPullRequest bool Token string `xorm:"-"` TokenHash string `xorm:"UNIQUE"` // sha256 of token TokenSalt string TokenLastEight string `xorm:"index token_last_eight"` LogFilename string // file name of log LogInStorage bool // read log from database or from storage LogLength int64 // lines count LogSize int64 // blob size LogIndexes LogIndexes `xorm:"LONGBLOB"` // line number to offset LogExpired bool // files that are too old will be deleted Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated index"` } var successfulTokenTaskCache *lru.Cache func init() { db.RegisterModel(new(ActionTask), func() error { if setting.SuccessfulTokensCacheSize > 0 { var err error successfulTokenTaskCache, err = lru.New(setting.SuccessfulTokensCacheSize) if err != nil { return fmt.Errorf("unable to allocate Task cache: %v", err) } } else { successfulTokenTaskCache = nil } return nil }) } func (task *ActionTask) Duration() time.Duration { return calculateDuration(task.Started, task.Stopped, task.Status) } func (task *ActionTask) IsStopped() bool { return task.Stopped > 0 } func (task *ActionTask) GetRunLink() string { if task.Job == nil || task.Job.Run == nil { return "" } return task.Job.Run.Link() } func (task *ActionTask) GetCommitLink() string { if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil { return "" } return task.Job.Run.Repo.CommitLink(task.CommitSHA) } func (task *ActionTask) GetRepoName() string { if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil { return "" } return task.Job.Run.Repo.FullName() } func (task *ActionTask) GetRepoLink() string { if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil { return "" } return task.Job.Run.Repo.Link() } func (task *ActionTask) LoadJob(ctx context.Context) error { if task.Job == nil { job, err := GetRunJobByID(ctx, task.JobID) if err != nil { return err } task.Job = job } return nil } // LoadAttributes load Job Steps if not loaded func (task *ActionTask) LoadAttributes(ctx context.Context) error { if task == nil { return nil } if err := task.LoadJob(ctx); err != nil { return err } if err := task.Job.LoadAttributes(ctx); err != nil { return err } if task.Steps == nil { // be careful, an empty slice (not nil) also means loaded steps, err := GetTaskStepsByTaskID(ctx, task.ID) if err != nil { return err } task.Steps = steps } return nil } func (task *ActionTask) GenerateToken() (err error) { task.Token, task.TokenSalt, task.TokenHash, task.TokenLastEight, err = generateSaltedToken() return err } func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) { var task ActionTask has, err := db.GetEngine(ctx).Where("id=?", id).Get(&task) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("task with id %d: %w", id, util.ErrNotExist) } return &task, nil } func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, error) { errNotExist := fmt.Errorf("task with token %q: %w", token, util.ErrNotExist) if token == "" { return nil, errNotExist } // A token is defined as being SHA1 sum these are 40 hexadecimal bytes long if len(token) != 40 { return nil, errNotExist } for _, x := range []byte(token) { if x < '0' || (x > '9' && x < 'a') || x > 'f' { return nil, errNotExist } } lastEight := token[len(token)-8:] if id := getTaskIDFromCache(token); id > 0 { task := &ActionTask{ TokenLastEight: lastEight, } // Re-get the task from the db in case it has been deleted in the intervening period has, err := db.GetEngine(ctx).ID(id).Get(task) if err != nil { return nil, err } if has { return task, nil } successfulTokenTaskCache.Remove(token) } var tasks []*ActionTask err := db.GetEngine(ctx).Where("token_last_eight = ? AND status = ?", lastEight, StatusRunning).Find(&tasks) if err != nil { return nil, err } else if len(tasks) == 0 { return nil, errNotExist } for _, t := range tasks { tempHash := auth_model.HashToken(token, t.TokenSalt) if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 { if successfulTokenTaskCache != nil { successfulTokenTaskCache.Add(token, t.ID) } return t, nil } } return nil, errNotExist } func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) { dbCtx, commiter, err := db.TxContext(ctx) if err != nil { return nil, false, err } defer commiter.Close() ctx = dbCtx.WithContext(ctx) e := db.GetEngine(ctx) jobCond := builder.NewCond() if runner.RepoID != 0 { jobCond = builder.Eq{"repo_id": runner.RepoID} } else if runner.OwnerID != 0 { jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID})) } if jobCond.IsValid() { jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond)) } var jobs []*ActionRunJob if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("id").Find(&jobs); err != nil { return nil, false, err } // TODO: a more efficient way to filter labels var job *ActionRunJob labels := runner.AgentLabels labels = append(labels, runner.CustomLabels...) log.Trace("runner labels: %v", labels) for _, v := range jobs { if isSubset(labels, v.RunsOn) { job = v break } } if job == nil { return nil, false, nil } if err := job.LoadAttributes(ctx); err != nil { return nil, false, err } now := timeutil.TimeStampNow() job.Attempt++ job.Started = now job.Status = StatusRunning task := &ActionTask{ JobID: job.ID, Attempt: job.Attempt, RunnerID: runner.ID, Started: now, Status: StatusRunning, RepoID: job.RepoID, OwnerID: job.OwnerID, CommitSHA: job.CommitSHA, IsForkPullRequest: job.IsForkPullRequest, } if err := task.GenerateToken(); err != nil { return nil, false, err } var workflowJob *jobparser.Job if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil { return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err) } else if len(gots) != 1 { return nil, false, fmt.Errorf("workflow of job %d: not signle workflow", job.ID) } else { _, workflowJob = gots[0].Job() } if _, err := e.Insert(task); err != nil { return nil, false, err } task.LogFilename = logFileName(job.Run.Repo.FullName(), task.ID) if _, err := e.ID(task.ID).Cols("log_filename").Update(task); err != nil { return nil, false, err } if len(workflowJob.Steps) > 0 { steps := make([]*ActionTaskStep, len(workflowJob.Steps)) for i, v := range workflowJob.Steps { steps[i] = &ActionTaskStep{ Name: v.String(), TaskID: task.ID, Index: int64(i), RepoID: task.RepoID, Status: StatusWaiting, } } if _, err := e.Insert(steps); err != nil { return nil, false, err } task.Steps = steps } job.TaskID = task.ID if n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}); err != nil { return nil, false, err } else if n != 1 { return nil, false, nil } if job.Run.Status.IsWaiting() { job.Run.Status = StatusRunning job.Run.Started = now if err := UpdateRun(ctx, job.Run, "status", "started"); err != nil { return nil, false, err } } task.Job = job if err := commiter.Commit(); err != nil { return nil, false, err } return task, true, nil } func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error { sess := db.GetEngine(ctx).ID(task.ID) if len(cols) > 0 { sess.Cols(cols...) } _, err := sess.Update(task) return err } func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) { stepStates := map[int64]*runnerv1.StepState{} for _, v := range state.Steps { stepStates[v.Id] = v } ctx, commiter, err := db.TxContext(ctx) if err != nil { return nil, err } defer commiter.Close() e := db.GetEngine(ctx) task := &ActionTask{} if has, err := e.ID(state.Id).Get(task); err != nil { return nil, err } else if !has { return nil, util.ErrNotExist } if state.Result != runnerv1.Result_RESULT_UNSPECIFIED { task.Status = Status(state.Result) task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix()) if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { return nil, err } } if _, err := e.ID(task.ID).Update(task); err != nil { return nil, err } if err := task.LoadAttributes(ctx); err != nil { return nil, err } for _, step := range task.Steps { var result runnerv1.Result if v, ok := stepStates[step.Index]; ok { result = v.Result step.LogIndex = v.LogIndex step.LogLength = v.LogLength step.Started = convertTimestamp(v.StartedAt) step.Stopped = convertTimestamp(v.StoppedAt) } if result != runnerv1.Result_RESULT_UNSPECIFIED { step.Status = Status(result) } else if step.Started != 0 { step.Status = StatusRunning } if _, err := e.ID(step.ID).Update(step); err != nil { return nil, err } } if err := commiter.Commit(); err != nil { return nil, err } return task, nil } func StopTask(ctx context.Context, taskID int64, status Status) error { if !status.IsDone() { return fmt.Errorf("cannot stop task with status %v", status) } e := db.GetEngine(ctx) task := &ActionTask{} if has, err := e.ID(taskID).Get(task); err != nil { return err } else if !has { return util.ErrNotExist } if task.Status.IsDone() { return nil } now := timeutil.TimeStampNow() task.Status = status task.Stopped = now if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { return err } if _, err := e.ID(task.ID).Update(task); err != nil { return err } if err := task.LoadAttributes(ctx); err != nil { return err } for _, step := range task.Steps { if !step.Status.IsDone() { step.Status = status if step.Started == 0 { step.Started = now } step.Stopped = now } if _, err := e.ID(step.ID).Update(step); err != nil { return err } } return nil } func isSubset(set, subset []string) bool { m := make(container.Set[string], len(set)) for _, v := range set { m.Add(v) } for _, v := range subset { if !m.Contains(v) { return false } } return true } func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp { if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 { return timeutil.TimeStamp(0) } return timeutil.TimeStamp(timestamp.AsTime().Unix()) } func logFileName(repoFullName string, taskID int64) string { return fmt.Sprintf("%s/%02x/%d.log", repoFullName, taskID%256, taskID) } func getTaskIDFromCache(token string) int64 { if successfulTokenTaskCache == nil { return 0 } tInterface, ok := successfulTokenTaskCache.Get(token) if !ok { return 0 } t, ok := tInterface.(int64) if !ok { return 0 } return t }
-
-
-
@@ -0,0 +1,105 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) type TaskList []*ActionTask func (tasks TaskList) GetJobIDs() []int64 { ids := make(container.Set[int64], len(tasks)) for _, t := range tasks { if t.JobID == 0 { continue } ids.Add(t.JobID) } return ids.Values() } func (tasks TaskList) LoadJobs(ctx context.Context) error { jobIDs := tasks.GetJobIDs() jobs := make(map[int64]*ActionRunJob, len(jobIDs)) if err := db.GetEngine(ctx).In("id", jobIDs).Find(&jobs); err != nil { return err } for _, t := range tasks { if t.JobID > 0 && t.Job == nil { t.Job = jobs[t.JobID] } } // TODO: Replace with "ActionJobList(maps.Values(jobs))" once available var jobsList ActionJobList = make([]*ActionRunJob, 0, len(jobs)) for _, j := range jobs { jobsList = append(jobsList, j) } return jobsList.LoadAttributes(ctx, true) } func (tasks TaskList) LoadAttributes(ctx context.Context) error { return tasks.LoadJobs(ctx) } type FindTaskOptions struct { db.ListOptions RepoID int64 OwnerID int64 CommitSHA string Status Status UpdatedBefore timeutil.TimeStamp StartedBefore timeutil.TimeStamp RunnerID int64 IDOrderDesc bool } func (opts FindTaskOptions) toConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) } if opts.OwnerID > 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } if opts.CommitSHA != "" { cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA}) } if opts.Status > StatusUnknown { cond = cond.And(builder.Eq{"status": opts.Status}) } if opts.UpdatedBefore > 0 { cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore}) } if opts.StartedBefore > 0 { cond = cond.And(builder.Lt{"started": opts.StartedBefore}) } if opts.RunnerID > 0 { cond = cond.And(builder.Eq{"runner_id": opts.RunnerID}) } return cond } func FindTasks(ctx context.Context, opts FindTaskOptions) (TaskList, error) { e := db.GetEngine(ctx).Where(opts.toConds()) if opts.PageSize > 0 && opts.Page >= 1 { e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } if opts.IDOrderDesc { e.OrderBy("id DESC") } var tasks TaskList return tasks, e.Find(&tasks) } func CountTasks(ctx context.Context, opts FindTaskOptions) (int64, error) { return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionTask)) }
-
-
-
@@ -0,0 +1,41 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) // ActionTaskStep represents a step of ActionTask type ActionTaskStep struct { ID int64 Name string `xorm:"VARCHAR(255)"` TaskID int64 `xorm:"index unique(task_index)"` Index int64 `xorm:"index unique(task_index)"` RepoID int64 `xorm:"index"` Status Status `xorm:"index"` LogIndex int64 LogLength int64 Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } func (step *ActionTaskStep) Duration() time.Duration { return calculateDuration(step.Started, step.Stopped, step.Status) } func init() { db.RegisterModel(new(ActionTaskStep)) } func GetTaskStepsByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskStep, error) { var steps []*ActionTaskStep return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("`index` ASC").Find(&steps) }
-
-
models/actions/utils.go (new)
-
@@ -0,0 +1,84 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "bytes" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) func generateSaltedToken() (string, string, string, string, error) { salt, err := util.CryptoRandomString(10) if err != nil { return "", "", "", "", err } buf, err := util.CryptoRandomBytes(20) if err != nil { return "", "", "", "", err } token := hex.EncodeToString(buf) hash := auth_model.HashToken(token, salt) return token, salt, hash, token[len(token)-8:], nil } /* LogIndexes is the index for mapping log line number to buffer offset. Because it uses varint encoding, it is impossible to predict its size. But we can make a simple estimate with an assumption that each log line has 200 byte, then: | lines | file size | index size | |-----------|---------------------|--------------------| | 100 | 20 KiB(20000) | 258 B(258) | | 1000 | 195 KiB(200000) | 2.9 KiB(2958) | | 10000 | 1.9 MiB(2000000) | 34 KiB(34715) | | 100000 | 19 MiB(20000000) | 386 KiB(394715) | | 1000000 | 191 MiB(200000000) | 4.1 MiB(4323626) | | 10000000 | 1.9 GiB(2000000000) | 47 MiB(49323626) | | 100000000 | 19 GiB(20000000000) | 490 MiB(513424280) | */ type LogIndexes []int64 func (indexes *LogIndexes) FromDB(b []byte) error { reader := bytes.NewReader(b) for { v, err := binary.ReadVarint(reader) if err != nil { if errors.Is(err, io.EOF) { return nil } return fmt.Errorf("binary ReadVarint: %w", err) } *indexes = append(*indexes, v) } } func (indexes *LogIndexes) ToDB() ([]byte, error) { buf, i := make([]byte, binary.MaxVarintLen64*len(*indexes)), 0 for _, v := range *indexes { n := binary.PutVarint(buf[i:], v) i += n } return buf[:i], nil } var timeSince = time.Since func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.Duration { if started == 0 { return 0 } s := started.AsTime() if status.IsDone() { return stopped.AsTime().Sub(s) } return timeSince(s).Truncate(time.Second) }
-
-
-
@@ -0,0 +1,90 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "math" "testing" "time" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestLogIndexes_ToDB(t *testing.T) { tests := []struct { indexes LogIndexes }{ { indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64}, }, } for _, tt := range tests { t.Run("", func(t *testing.T) { got, err := tt.indexes.ToDB() require.NoError(t, err) indexes := LogIndexes{} require.NoError(t, indexes.FromDB(got)) assert.Equal(t, tt.indexes, indexes) }) } } func Test_calculateDuration(t *testing.T) { oldTimeSince := timeSince defer func() { timeSince = oldTimeSince }() timeSince = func(t time.Time) time.Duration { return timeutil.TimeStamp(1000).AsTime().Sub(t) } type args struct { started timeutil.TimeStamp stopped timeutil.TimeStamp status Status } tests := []struct { name string args args want time.Duration }{ { name: "unknown", args: args{ started: 0, stopped: 0, status: StatusUnknown, }, want: 0, }, { name: "running", args: args{ started: 500, stopped: 0, status: StatusRunning, }, want: 500 * time.Second, }, { name: "done", args: args{ started: 500, stopped: 600, status: StatusSuccess, }, want: 100 * time.Second, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equalf(t, tt.want, calculateDuration(tt.args.started, tt.args.stopped, tt.args.status), "calculateDuration(%v, %v, %v)", tt.args.started, tt.args.stopped, tt.args.status) }) } }
-
-
-
@@ -151,7 +151,7 @@ return err} for i := range users { notify = append(notify, &Notification{ UserID: users[i].ID, UserID: i, RepoID: repo.ID, Status: NotificationStatusUnread, UpdatedBy: doer.ID,
-
-
-
@@ -24,6 +24,9 @@ func (err ErrKeyUnableVerify) Error() string {return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result) } // ErrKeyIsPrivate is returned when the provided key is a private key not a public key var ErrKeyIsPrivate = util.NewSilentWrapErrorf(util.ErrInvalidArgument, "the provided key is a private key") // ErrKeyNotExist represents a "KeyNotExist" kind of error. type ErrKeyNotExist struct { ID int64
-
-
-
@@ -96,6 +96,9 @@ block, _ := pem.Decode([]byte(content))if block == nil { return "", fmt.Errorf("failed to parse PEM block containing the public key") } if strings.Contains(block.Type, "PRIVATE") { return "", ErrKeyIsPrivate } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil {
-
-
-
@@ -27,3 +27,9 @@ SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"SearchOrderByForks SearchOrderBy = "num_forks ASC" SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" ) const ( // Which means a condition to filter the records which don't match any id. // It's different from zero which means the condition could be ignored. NoneID = -1 )
-
-
-
@@ -37,9 +37,7 @@ return conn, err} schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema) // golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here // and in any case pq does not implement it if execer, ok := conn.(driver.Execer); ok { //nolint if execer, ok := conn.(driver.Execer); ok { _, err := execer.Exec(`SELECT set_config( 'search_path', $1 || ',' || current_setting('search_path'),
-
@@ -63,8 +61,7 @@ defer stmt.Close()// driver.String.ConvertValue will never return err for string // golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint _, err = stmt.Exec([]driver.Value{schemaValue}) if err != nil { _ = conn.Close() return nil, err
-
-
models/dbfs/dbfile.go (new)
-
@@ -0,0 +1,357 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package dbfs import ( "context" "errors" "io" "os" "path/filepath" "strconv" "strings" "time" "code.gitea.io/gitea/models/db" ) var defaultFileBlockSize int64 = 32 * 1024 type File interface { io.ReadWriteCloser io.Seeker } type file struct { ctx context.Context metaID int64 fullPath string blockSize int64 allowRead bool allowWrite bool offset int64 } var _ File = (*file)(nil) func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err error) { if offset >= fileMeta.FileSize { return 0, io.EOF } blobPos := int(offset % f.blockSize) blobOffset := offset - int64(blobPos) blobRemaining := int(f.blockSize) - blobPos needRead := len(p) if needRead > blobRemaining { needRead = blobRemaining } if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize { needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos)) } if needRead <= 0 { return 0, io.EOF } var fileData dbfsData ok, err := db.GetEngine(f.ctx).Where("meta_id = ? AND blob_offset = ?", f.metaID, blobOffset).Get(&fileData) if err != nil { return 0, err } blobData := fileData.BlobData if !ok { blobData = nil } canCopy := len(blobData) - blobPos if canCopy <= 0 { canCopy = 0 } realRead := needRead if realRead > canCopy { realRead = canCopy } if realRead > 0 { copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead]) } for i := realRead; i < needRead; i++ { p[i] = 0 } return needRead, nil } func (f *file) Read(p []byte) (n int, err error) { if f.metaID == 0 || !f.allowRead { return 0, os.ErrInvalid } fileMeta, err := findFileMetaByID(f.ctx, f.metaID) if err != nil { return 0, err } n, err = f.readAt(fileMeta, f.offset, p) f.offset += int64(n) return n, err } func (f *file) Write(p []byte) (n int, err error) { if f.metaID == 0 || !f.allowWrite { return 0, os.ErrInvalid } fileMeta, err := findFileMetaByID(f.ctx, f.metaID) if err != nil { return 0, err } needUpdateSize := false written := 0 for len(p) > 0 { blobPos := int(f.offset % f.blockSize) blobOffset := f.offset - int64(blobPos) blobRemaining := int(f.blockSize) - blobPos needWrite := len(p) if needWrite > blobRemaining { needWrite = blobRemaining } buf := make([]byte, f.blockSize) readBytes, err := f.readAt(fileMeta, blobOffset, buf) if err != nil && !errors.Is(err, io.EOF) { return written, err } copy(buf[blobPos:blobPos+needWrite], p[:needWrite]) if blobPos+needWrite > readBytes { buf = buf[:blobPos+needWrite] } else { buf = buf[:readBytes] } fileData := dbfsData{ MetaID: fileMeta.ID, BlobOffset: blobOffset, BlobData: buf, } if res, err := db.GetEngine(f.ctx).Exec("UPDATE dbfs_data SET revision=revision+1, blob_data=? WHERE meta_id=? AND blob_offset=?", buf, fileMeta.ID, blobOffset); err != nil { return written, err } else if updated, err := res.RowsAffected(); err != nil { return written, err } else if updated == 0 { if _, err = db.GetEngine(f.ctx).Insert(&fileData); err != nil { return written, err } } written += needWrite f.offset += int64(needWrite) if f.offset > fileMeta.FileSize { fileMeta.FileSize = f.offset needUpdateSize = true } p = p[needWrite:] } fileMetaUpdate := dbfsMeta{ ModifyTimestamp: timeToFileTimestamp(time.Now()), } if needUpdateSize { fileMetaUpdate.FileSize = f.offset } if _, err := db.GetEngine(f.ctx).ID(fileMeta.ID).Update(fileMetaUpdate); err != nil { return written, err } return written, nil } func (f *file) Seek(n int64, whence int) (int64, error) { if f.metaID == 0 { return 0, os.ErrInvalid } newOffset := f.offset switch whence { case io.SeekStart: newOffset = n case io.SeekCurrent: newOffset += n case io.SeekEnd: size, err := f.size() if err != nil { return f.offset, err } newOffset = size + n default: return f.offset, os.ErrInvalid } if newOffset < 0 { return f.offset, os.ErrInvalid } f.offset = newOffset return newOffset, nil } func (f *file) Close() error { return nil } func timeToFileTimestamp(t time.Time) int64 { return t.UnixMicro() } func (f *file) loadMetaByPath() (*dbfsMeta, error) { var fileMeta dbfsMeta if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil { return nil, err } else if ok { f.metaID = fileMeta.ID f.blockSize = fileMeta.BlockSize return &fileMeta, nil } return nil, nil } func (f *file) open(flag int) (err error) { // see os.OpenFile for flag values if flag&os.O_WRONLY != 0 { f.allowWrite = true } else if flag&os.O_RDWR != 0 { f.allowRead = true f.allowWrite = true } else /* O_RDONLY */ { f.allowRead = true } if f.allowWrite { if flag&os.O_CREATE != 0 { if flag&os.O_EXCL != 0 { // file must not exist. if f.metaID != 0 { return os.ErrExist } } else { // create a new file if none exists. if f.metaID == 0 { if err = f.createEmpty(); err != nil { return err } } } } if flag&os.O_TRUNC != 0 { if err = f.truncate(); err != nil { return err } } if flag&os.O_APPEND != 0 { if _, err = f.Seek(0, io.SeekEnd); err != nil { return err } } return nil } // read only mode if f.metaID == 0 { return os.ErrNotExist } return nil } func (f *file) createEmpty() error { if f.metaID != 0 { return os.ErrExist } now := time.Now() _, err := db.GetEngine(f.ctx).Insert(&dbfsMeta{ FullPath: f.fullPath, BlockSize: f.blockSize, CreateTimestamp: timeToFileTimestamp(now), ModifyTimestamp: timeToFileTimestamp(now), }) if err != nil { return err } if _, err = f.loadMetaByPath(); err != nil { return err } return nil } func (f *file) truncate() error { if f.metaID == 0 { return os.ErrNotExist } return db.WithTx(f.ctx, func(ctx context.Context) error { if _, err := db.GetEngine(ctx).Exec("UPDATE dbfs_meta SET file_size = 0 WHERE id = ?", f.metaID); err != nil { return err } if _, err := db.GetEngine(ctx).Delete(&dbfsData{MetaID: f.metaID}); err != nil { return err } return nil }) } func (f *file) renameTo(newPath string) error { if f.metaID == 0 { return os.ErrNotExist } newPath = buildPath(newPath) return db.WithTx(f.ctx, func(ctx context.Context) error { if _, err := db.GetEngine(ctx).Exec("UPDATE dbfs_meta SET full_path = ? WHERE id = ?", newPath, f.metaID); err != nil { return err } return nil }) } func (f *file) delete() error { if f.metaID == 0 { return os.ErrNotExist } return db.WithTx(f.ctx, func(ctx context.Context) error { if _, err := db.GetEngine(ctx).Delete(&dbfsMeta{ID: f.metaID}); err != nil { return err } if _, err := db.GetEngine(ctx).Delete(&dbfsData{MetaID: f.metaID}); err != nil { return err } return nil }) } func (f *file) size() (int64, error) { if f.metaID == 0 { return 0, os.ErrNotExist } fileMeta, err := findFileMetaByID(f.ctx, f.metaID) if err != nil { return 0, err } return fileMeta.FileSize, nil } func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) { var fileMeta dbfsMeta if ok, err := db.GetEngine(ctx).Where("id = ?", metaID).Get(&fileMeta); err != nil { return nil, err } else if ok { return &fileMeta, nil } return nil, nil } func buildPath(path string) string { path = filepath.Clean(path) path = strings.ReplaceAll(path, "\\", "/") path = strings.TrimPrefix(path, "/") return strconv.Itoa(strings.Count(path, "/")) + ":" + path } func newDbFile(ctx context.Context, path string) (*file, error) { path = buildPath(path) f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize} if _, err := f.loadMetaByPath(); err != nil { return nil, err } return f, nil }
-
-
models/dbfs/dbfs.go (new)
-
@@ -0,0 +1,102 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package dbfs import ( "context" "os" "code.gitea.io/gitea/models/db" ) /* The reasons behind the DBFS (database-filesystem) package: When a Gitea action is running, the Gitea action server should collect and store all the logs. The requirements are: * The running logs must be stored across the cluster if the Gitea servers are deployed as a cluster. * The logs will be archived to Object Storage (S3/MinIO, etc.) after a period of time. * The Gitea action UI should be able to render the running logs and the archived logs. Some possible solutions for the running logs: * [Not ideal] Using local temp file: it can not be shared across the cluster. * [Not ideal] Using shared file in the filesystem of git repository: although at the moment, the Gitea cluster's git repositories must be stored in a shared filesystem, in the future, Gitea may need a dedicated Git Service Server to decouple the shared filesystem. Then the action logs will become a blocker. * [Not ideal] Record the logs in a database table line by line: it has a couple of problems: - It's difficult to make multiple increasing sequence (log line number) for different databases. - The database table will have a lot of rows and be affected by the big-table performance problem. - It's difficult to load logs by using the same interface as other storages. - It's difficult to calculate the size of the logs. The DBFS solution: * It can be used in a cluster. * It can share the same interface (Read/Write/Seek) as other storages. * It's very friendly to database because it only needs to store much fewer rows than the log-line solution. * In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size. * Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek. The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much. */ type dbfsMeta struct { ID int64 `xorm:"pk autoincr"` FullPath string `xorm:"VARCHAR(500) UNIQUE NOT NULL"` BlockSize int64 `xorm:"BIGINT NOT NULL"` FileSize int64 `xorm:"BIGINT NOT NULL"` CreateTimestamp int64 `xorm:"BIGINT NOT NULL"` ModifyTimestamp int64 `xorm:"BIGINT NOT NULL"` } type dbfsData struct { ID int64 `xorm:"pk autoincr"` Revision int64 `xorm:"BIGINT NOT NULL"` MetaID int64 `xorm:"BIGINT index(meta_offset) NOT NULL"` BlobOffset int64 `xorm:"BIGINT index(meta_offset) NOT NULL"` BlobSize int64 `xorm:"BIGINT NOT NULL"` BlobData []byte `xorm:"BLOB NOT NULL"` } func init() { db.RegisterModel(new(dbfsMeta)) db.RegisterModel(new(dbfsData)) } func OpenFile(ctx context.Context, name string, flag int) (File, error) { f, err := newDbFile(ctx, name) if err != nil { return nil, err } err = f.open(flag) if err != nil { _ = f.Close() return nil, err } return f, nil } func Open(ctx context.Context, name string) (File, error) { return OpenFile(ctx, name, os.O_RDONLY) } func Create(ctx context.Context, name string) (File, error) { return OpenFile(ctx, name, os.O_RDWR|os.O_CREATE|os.O_TRUNC) } func Rename(ctx context.Context, oldPath, newPath string) error { f, err := newDbFile(ctx, oldPath) if err != nil { return err } defer f.Close() return f.renameTo(newPath) } func Remove(ctx context.Context, name string) error { f, err := newDbFile(ctx, name) if err != nil { return err } defer f.Close() return f.delete() }
-
-
models/dbfs/dbfs_test.go (new)
-
@@ -0,0 +1,179 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package dbfs import ( "bufio" "io" "os" "testing" "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" _ "github.com/mattn/go-sqlite3" ) func changeDefaultFileBlockSize(n int64) (restore func()) { old := defaultFileBlockSize defaultFileBlockSize = n return func() { defaultFileBlockSize = old } } func TestDbfsBasic(t *testing.T) { defer changeDefaultFileBlockSize(4)() // test basic write/read f, err := OpenFile(db.DefaultContext, "test.txt", os.O_RDWR|os.O_CREATE) assert.NoError(t, err) n, err := f.Write([]byte("0123456789")) // blocks: 0123 4567 89 assert.NoError(t, err) assert.EqualValues(t, 10, n) _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err := io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, 10, n) assert.EqualValues(t, "0123456789", string(buf)) // write some new data _, err = f.Seek(1, io.SeekStart) assert.NoError(t, err) _, err = f.Write([]byte("bcdefghi")) // blocks: 0bcd efgh i9 assert.NoError(t, err) // read from offset buf, err = io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, "9", string(buf)) // read all _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, "0bcdefghi9", string(buf)) // write to new size _, err = f.Seek(-1, io.SeekEnd) assert.NoError(t, err) _, err = f.Write([]byte("JKLMNOP")) // blocks: 0bcd efgh iJKL MNOP assert.NoError(t, err) _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, "0bcdefghiJKLMNOP", string(buf)) // write beyond EOF and fill with zero _, err = f.Seek(5, io.SeekCurrent) assert.NoError(t, err) _, err = f.Write([]byte("xyzu")) // blocks: 0bcd efgh iJKL MNOP 0000 0xyz u assert.NoError(t, err) _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu", string(buf)) // write to the block with zeros _, err = f.Seek(-6, io.SeekCurrent) assert.NoError(t, err) _, err = f.Write([]byte("ABCD")) // blocks: 0bcd efgh iJKL MNOP 000A BCDz u assert.NoError(t, err) _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) assert.EqualValues(t, "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu", string(buf)) assert.NoError(t, f.Close()) // test rename err = Rename(db.DefaultContext, "test.txt", "test2.txt") assert.NoError(t, err) _, err = OpenFile(db.DefaultContext, "test.txt", os.O_RDONLY) assert.Error(t, err) f, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY) assert.NoError(t, err) assert.NoError(t, f.Close()) // test remove err = Remove(db.DefaultContext, "test2.txt") assert.NoError(t, err) _, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY) assert.Error(t, err) } func TestDbfsReadWrite(t *testing.T) { defer changeDefaultFileBlockSize(4)() f1, err := OpenFile(db.DefaultContext, "test.log", os.O_RDWR|os.O_CREATE) assert.NoError(t, err) defer f1.Close() f2, err := OpenFile(db.DefaultContext, "test.log", os.O_RDONLY) assert.NoError(t, err) defer f2.Close() _, err = f1.Write([]byte("line 1\n")) assert.NoError(t, err) f2r := bufio.NewReader(f2) line, err := f2r.ReadString('\n') assert.NoError(t, err) assert.EqualValues(t, "line 1\n", line) _, err = f2r.ReadString('\n') assert.ErrorIs(t, err, io.EOF) _, err = f1.Write([]byte("line 2\n")) assert.NoError(t, err) line, err = f2r.ReadString('\n') assert.NoError(t, err) assert.EqualValues(t, "line 2\n", line) _, err = f2r.ReadString('\n') assert.ErrorIs(t, err, io.EOF) } func TestDbfsSeekWrite(t *testing.T) { defer changeDefaultFileBlockSize(4)() f, err := OpenFile(db.DefaultContext, "test2.log", os.O_RDWR|os.O_CREATE) assert.NoError(t, err) defer f.Close() n, err := f.Write([]byte("111")) assert.NoError(t, err) _, err = f.Seek(int64(n), io.SeekStart) assert.NoError(t, err) _, err = f.Write([]byte("222")) assert.NoError(t, err) _, err = f.Seek(int64(n), io.SeekStart) assert.NoError(t, err) _, err = f.Write([]byte("333")) assert.NoError(t, err) fr, err := OpenFile(db.DefaultContext, "test2.log", os.O_RDONLY) assert.NoError(t, err) defer f.Close() buf, err := io.ReadAll(fr) assert.NoError(t, err) assert.EqualValues(t, "111333", string(buf)) }
-
-
models/dbfs/main_test.go (new)
-
@@ -0,0 +1,23 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package dbfs import ( "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" ) func init() { setting.SetCustomPathAndConf("", "", "") setting.LoadForTest() } func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", ".."), }) }
-
-
-
@@ -4,6 +4,7 @@ owner_id: 2owner_name: user2 lower_name: repo1 name: repo1 default_branch: master num_watches: 4 num_stars: 0 num_forks: 0
-
@@ -34,6 +35,7 @@ owner_id: 2owner_name: user2 lower_name: repo2 name: repo2 default_branch: master num_watches: 0 num_stars: 1 num_forks: 0
-
@@ -64,6 +66,7 @@ owner_id: 3owner_name: user3 lower_name: repo3 name: repo3 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -94,6 +97,7 @@ owner_id: 5owner_name: user5 lower_name: repo4 name: repo4 default_branch: master num_watches: 0 num_stars: 1 num_forks: 0
-
@@ -274,6 +278,7 @@ owner_id: 12owner_name: user12 lower_name: repo10 name: repo10 default_branch: master num_watches: 0 num_stars: 0 num_forks: 1
-
@@ -304,6 +309,7 @@ owner_id: 13owner_name: user13 lower_name: repo11 name: repo11 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -425,6 +431,7 @@ owner_id: 2owner_name: user2 lower_name: repo15 name: repo15 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -455,6 +462,7 @@ owner_id: 2owner_name: user2 lower_name: repo16 name: repo16 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -905,6 +913,7 @@ owner_id: 2owner_name: user2 lower_name: repo20 name: repo20 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -965,6 +974,7 @@ owner_id: 2owner_name: user2 lower_name: utf8 name: utf8 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1055,6 +1065,7 @@ owner_id: 2owner_name: user2 lower_name: commits_search_test name: commits_search_test default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1085,6 +1096,7 @@ owner_id: 2owner_name: user2 lower_name: git_hooks_test name: git_hooks_test default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1115,6 +1127,7 @@ owner_id: 22owner_name: limited_org lower_name: public_repo_on_limited_org name: public_repo_on_limited_org default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1145,6 +1158,7 @@ owner_id: 22owner_name: limited_org lower_name: private_repo_on_limited_org name: private_repo_on_limited_org default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1175,6 +1189,7 @@ owner_id: 23owner_name: privated_org lower_name: public_repo_on_private_org name: public_repo_on_private_org default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1205,6 +1220,7 @@ owner_id: 23owner_name: privated_org lower_name: private_repo_on_private_org name: private_repo_on_private_org default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1235,6 +1251,7 @@ owner_id: 2owner_name: user2 lower_name: glob name: glob default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1295,6 +1312,7 @@ owner_id: 27owner_name: user27 lower_name: template1 name: template1 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1355,6 +1373,7 @@ owner_id: 26owner_name: org26 lower_name: repo_external_tracker name: repo_external_tracker default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1385,6 +1404,7 @@ owner_id: 26owner_name: org26 lower_name: repo_external_tracker_numeric name: repo_external_tracker_numeric default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1415,6 +1435,7 @@ owner_id: 26owner_name: org26 lower_name: repo_external_tracker_alpha name: repo_external_tracker_alpha default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1445,6 +1466,7 @@ owner_id: 27owner_name: user27 lower_name: repo49 name: repo49 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1475,6 +1497,7 @@ owner_id: 30owner_name: user30 lower_name: repo50 name: repo50 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1505,6 +1528,7 @@ owner_id: 30owner_name: user30 lower_name: repo51 name: repo51 default_branch: master num_watches: 0 num_stars: 0 num_forks: 0
-
@@ -1565,6 +1589,7 @@ owner_id: 30owner_name: user30 lower_name: renderer name: renderer default_branch: master is_archived: false is_empty: false is_private: false
-
@@ -1592,6 +1617,7 @@ owner_id: 2owner_name: user2 lower_name: lfs name: lfs default_branch: master is_empty: false is_archived: false is_private: true
-
-
-
@@ -140,3 +140,14 @@ num_repos: 0num_members: 1 includes_all_repositories: false can_create_org_repo: false - id: 14 org_id: 3 lower_name: teamcreaterepo name: teamCreateRepo authorize: 2 # write num_repos: 0 num_members: 1 includes_all_repositories: false can_create_org_repo: true
-
-
-
@@ -93,3 +93,9 @@ id: 16org_id: 19 team_id: 6 uid: 31 - id: 17 org_id: 3 team_id: 14 uid: 2
-
-
-
@@ -104,7 +104,7 @@ num_followers: 0num_following: 0 num_stars: 0 num_repos: 3 num_teams: 4 num_teams: 5 num_members: 3 visibility: 0 repo_admin_change_team_access: false
-
-
-
@@ -357,7 +357,7 @@ if c.PosterID <= 0 || c.Poster != nil {return nil } c.Poster, err = user_model.GetUserByID(ctx, c.PosterID) c.Poster, err = user_model.GetPossibleUserByID(ctx, c.PosterID) if err != nil { if user_model.IsErrUserNotExist(err) { c.PosterID = -1
-
-
-
@@ -29,32 +29,13 @@ if len(comments) == 0 {return nil } posterIDs := comments.getPosterIDs() posterMaps := make(map[int64]*user_model.User, len(posterIDs)) left := len(posterIDs) for left > 0 { limit := db.DefaultMaxInSize if left < limit { limit = left } err := db.GetEngine(ctx). In("id", posterIDs[:limit]). Find(&posterMaps) if err != nil { return err } left -= limit posterIDs = posterIDs[limit:] posterMaps, err := getPosters(ctx, comments.getPosterIDs()) if err != nil { return err } for _, comment := range comments { if comment.PosterID <= 0 { continue } var ok bool if comment.Poster, ok = posterMaps[comment.PosterID]; !ok { comment.Poster = user_model.NewGhostUser() } comment.Poster = getPoster(comment.PosterID, posterMaps) } return nil }
-
-
-
@@ -237,7 +237,7 @@// LoadPoster loads poster func (issue *Issue) LoadPoster(ctx context.Context) (err error) { if issue.Poster == nil { issue.Poster, err = user_model.GetUserByID(ctx, issue.PosterID) issue.Poster, err = user_model.GetPossibleUserByID(ctx, issue.PosterID) if err != nil { issue.PosterID = -1 issue.Poster = user_model.NewGhostUser()
-
@@ -1253,6 +1253,8 @@if opts.ProjectID > 0 { sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). And("project_issue.project_id=?", opts.ProjectID) } else if opts.ProjectID == db.NoneID { // show those that are in no project sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue"))) } if opts.ProjectBoardID != 0 {
-
@@ -1574,6 +1576,7 @@ type IssueStatsOptions struct {RepoID int64 Labels string MilestoneID int64 ProjectID int64 AssigneeID int64 MentionedID int64 PosterID int64
-
@@ -1650,6 +1653,11 @@ }if opts.MilestoneID > 0 { sess.And("issue.milestone_id = ?", opts.MilestoneID) } if opts.ProjectID > 0 { sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). And("project_issue.project_id=?", opts.ProjectID) } if opts.AssigneeID > 0 {
-
-
-
@@ -86,7 +86,18 @@ if len(issues) == 0 {return nil } posterIDs := issues.getPosterIDs() posterMaps, err := getPosters(ctx, issues.getPosterIDs()) if err != nil { return err } for _, issue := range issues { issue.Poster = getPoster(issue.PosterID, posterMaps) } return nil } func getPosters(ctx context.Context, posterIDs []int64) (map[int64]*user_model.User, error) { posterMaps := make(map[int64]*user_model.User, len(posterIDs)) left := len(posterIDs) for left > 0 {
-
@@ -98,22 +109,26 @@ err := db.GetEngine(ctx).In("id", posterIDs[:limit]). Find(&posterMaps) if err != nil { return err return nil, err } left -= limit posterIDs = posterIDs[limit:] } return posterMaps, nil } for _, issue := range issues { if issue.PosterID <= 0 { continue } var ok bool if issue.Poster, ok = posterMaps[issue.PosterID]; !ok { issue.Poster = user_model.NewGhostUser() } func getPoster(posterID int64, posterMaps map[int64]*user_model.User) *user_model.User { if posterID == user_model.ActionsUserID { return user_model.NewActionsUser() } return nil if posterID <= 0 { return nil } poster, ok := posterMaps[posterID] if !ok { return user_model.NewGhostUser() } return poster } func (issues IssueList) getIssueIDs() []int64 {
-
-
-
@@ -8,6 +8,7 @@ import ("context" "fmt" "io" "strconv" "strings" "code.gitea.io/gitea/models/db"
-
@@ -132,6 +133,27 @@ PullRequestStatusEmptyPullRequestStatusAncestor ) func (status PullRequestStatus) String() string { switch status { case PullRequestStatusConflict: return "CONFLICT" case PullRequestStatusChecking: return "CHECKING" case PullRequestStatusMergeable: return "MERGEABLE" case PullRequestStatusManuallyMerged: return "MANUALLY_MERGED" case PullRequestStatusError: return "ERROR" case PullRequestStatusEmpty: return "EMPTY" case PullRequestStatusAncestor: return "ANCESTOR" default: return strconv.Itoa(int(status)) } } // PullRequestFlow the flow of pull request type PullRequestFlow int
-
@@ -202,6 +224,42 @@ _, err := db.DeleteByBean(ctx, &PullRequest{BaseRepoID: repoID})return err } // ColorFormat writes a colored string to identify this struct func (pr *PullRequest) ColorFormat(s fmt.State) { if pr == nil { log.ColorFprintf(s, "PR[%d]%s#%d[%s...%s:%s]", log.NewColoredIDValue(0), log.NewColoredValue("<nil>/<nil>"), log.NewColoredIDValue(0), log.NewColoredValue("<nil>"), log.NewColoredValue("<nil>/<nil>"), log.NewColoredValue("<nil>"), ) return } log.ColorFprintf(s, "PR[%d]", log.NewColoredIDValue(pr.ID)) if pr.BaseRepo != nil { log.ColorFprintf(s, "%s#%d[%s...", log.NewColoredValue(pr.BaseRepo.FullName()), log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch)) } else { log.ColorFprintf(s, "Repo[%d]#%d[%s...", log.NewColoredIDValue(pr.BaseRepoID), log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch)) } if pr.HeadRepoID == pr.BaseRepoID { log.ColorFprintf(s, "%s]", log.NewColoredValue(pr.HeadBranch)) } else if pr.HeadRepo != nil { log.ColorFprintf(s, "%s:%s]", log.NewColoredValue(pr.HeadRepo.FullName()), log.NewColoredValue(pr.HeadBranch)) } else { log.ColorFprintf(s, "Repo[%d]:%s]", log.NewColoredIDValue(pr.HeadRepoID), log.NewColoredValue(pr.HeadBranch)) } } // String represents the pr as a simple string func (pr *PullRequest) String() string { return log.ColorFormatAsString(pr) } // MustHeadUserName returns the HeadRepo's username if failed return blank func (pr *PullRequest) MustHeadUserName(ctx context.Context) string { if err := pr.LoadHeadRepo(ctx); err != nil {
-
@@ -234,7 +292,8 @@return nil } // LoadHeadRepo loads the head repository // LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist // and thus ErrRepoNotExist will never be returned func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) { if !pr.isHeadRepoLoaded && pr.HeadRepo == nil && pr.HeadRepoID > 0 { if pr.HeadRepoID == pr.BaseRepoID {
-
@@ -249,14 +308,14 @@ }pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID) if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work return fmt.Errorf("GetRepositoryByID(head): %w", err) return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err) } pr.isHeadRepoLoaded = true } return nil } // LoadBaseRepo loads the target repository // LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned. func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) { if pr.BaseRepo != nil { return nil
-
@@ -274,7 +333,7 @@ }pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID) if err != nil { return fmt.Errorf("repo_model.GetRepositoryByID(base): %w", err) return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err) } return nil }
-
@@ -392,6 +451,11 @@// IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit func (pr *PullRequest) IsAncestor() bool { return pr.Status == PullRequestStatusAncestor } // IsFromFork return true if this PR is from a fork. func (pr *PullRequest) IsFromFork() bool { return pr.HeadRepoID != pr.BaseRepoID } // SetMerged sets a pull request to merged and closes the corresponding issue
-
-
-
@@ -173,8 +173,9 @@ set := make(map[int64]*Issue)for i := range issues { set[issues[i].ID] = issues[i] } for i := range prs { prs[i].Issue = set[prs[i].IssueID] for _, pr := range prs { pr.Issue = set[pr.IssueID] pr.Issue.PullRequest = pr // panic here means issueIDs and prs are not in sync } return nil }
-
-
-
@@ -158,7 +158,7 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {if r.ReviewerID == 0 || r.Reviewer != nil { return } r.Reviewer, err = user_model.GetUserByID(ctx, r.ReviewerID) r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID) return err }
-
-
-
@@ -453,6 +453,8 @@ // v238 -> v239NewMigration("Add updated unix to LFSMetaObject", v1_19.AddUpdatedUnixToLFSMetaObject), // v239 -> v240 NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens), // v240 -> v241 NewMigration("Add actions tables", v1_19.AddActionsTables), } // GetCurrentDBVersion returns the current db version
-
-
-
@@ -0,0 +1,176 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package v1_19 //nolint import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "xorm.io/xorm" ) func AddActionsTables(x *xorm.Engine) error { type ActionRunner struct { ID int64 UUID string `xorm:"CHAR(36) UNIQUE"` Name string `xorm:"VARCHAR(255)"` OwnerID int64 `xorm:"index"` // org level runner, 0 means system RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global Description string `xorm:"TEXT"` Base int // 0 native 1 docker 2 virtual machine RepoRange string // glob match which repositories could use this runner Token string `xorm:"-"` TokenHash string `xorm:"UNIQUE"` // sha256 of token TokenSalt string // TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token LastOnline timeutil.TimeStamp `xorm:"index"` LastActive timeutil.TimeStamp `xorm:"index"` // Store OS and Artch. AgentLabels []string // Store custom labes use defined. CustomLabels []string Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` Deleted timeutil.TimeStamp `xorm:"deleted"` } type ActionRunnerToken struct { ID int64 Token string `xorm:"UNIQUE"` OwnerID int64 `xorm:"index"` // org level runner, 0 means system RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global IsActive bool Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` Deleted timeutil.TimeStamp `xorm:"deleted"` } type ActionRun struct { ID int64 Title string RepoID int64 `xorm:"index unique(repo_index)"` OwnerID int64 `xorm:"index"` WorkflowID string `xorm:"index"` // the name of workflow file Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository TriggerUserID int64 Ref string CommitSHA string Event string IsForkPullRequest bool EventPayload string `xorm:"LONGTEXT"` Status int `xorm:"index"` Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } type ActionRunJob struct { ID int64 RunID int64 `xorm:"index"` RepoID int64 `xorm:"index"` OwnerID int64 `xorm:"index"` CommitSHA string `xorm:"index"` IsForkPullRequest bool Name string `xorm:"VARCHAR(255)"` Attempt int64 WorkflowPayload []byte JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id Needs []string `xorm:"JSON TEXT"` RunsOn []string `xorm:"JSON TEXT"` TaskID int64 // the latest task of the job Status int `xorm:"index"` Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated index"` } type Repository struct { NumActionRuns int `xorm:"NOT NULL DEFAULT 0"` NumClosedActionRuns int `xorm:"NOT NULL DEFAULT 0"` } type ActionRunIndex db.ResourceIndex type ActionTask struct { ID int64 JobID int64 Attempt int64 RunnerID int64 `xorm:"index"` Status int `xorm:"index"` Started timeutil.TimeStamp `xorm:"index"` Stopped timeutil.TimeStamp RepoID int64 `xorm:"index"` OwnerID int64 `xorm:"index"` CommitSHA string `xorm:"index"` IsForkPullRequest bool TokenHash string `xorm:"UNIQUE"` // sha256 of token TokenSalt string TokenLastEight string `xorm:"index token_last_eight"` LogFilename string // file name of log LogInStorage bool // read log from database or from storage LogLength int64 // lines count LogSize int64 // blob size LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset LogExpired bool // files that are too old will be deleted Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated index"` } type ActionTaskStep struct { ID int64 Name string `xorm:"VARCHAR(255)"` TaskID int64 `xorm:"index unique(task_index)"` Index int64 `xorm:"index unique(task_index)"` RepoID int64 `xorm:"index"` Status int `xorm:"index"` LogIndex int64 LogLength int64 Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } type dbfsMeta struct { ID int64 `xorm:"pk autoincr"` FullPath string `xorm:"VARCHAR(500) UNIQUE NOT NULL"` BlockSize int64 `xorm:"BIGINT NOT NULL"` FileSize int64 `xorm:"BIGINT NOT NULL"` CreateTimestamp int64 `xorm:"BIGINT NOT NULL"` ModifyTimestamp int64 `xorm:"BIGINT NOT NULL"` } type dbfsData struct { ID int64 `xorm:"pk autoincr"` Revision int64 `xorm:"BIGINT NOT NULL"` MetaID int64 `xorm:"BIGINT index(meta_offset) NOT NULL"` BlobOffset int64 `xorm:"BIGINT index(meta_offset) NOT NULL"` BlobSize int64 `xorm:"BIGINT NOT NULL"` BlobData []byte `xorm:"BLOB NOT NULL"` } return x.Sync( new(ActionRunner), new(ActionRunnerToken), new(ActionRun), new(ActionRunJob), new(Repository), new(ActionRunIndex), new(ActionTask), new(ActionTaskStep), new(dbfsMeta), new(dbfsData), ) }
-
-
-
@@ -397,13 +397,14 @@ return authorize, err} // GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) ([]*user_model.User, error) { users := make([]*user_model.User, 0, 10) func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*user_model.User, error) { // Use a map, in order to de-duplicate users. users := make(map[int64]*user_model.User) return users, db.GetEngine(ctx). Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})). And("team_user.org_id = ?", orgID).Asc("`user`.name").Find(&users) And("team_user.org_id = ?", orgID).Find(&users) } // SearchOrganizationsOptions options to filter organizations
-
-
-
@@ -91,11 +91,12 @@ assert.NoError(t, unittest.PrepareTestDatabase())org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) teams, err := org.LoadTeams() assert.NoError(t, err) if assert.Len(t, teams, 4) { if assert.Len(t, teams, 5) { assert.Equal(t, int64(1), teams[0].ID) assert.Equal(t, int64(2), teams[1].ID) assert.Equal(t, int64(12), teams[2].ID) assert.Equal(t, int64(7), teams[3].ID) assert.Equal(t, int64(14), teams[3].ID) assert.Equal(t, int64(7), teams[4].ID) } }
-
@@ -292,7 +293,7 @@ teamIDs, err := org.GetUserTeamIDs(userID)assert.NoError(t, err) assert.Equal(t, expected, teamIDs) } testSuccess(2, []int64{1, 2}) testSuccess(2, []int64{1, 2, 14}) testSuccess(4, []int64{2}) testSuccess(unittest.NonexistentID, []int64{}) }
-
@@ -447,7 +448,7 @@users, err = organization.GetUsersWhoCanCreateOrgRepo(db.DefaultContext, 7) assert.NoError(t, err) assert.Len(t, users, 1) assert.EqualValues(t, 5, users[0].ID) assert.NotNil(t, users[5]) } func TestUser_RemoveOrgRepo(t *testing.T) {
-
-
-
@@ -0,0 +1,63 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package conda import ( "context" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" conda_module "code.gitea.io/gitea/modules/packages/conda" "xorm.io/builder" ) type FileSearchOptions struct { OwnerID int64 Channel string Subdir string Filename string } // SearchFiles gets all files matching the search options func SearchFiles(ctx context.Context, opts *FileSearchOptions) ([]*packages.PackageFile, error) { var cond builder.Cond = builder.Eq{ "package.type": packages.TypeConda, "package.owner_id": opts.OwnerID, "package_version.is_internal": false, } if opts.Filename != "" { cond = cond.And(builder.Eq{ "package_file.lower_name": strings.ToLower(opts.Filename), }) } var versionPropsCond builder.Cond = builder.Eq{ "package_property.ref_type": packages.PropertyTypePackage, "package_property.name": conda_module.PropertyChannel, "package_property.value": opts.Channel, } cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(versionPropsCond).From("package_property"))) var filePropsCond builder.Cond = builder.Eq{ "package_property.ref_type": packages.PropertyTypeFile, "package_property.name": conda_module.PropertySubdir, "package_property.value": opts.Subdir, } cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(filePropsCond).From("package_property"))) sess := db.GetEngine(ctx). Select("package_file.*"). Table("package_file"). Join("INNER", "package_version", "package_version.id = package_file.version_id"). Join("INNER", "package", "package.id = package_version.package_id"). Where(cond) pfs := make([]*packages.PackageFile, 0, 10) return pfs, sess.Find(&pfs) }
-
-
-
@@ -13,6 +13,7 @@ user_model "code.gitea.io/gitea/models/user""code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/packages/composer" "code.gitea.io/gitea/modules/packages/conan" "code.gitea.io/gitea/modules/packages/conda" "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/packages/helm" "code.gitea.io/gitea/modules/packages/maven"
-
@@ -132,6 +133,8 @@ case TypeComposer:metadata = &composer.Metadata{} case TypeConan: metadata = &conan.Metadata{} case TypeConda: metadata = &conda.VersionMetadata{} case TypeContainer: metadata = &container.Metadata{} case TypeGeneric:
-
-
-
@@ -32,6 +32,7 @@ // List of supported packagesconst ( TypeComposer Type = "composer" TypeConan Type = "conan" TypeConda Type = "conda" TypeContainer Type = "container" TypeGeneric Type = "generic" TypeHelm Type = "helm"
-
@@ -47,6 +48,7 @@var TypeList = []Type{ TypeComposer, TypeConan, TypeConda, TypeContainer, TypeGeneric, TypeHelm,
-
@@ -66,6 +68,8 @@ case TypeComposer:return "Composer" case TypeConan: return "Conan" case TypeConda: return "Conda" case TypeContainer: return "Container" case TypeGeneric:
-
@@ -97,6 +101,8 @@ case TypeComposer:return "gitea-composer" case TypeConan: return "gitea-conan" case TypeConda: return "gitea-conda" case TypeContainer: return "octicon-container" case TypeGeneric:
-
-
-
@@ -11,6 +11,7 @@ "strconv"_ "image/jpeg" // Needed for jpeg support actions_model "code.gitea.io/gitea/models/actions" activities_model "code.gitea.io/gitea/models/activities" admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey"
-
@@ -26,6 +27,7 @@ system_model "code.gitea.io/gitea/models/system""code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" actions_module "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/storage"
-
@@ -51,6 +53,12 @@ return err} defer committer.Close() sess := db.GetEngine(ctx) // Query the action tasks of this repo, they will be needed after they have been deleted to remove the logs tasks, err := actions_model.FindTasks(ctx, actions_model.FindTaskOptions{RepoID: repoID}) if err != nil { return fmt.Errorf("find actions tasks of repo %v: %w", repoID, err) } // In case is a organization. org, err := user_model.GetUserByID(ctx, uid)
-
@@ -152,6 +160,11 @@ &admin_model.Task{RepoID: repoID},&repo_model.Watch{RepoID: repoID}, &webhook.Webhook{RepoID: repoID}, &secret_model.Secret{RepoID: repoID}, &actions_model.ActionTaskStep{RepoID: repoID}, &actions_model.ActionTask{RepoID: repoID}, &actions_model.ActionRunJob{RepoID: repoID}, &actions_model.ActionRun{RepoID: repoID}, &actions_model.ActionRunner{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) }
-
@@ -312,6 +325,15 @@if len(repo.Avatar) > 0 { if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil { return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err) } } // Finally, delete action logs after the actions have already been deleted to avoid new log files for _, task := range tasks { err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename) if err != nil { log.Error("remove log file %q: %v", task.LogFilename, err) // go on } }
-
-
-
@@ -142,6 +142,9 @@ NumOpenMilestones int `xorm:"-"`NumProjects int `xorm:"NOT NULL DEFAULT 0"` NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"` NumOpenProjects int `xorm:"-"` NumActionRuns int `xorm:"NOT NULL DEFAULT 0"` NumClosedActionRuns int `xorm:"NOT NULL DEFAULT 0"` NumOpenActionRuns int `xorm:"-"` IsPrivate bool `xorm:"INDEX"` IsEmpty bool `xorm:"INDEX"`
-
@@ -225,15 +228,11 @@ }// AfterLoad is invoked from XORM after setting the values of all fields of this object. func (repo *Repository) AfterLoad() { // FIXME: use models migration to solve all at once. if len(repo.DefaultBranch) == 0 { repo.DefaultBranch = setting.Repository.DefaultBranch } repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns } // LoadAttributes loads attributes of the repository.
-
-
-
@@ -494,7 +494,7 @@ cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))} if opts.OnlyShowRelevant { // Only show a repo that either has a topic or description. // Only show a repo that has at least a topic, an icon, or a description subQueryCond := builder.NewCond() // Topic checking. Topics are present.
-
@@ -504,13 +504,13 @@ } else {subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"})) } // Description checking. Description not empty. // Description checking. Description not empty subQueryCond = subQueryCond.Or(builder.Neq{"description": ""}) // Repo has a avatar. // Repo has a avatar subQueryCond = subQueryCond.Or(builder.Neq{"avatar": ""}) // Always hide repo's that are empty. // Always hide repo's that are empty subQueryCond = subQueryCond.And(builder.Eq{"is_empty": false}) cond = cond.And(subQueryCond)
-
-
-
@@ -174,7 +174,7 @@ case unit.TypePullRequests:r.Config = new(PullRequestsConfig) case unit.TypeIssues: r.Config = new(IssuesConfig) case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages: case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages, unit.TypeActions: fallthrough default: r.Config = new(UnitConfig)
-
-
-
@@ -27,6 +27,7 @@ TypeExternalWiki // 6 ExternalWikiTypeExternalTracker // 7 ExternalTracker TypeProjects // 8 Kanban board TypePackages // 9 Packages TypeActions // 10 Actions ) // Value returns integer value for unit type
-
@@ -54,6 +55,8 @@ case TypeProjects:return "TypeProjects" case TypePackages: return "TypePackages" case TypeActions: return "TypeActions" } return fmt.Sprintf("Unknown Type %d", u) }
-
@@ -77,6 +80,7 @@ TypeExternalWiki,TypeExternalTracker, TypeProjects, TypePackages, TypeActions, } // DefaultRepoUnits contains the default unit types
-
@@ -90,6 +94,12 @@ TypeProjects,TypePackages, } // ForkRepoUnits contains the default unit types for forks DefaultForkRepoUnits = []Type{ TypeCode, TypePullRequests, } // NotAllowedDefaultRepoUnits contains units that can't be default NotAllowedDefaultRepoUnits = []Type{ TypeExternalWiki,
-
@@ -106,26 +116,41 @@ // DisabledRepoUnits contains the units that have been globally disabledDisabledRepoUnits = []Type{} ) // LoadUnitConfig load units from settings func LoadUnitConfig() { setDefaultRepoUnits := FindUnitTypes(setting.Repository.DefaultRepoUnits...) // Default repo units set if setting is not empty if len(setDefaultRepoUnits) > 0 { // Get valid set of default repository units from settings func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type { units := defaultUnits // Use setting if not empty if len(settingDefaultUnits) > 0 { // MustRepoUnits required as default DefaultRepoUnits = make([]Type, len(MustRepoUnits)) copy(DefaultRepoUnits, MustRepoUnits) for _, defaultU := range setDefaultRepoUnits { if !defaultU.CanBeDefault() { log.Warn("Not allowed as default unit: %s", defaultU.String()) units = make([]Type, len(MustRepoUnits)) copy(units, MustRepoUnits) for _, settingUnit := range settingDefaultUnits { if !settingUnit.CanBeDefault() { log.Warn("Not allowed as default unit: %s", settingUnit.String()) continue } // MustRepoUnits already added if defaultU.CanDisable() { DefaultRepoUnits = append(DefaultRepoUnits, defaultU) if settingUnit.CanDisable() { units = append(units, settingUnit) } } } // Remove disabled units for _, disabledUnit := range DisabledRepoUnits { for i, unit := range units { if unit == disabledUnit { units = append(units[:i], units[i+1:]...) } } } return units } // LoadUnitConfig load units from settings func LoadUnitConfig() { DisabledRepoUnits = FindUnitTypes(setting.Repository.DisabledRepoUnits...) // Check that must units are not disabled for i, disabledU := range DisabledRepoUnits {
-
@@ -134,14 +159,11 @@ log.Warn("Not allowed to global disable unit %s", disabledU.String())DisabledRepoUnits = append(DisabledRepoUnits[:i], DisabledRepoUnits[i+1:]...) } } // Remove disabled units from default units for _, disabledU := range DisabledRepoUnits { for i, defaultU := range DefaultRepoUnits { if defaultU == disabledU { DefaultRepoUnits = append(DefaultRepoUnits[:i], DefaultRepoUnits[i+1:]...) } } } setDefaultRepoUnits := FindUnitTypes(setting.Repository.DefaultRepoUnits...) DefaultRepoUnits = validateDefaultRepoUnits(DefaultRepoUnits, setDefaultRepoUnits) setDefaultForkRepoUnits := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...) DefaultForkRepoUnits = validateDefaultRepoUnits(DefaultForkRepoUnits, setDefaultForkRepoUnits) } // UnitGlobalDisabled checks if unit type is global disabled
-
@@ -288,6 +310,15 @@ 6,perm.AccessModeRead, } UnitActions = Unit{ TypeActions, "actions.actions", "/actions", "actions.unit.desc", 7, perm.AccessModeOwner, } // Units contains all the units Units = map[Type]Unit{ TypeCode: UnitCode,
-
@@ -299,6 +330,7 @@ TypeWiki: UnitWiki,TypeExternalWiki: UnitExternalWiki, TypeProjects: UnitProjects, TypePackages: UnitPackages, TypeActions: UnitActions, } )
-
-
-
@@ -104,6 +104,8 @@ setting.RepoArchive.Storage.Path = filepath.Join(setting.AppDataPath, "repo-archive")setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") setting.Actions.Storage.Path = filepath.Join(setting.AppDataPath, "actions_log") setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home") setting.IncomingEmail.ReplyToAddress = "incoming+%{token}@localhost"
-
-
-
@@ -560,32 +560,6 @@ // Returns a 32 bytes long string.return hex.EncodeToString(rBytes), nil } // NewGhostUser creates and returns a fake user for someone has deleted their account. func NewGhostUser() *User { return &User{ ID: -1, Name: "Ghost", LowerName: "ghost", } } // NewReplaceUser creates and returns a fake user for external user func NewReplaceUser(name string) *User { return &User{ ID: -1, Name: name, LowerName: strings.ToLower(name), } } // IsGhost check if user is fake user for a deleted account func (u *User) IsGhost() bool { if u == nil { return false } return u.ID == -1 && u.Name == "Ghost" } var ( reservedUsernames = []string{ ".",
-
@@ -623,6 +597,7 @@ "ssh_info","swagger.v1.json", "user", "v2", "gitea-actions", } reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom"}
-
@@ -1012,6 +987,20 @@ } else if !has {return nil, ErrUserNotExist{id, "", 0} } return u, nil } // GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0 func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) { switch id { case -1: return NewGhostUser(), nil case ActionsUserID: return NewActionsUser(), nil case 0: return nil, ErrUserNotExist{} default: return GetUserByID(ctx, id) } } // GetUserByNameCtx returns user by given name.
-
-
-
@@ -0,0 +1,64 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package user import ( "strings" "code.gitea.io/gitea/modules/structs" ) // NewGhostUser creates and returns a fake user for someone has deleted their account. func NewGhostUser() *User { return &User{ ID: -1, Name: "Ghost", LowerName: "ghost", } } // IsGhost check if user is fake user for a deleted account func (u *User) IsGhost() bool { if u == nil { return false } return u.ID == -1 && u.Name == "Ghost" } // NewReplaceUser creates and returns a fake user for external user func NewReplaceUser(name string) *User { return &User{ ID: -1, Name: name, LowerName: strings.ToLower(name), } } const ( ActionsUserID = -2 ActionsUserName = "gitea-actions" ActionsFullName = "Gitea Actions" ActionsEmail = "teabot@gitea.io" ) // NewActionsUser creates and returns a fake user for running the actions. func NewActionsUser() *User { return &User{ ID: ActionsUserID, Name: ActionsUserName, LowerName: ActionsUserName, IsActive: true, FullName: ActionsFullName, Email: ActionsEmail, KeepEmailPrivate: true, LoginName: ActionsUserName, Type: UserTypeIndividual, AllowCreateOrganization: true, Visibility: structs.VisibleTypePublic, } } func (u *User) IsActions() bool { return u != nil && u.ID == ActionsUserID }
-
-
-
@@ -463,41 +463,6 @@ func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) {return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Webhook{}) } // GetDefaultWebhooks returns all admin-default webhooks. func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). Find(&webhooks) } // GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { webhook := &Webhook{ID: id} has, err := db.GetEngine(db.DefaultContext). Where("repo_id=? AND org_id=?", 0, 0). Get(webhook) if err != nil { return nil, err } else if !has { return nil, ErrWebhookNotExist{ID: id} } return webhook, nil } // GetSystemWebhooks returns all admin system webhooks. func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) if isActive.IsNone() { return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). Find(&webhooks) } return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). Find(&webhooks) } // UpdateWebhook updates information of webhook. func UpdateWebhook(w *Webhook) error { _, err := db.GetEngine(db.DefaultContext).ID(w.ID).AllCols().Update(w)
-
@@ -545,44 +510,3 @@ ID: id,OrgID: orgID, }) } // DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) func DeleteDefaultSystemWebhook(id int64) error { ctx, committer, err := db.TxContext(db.DefaultContext) if err != nil { return err } defer committer.Close() count, err := db.GetEngine(ctx). Where("repo_id=? AND org_id=?", 0, 0). Delete(&Webhook{ID: id}) if err != nil { return err } else if count == 0 { return ErrWebhookNotExist{ID: id} } if _, err := db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil { return err } return committer.Commit() } // CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error { ws, err := GetDefaultWebhooks(ctx) if err != nil { return fmt.Errorf("GetDefaultWebhooks: %w", err) } for _, w := range ws { w.ID = 0 w.RepoID = repoID if err := CreateWebhook(ctx, w); err != nil { return fmt.Errorf("CreateWebhook: %w", err) } } return nil }
-
-
-
@@ -0,0 +1,81 @@// Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package webhook import ( "context" "fmt" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" ) // GetDefaultWebhooks returns all admin-default webhooks. func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). Find(&webhooks) } // GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) { webhook := &Webhook{ID: id} has, err := db.GetEngine(ctx). Where("repo_id=? AND org_id=?", 0, 0). Get(webhook) if err != nil { return nil, err } else if !has { return nil, ErrWebhookNotExist{ID: id} } return webhook, nil } // GetSystemWebhooks returns all admin system webhooks. func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) if isActive.IsNone() { return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). Find(&webhooks) } return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). Find(&webhooks) } // DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error { return db.WithTx(ctx, func(ctx context.Context) error { count, err := db.GetEngine(ctx). Where("repo_id=? AND org_id=?", 0, 0). Delete(&Webhook{ID: id}) if err != nil { return err } else if count == 0 { return ErrWebhookNotExist{ID: id} } _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}) return err }) } // CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error { ws, err := GetDefaultWebhooks(ctx) if err != nil { return fmt.Errorf("GetDefaultWebhooks: %v", err) } for _, w := range ws { w.ID = 0 w.RepoID = repoID if err := CreateWebhook(ctx, w); err != nil { return fmt.Errorf("CreateWebhook: %v", err) } } return nil }
-
-
modules/actions/log.go (new)
-
@@ -0,0 +1,163 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "bufio" "context" "fmt" "io" "os" "strings" "time" "code.gitea.io/gitea/models/dbfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/storage" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "google.golang.org/protobuf/types/known/timestamppb" ) const ( MaxLineSize = 64 * 1024 DBFSPrefix = "actions_log/" timeFormat = "2006-01-02T15:04:05.0000000Z07:00" defaultBufSize = MaxLineSize ) func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) { name := DBFSPrefix + filename f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE) if err != nil { return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err) } defer f.Close() if _, err := f.Seek(offset, io.SeekStart); err != nil { return nil, fmt.Errorf("dbfs Seek %q: %w", name, err) } writer := bufio.NewWriterSize(f, defaultBufSize) ns := make([]int, 0, len(rows)) for _, row := range rows { n, err := writer.WriteString(FormatLog(row.Time.AsTime(), row.Content) + "\n") if err != nil { return nil, err } ns = append(ns, n) } if err := writer.Flush(); err != nil { return nil, err } return ns, nil } func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) { f, err := openLogs(ctx, inStorage, filename) if err != nil { return nil, err } defer f.Close() if _, err := f.Seek(offset, io.SeekStart); err != nil { return nil, fmt.Errorf("file seek: %w", err) } scanner := bufio.NewScanner(f) maxLineSize := len(timeFormat) + MaxLineSize + 1 scanner.Buffer(make([]byte, maxLineSize), maxLineSize) var rows []*runnerv1.LogRow for scanner.Scan() && (int64(len(rows)) < limit || limit < 0) { t, c, err := ParseLog(scanner.Text()) if err != nil { return nil, fmt.Errorf("parse log %q: %w", scanner.Text(), err) } rows = append(rows, &runnerv1.LogRow{ Time: timestamppb.New(t), Content: c, }) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("scan: %w", err) } return rows, nil } func TransferLogs(ctx context.Context, filename string) (func(), error) { name := DBFSPrefix + filename remove := func() { if err := dbfs.Remove(ctx, name); err != nil { log.Warn("dbfs remove %q: %v", name, err) } } f, err := dbfs.Open(ctx, name) if err != nil { return nil, fmt.Errorf("dbfs open %q: %w", name, err) } defer f.Close() if _, err := storage.Actions.Save(filename, f, -1); err != nil { return nil, fmt.Errorf("storage save %q: %w", filename, err) } return remove, nil } func RemoveLogs(ctx context.Context, inStorage bool, filename string) error { if !inStorage { name := DBFSPrefix + filename err := dbfs.Remove(ctx, name) if err != nil { return fmt.Errorf("dbfs remove %q: %w", name, err) } return nil } err := storage.Actions.Delete(filename) if err != nil { return fmt.Errorf("storage delete %q: %w", filename, err) } return nil } func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { if !inStorage { name := DBFSPrefix + filename f, err := dbfs.Open(ctx, name) if err != nil { return nil, fmt.Errorf("dbfs open %q: %w", name, err) } return f, nil } f, err := storage.Actions.Open(filename) if err != nil { return nil, fmt.Errorf("storage open %q: %w", filename, err) } return f, nil } func FormatLog(timestamp time.Time, content string) string { // Content shouldn't contain new line, it will break log indexes, other control chars are safe. content = strings.ReplaceAll(content, "\n", `\n`) if len(content) > MaxLineSize { content = content[:MaxLineSize] } return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), content) } func ParseLog(in string) (time.Time, string, error) { index := strings.IndexRune(in, ' ') if index < 0 { return time.Time{}, "", fmt.Errorf("invalid log: %q", in) } timestamp, err := time.Parse(timeFormat, in[:index]) if err != nil { return time.Time{}, "", err } return timestamp, in[index+1:], nil }
-
-
-
@@ -0,0 +1,101 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( actions_model "code.gitea.io/gitea/models/actions" ) const ( preStepName = "Set up job" postStepName = "Complete job" ) // FullSteps returns steps with "Set up job" and "Complete job" func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { if len(task.Steps) == 0 { return fullStepsOfEmptySteps(task) } firstStep := task.Steps[0] var logIndex int64 preStep := &actions_model.ActionTaskStep{ Name: preStepName, LogLength: task.LogLength, Started: task.Started, Status: actions_model.StatusRunning, } if firstStep.Status.HasRun() || firstStep.Status.IsRunning() { preStep.LogLength = firstStep.LogIndex preStep.Stopped = firstStep.Started preStep.Status = actions_model.StatusSuccess } else if task.Status.IsDone() { preStep.Stopped = task.Stopped preStep.Status = actions_model.StatusFailure } logIndex += preStep.LogLength var lastHasRunStep *actions_model.ActionTaskStep for _, step := range task.Steps { if step.Status.HasRun() { lastHasRunStep = step } logIndex += step.LogLength } if lastHasRunStep == nil { lastHasRunStep = preStep } postStep := &actions_model.ActionTaskStep{ Name: postStepName, Status: actions_model.StatusWaiting, } if task.Status.IsDone() { postStep.LogIndex = logIndex postStep.LogLength = task.LogLength - postStep.LogIndex postStep.Status = task.Status postStep.Started = lastHasRunStep.Stopped postStep.Stopped = task.Stopped } ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2) ret = append(ret, preStep) ret = append(ret, task.Steps...) ret = append(ret, postStep) return ret } func fullStepsOfEmptySteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { preStep := &actions_model.ActionTaskStep{ Name: preStepName, LogLength: task.LogLength, Started: task.Started, Stopped: task.Stopped, Status: actions_model.StatusRunning, } postStep := &actions_model.ActionTaskStep{ Name: postStepName, LogIndex: task.LogLength, Started: task.Stopped, Stopped: task.Stopped, Status: actions_model.StatusWaiting, } if task.Status.IsDone() { preStep.Status = task.Status if preStep.Status.IsSuccess() { postStep.Status = actions_model.StatusSuccess } else { postStep.Status = actions_model.StatusCancelled } } return []*actions_model.ActionTaskStep{ preStep, postStep, } }
-
-
-
@@ -0,0 +1,112 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "testing" actions_model "code.gitea.io/gitea/models/actions" "github.com/stretchr/testify/assert" ) func TestFullSteps(t *testing.T) { tests := []struct { name string task *actions_model.ActionTask want []*actions_model.ActionTaskStep }{ { name: "regular", task: &actions_model.ActionTask{ Steps: []*actions_model.ActionTaskStep{ {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, }, Status: actions_model.StatusSuccess, Started: 10000, Stopped: 10100, LogLength: 100, }, want: []*actions_model.ActionTaskStep{ {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100}, }, }, { name: "failed step", task: &actions_model.ActionTask{ Steps: []*actions_model.ActionTaskStep{ {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 20, Started: 10010, Stopped: 10020}, {Status: actions_model.StatusFailure, LogIndex: 30, LogLength: 60, Started: 10020, Stopped: 10090}, {Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, }, Status: actions_model.StatusFailure, Started: 10000, Stopped: 10100, LogLength: 100, }, want: []*actions_model.ActionTaskStep{ {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 20, Started: 10010, Stopped: 10020}, {Status: actions_model.StatusFailure, LogIndex: 30, LogLength: 60, Started: 10020, Stopped: 10090}, {Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, {Name: postStepName, Status: actions_model.StatusFailure, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100}, }, }, { name: "first step is running", task: &actions_model.ActionTask{ Steps: []*actions_model.ActionTaskStep{ {Status: actions_model.StatusRunning, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 0}, }, Status: actions_model.StatusRunning, Started: 10000, Stopped: 10100, LogLength: 100, }, want: []*actions_model.ActionTaskStep{ {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, {Status: actions_model.StatusRunning, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 0}, {Name: postStepName, Status: actions_model.StatusWaiting, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, }, }, { name: "first step has canceled", task: &actions_model.ActionTask{ Steps: []*actions_model.ActionTaskStep{ {Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, }, Status: actions_model.StatusFailure, Started: 10000, Stopped: 10100, LogLength: 100, }, want: []*actions_model.ActionTaskStep{ {Name: preStepName, Status: actions_model.StatusFailure, LogIndex: 0, LogLength: 100, Started: 10000, Stopped: 10100}, {Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, {Name: postStepName, Status: actions_model.StatusFailure, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100}, }, }, { name: "empty steps", task: &actions_model.ActionTask{ Steps: []*actions_model.ActionTaskStep{}, Status: actions_model.StatusSuccess, Started: 10000, Stopped: 10100, LogLength: 100, }, want: []*actions_model.ActionTaskStep{ {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 100, Started: 10000, Stopped: 10100}, {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equalf(t, tt.want, FullSteps(tt.task), "FullSteps(%v)", tt.task) }) } }
-
-
-
@@ -0,0 +1,222 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "bytes" "io" "strings" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/gobwas/glob" "github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/model" ) func ListWorkflows(commit *git.Commit) (git.Entries, error) { tree, err := commit.SubTree(".gitea/workflows") if _, ok := err.(git.ErrNotExist); ok { tree, err = commit.SubTree(".github/workflows") } if _, ok := err.(git.ErrNotExist); ok { return nil, nil } if err != nil { return nil, err } entries, err := tree.ListEntriesRecursiveFast() if err != nil { return nil, err } ret := make(git.Entries, 0, len(entries)) for _, entry := range entries { if strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml") { ret = append(ret, entry) } } return ret, nil } func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader) (map[string][]byte, error) { entries, err := ListWorkflows(commit) if err != nil { return nil, err } workflows := make(map[string][]byte, len(entries)) for _, entry := range entries { f, err := entry.Blob().DataAsync() if err != nil { return nil, err } content, err := io.ReadAll(f) _ = f.Close() if err != nil { return nil, err } workflow, err := model.ReadWorkflow(bytes.NewReader(content)) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) continue } events, err := jobparser.ParseRawOn(&workflow.RawOn) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) continue } for _, evt := range events { if evt.Name != triggedEvent.Event() { continue } if detectMatched(commit, triggedEvent, payload, evt) { workflows[entry.Name()] = content } } } return workflows, nil } func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool { if len(evt.Acts) == 0 { return true } switch triggedEvent { case webhook_module.HookEventCreate: fallthrough case webhook_module.HookEventDelete: fallthrough case webhook_module.HookEventFork: log.Warn("unsupported event %q", triggedEvent.Event()) return false case webhook_module.HookEventPush: pushPayload := payload.(*api.PushPayload) matchTimes := 0 // all acts conditions should be satisfied for cond, vals := range evt.Acts { switch cond { case "branches", "tags": refShortName := git.RefName(pushPayload.Ref).ShortName() for _, val := range vals { if glob.MustCompile(val, '/').Match(refShortName) { matchTimes++ break } } case "paths": filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) } else { for _, val := range vals { matched := false for _, file := range filesChanged { if glob.MustCompile(val, '/').Match(file) { matched = true break } } if matched { matchTimes++ break } } } default: log.Warn("unsupported condition %q", cond) } } return matchTimes == len(evt.Acts) case webhook_module.HookEventIssues: fallthrough case webhook_module.HookEventIssueAssign: fallthrough case webhook_module.HookEventIssueLabel: fallthrough case webhook_module.HookEventIssueMilestone: fallthrough case webhook_module.HookEventIssueComment: fallthrough case webhook_module.HookEventPullRequest: prPayload := payload.(*api.PullRequestPayload) matchTimes := 0 // all acts conditions should be satisfied for cond, vals := range evt.Acts { switch cond { case "types": for _, val := range vals { if glob.MustCompile(val, '/').Match(string(prPayload.Action)) { matchTimes++ break } } case "branches": refShortName := git.RefName(prPayload.PullRequest.Base.Ref).ShortName() for _, val := range vals { if glob.MustCompile(val, '/').Match(refShortName) { matchTimes++ break } } case "paths": filesChanged, err := commit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) } else { for _, val := range vals { matched := false for _, file := range filesChanged { if glob.MustCompile(val, '/').Match(file) { matched = true break } } if matched { matchTimes++ break } } } default: log.Warn("unsupported condition %q", cond) } } return matchTimes == len(evt.Acts) case webhook_module.HookEventPullRequestAssign: fallthrough case webhook_module.HookEventPullRequestLabel: fallthrough case webhook_module.HookEventPullRequestMilestone: fallthrough case webhook_module.HookEventPullRequestComment: fallthrough case webhook_module.HookEventPullRequestReviewApproved: fallthrough case webhook_module.HookEventPullRequestReviewRejected: fallthrough case webhook_module.HookEventPullRequestReviewComment: fallthrough case webhook_module.HookEventPullRequestSync: fallthrough case webhook_module.HookEventWiki: fallthrough case webhook_module.HookEventRepository: fallthrough case webhook_module.HookEventRelease: fallthrough case webhook_module.HookEventPackage: fallthrough default: log.Warn("unsupported event %q", triggedEvent.Event()) } return false }
-
-
-
@@ -28,7 +28,7 @@ WebAuthn = &webauthn.WebAuthn{Config: &webauthn.Config{ RPDisplayName: setting.AppName, RPID: setting.Domain, RPOrigin: appURL, RPOrigins: []string{appURL}, AuthenticatorSelection: protocol.AuthenticatorSelection{ UserVerification: "discouraged", },
-
-
-
@@ -15,11 +15,11 @@ func TestInit(t *testing.T) {setting.Domain = "domain" setting.AppName = "AppName" setting.AppURL = "https://domain/" rpOrigin := "https://domain" rpOrigin := []string{"https://domain"} Init() assert.Equal(t, setting.Domain, WebAuthn.Config.RPID) assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName) assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigin) assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigins) }
-
-
-
@@ -44,7 +44,7 @@ }return streamer.escaped, err } // EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte // EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method. func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) { bufRd := bufio.NewReader(reader) outputStream := &HTMLStreamerWriter{Writer: writer}
-
@@ -64,10 +64,6 @@ if rdErr != io.EOF {err = rdErr } break } if err := streamer.SelfClosingTag("br"); err != nil { streamer.escaped.HasError = true return streamer.escaped, err } } return streamer.escaped, err
-
-
-
@@ -805,6 +805,7 @@ ctx.Data["EnableSwagger"] = setting.API.EnableSwaggerctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["EnableActions"] = setting.Actions.Enabled ctx.Data["ManifestData"] = setting.ManifestData
-
@@ -812,6 +813,7 @@ ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled() ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled() ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled() ctx.Data["locale"] = locale ctx.Data["AllLangs"] = translation.AllLangs()
-
-
-
@@ -1043,6 +1043,7 @@ ctx.Data["UnitTypeExternalWiki"] = unit_model.TypeExternalWikictx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects ctx.Data["UnitTypePackages"] = unit_model.TypePackages ctx.Data["UnitTypeActions"] = unit_model.TypeActions } }
-
-
-
@@ -16,14 +16,20 @@ "strings""time" "unsafe" "code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/util" ) // TrustedCmdArgs returns the trusted arguments for git command. // It's mainly for passing user-provided and trusted arguments to git command // In most cases, it shouldn't be used. Use AddXxx function instead type TrustedCmdArgs []internal.CmdArg var ( // globalCommandArgs global command args for external package setting globalCommandArgs []CmdArg globalCommandArgs TrustedCmdArgs // defaultCommandExecutionTimeout default command execution timeout duration defaultCommandExecutionTimeout = 360 * time.Second
-
@@ -42,8 +48,6 @@ globalArgsLength intbrokenArgs []string } type CmdArg string func (c *Command) String() string { if len(c.args) == 0 { return c.name
-
@@ -53,7 +57,7 @@ }// NewCommand creates and returns a new Git Command based on given command and arguments. // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. func NewCommand(ctx context.Context, args ...CmdArg) *Command { func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command { // Make an explicit copy of globalCommandArgs, otherwise append might overwrite it cargs := make([]string, 0, len(globalCommandArgs)+len(args)) for _, arg := range globalCommandArgs {
-
@@ -70,15 +74,9 @@ globalArgsLength: len(globalCommandArgs),} } // NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. func NewCommandNoGlobals(args ...CmdArg) *Command { return NewCommandContextNoGlobals(DefaultContext, args...) } // NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. func NewCommandContextNoGlobals(ctx context.Context, args ...CmdArg) *Command { func NewCommandContextNoGlobals(ctx context.Context, args ...internal.CmdArg) *Command { cargs := make([]string, 0, len(args)) for _, arg := range args { cargs = append(cargs, string(arg))
-
@@ -96,27 +94,70 @@ c.parentContext = ctxreturn c } // SetDescription sets the description for this command which be returned on // c.String() // SetDescription sets the description for this command which be returned on c.String() func (c *Command) SetDescription(desc string) *Command { c.desc = desc return c } // AddArguments adds new git argument(s) to the command. Each argument must be safe to be trusted. // User-provided arguments should be passed to AddDynamicArguments instead. func (c *Command) AddArguments(args ...CmdArg) *Command { // isSafeArgumentValue checks if the argument is safe to be used as a value (not an option) func isSafeArgumentValue(s string) bool { return s == "" || s[0] != '-' } // isValidArgumentOption checks if the argument is a valid option (starting with '-'). // It doesn't check whether the option is supported or not func isValidArgumentOption(s string) bool { return s != "" && s[0] == '-' } // AddArguments adds new git arguments (option/value) to the command. It only accepts string literals, or trusted CmdArg. // Type CmdArg is in the internal package, so it can not be used outside of this package directly, // it makes sure that user-provided arguments won't cause RCE risks. // User-provided arguments should be passed by other AddXxx functions func (c *Command) AddArguments(args ...internal.CmdArg) *Command { for _, arg := range args { c.args = append(c.args, string(arg)) } return c } // AddDynamicArguments adds new dynamic argument(s) to the command. // The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options // AddOptionValues adds a new option with a list of non-option values // For example: AddOptionValues("--opt", val) means 2 arguments: {"--opt", val}. // The values are treated as dynamic argument values. It equals to: AddArguments("--opt") then AddDynamicArguments(val). func (c *Command) AddOptionValues(opt internal.CmdArg, args ...string) *Command { if !isValidArgumentOption(string(opt)) { c.brokenArgs = append(c.brokenArgs, string(opt)) return c } c.args = append(c.args, string(opt)) c.AddDynamicArguments(args...) return c } // AddOptionFormat adds a new option with a format string and arguments // For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}. func (c *Command) AddOptionFormat(opt string, args ...any) *Command { if !isValidArgumentOption(opt) { c.brokenArgs = append(c.brokenArgs, opt) return c } // a quick check to make sure the format string matches the number of arguments, to find low-level mistakes ASAP if strings.Count(strings.ReplaceAll(opt, "%%", ""), "%") != len(args) { c.brokenArgs = append(c.brokenArgs, opt) return c } s := fmt.Sprintf(opt, args...) c.args = append(c.args, s) return c } // AddDynamicArguments adds new dynamic argument values to the command. // The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options. // TODO: in the future, this function can be renamed to AddArgumentValues func (c *Command) AddDynamicArguments(args ...string) *Command { for _, arg := range args { if arg != "" && arg[0] == '-' { if !isSafeArgumentValue(arg) { c.brokenArgs = append(c.brokenArgs, arg) } }
-
@@ -137,14 +178,14 @@ c.args = append(c.args, list...)return c } // CmdArgCheck checks whether the string is safe to be used as a dynamic argument. // It panics if the check fails. Usually it should not be used, it's just for refactoring purpose // deprecated func CmdArgCheck(s string) CmdArg { if s != "" && s[0] == '-' { panic("invalid git cmd argument: " + s) // ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs // In most cases, it shouldn't be used. Use AddXxx function instead func ToTrustedCmdArgs(args []string) TrustedCmdArgs { ret := make(TrustedCmdArgs, len(args)) for i, arg := range args { ret[i] = internal.CmdArg(arg) } return CmdArg(s) return ret } // RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
-
@@ -364,9 +405,9 @@ return stdoutBuf.Bytes(), stderr, nil} // AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests func AllowLFSFiltersArgs() []CmdArg { func AllowLFSFiltersArgs() TrustedCmdArgs { // Now here we should explicitly allow lfs filters to run filteredLFSGlobalArgs := make([]CmdArg, len(globalCommandArgs)) filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs)) j := 0 for _, arg := range globalCommandArgs { if strings.Contains(string(arg), "lfs") {
-
-
-
@@ -41,3 +41,14 @@ assert.NoError(t, err)assert.Empty(t, stderr) assert.Contains(t, stdout, "git version") } func TestGitArgument(t *testing.T) { assert.True(t, isValidArgumentOption("-x")) assert.True(t, isValidArgumentOption("--xx")) assert.False(t, isValidArgumentOption("")) assert.False(t, isValidArgumentOption("x")) assert.True(t, isSafeArgumentValue("")) assert.True(t, isSafeArgumentValue("x")) assert.False(t, isSafeArgumentValue("-x")) }
-
-
-
@@ -9,7 +9,6 @@ "bufio""bytes" "context" "errors" "fmt" "io" "os/exec" "strconv"
-
@@ -91,8 +90,8 @@ return AddChangesWithArgs(repoPath, globalCommandArgs, all, files...)} // AddChangesWithArgs marks local changes to be ready for commit. func AddChangesWithArgs(repoPath string, globalArgs []CmdArg, all bool, files ...string) error { cmd := NewCommandNoGlobals(append(globalArgs, "add")...) func AddChangesWithArgs(repoPath string, globalArgs TrustedCmdArgs, all bool, files ...string) error { cmd := NewCommandContextNoGlobals(DefaultContext, globalArgs...).AddArguments("add") if all { cmd.AddArguments("--all") }
-
@@ -111,17 +110,18 @@// CommitChanges commits local changes with given committer, author and message. // If author is nil, it will be the same as committer. func CommitChanges(repoPath string, opts CommitChangesOptions) error { cargs := make([]CmdArg, len(globalCommandArgs)) cargs := make(TrustedCmdArgs, len(globalCommandArgs)) copy(cargs, globalCommandArgs) return CommitChangesWithArgs(repoPath, cargs, opts) } // CommitChangesWithArgs commits local changes with given committer, author and message. // If author is nil, it will be the same as committer. func CommitChangesWithArgs(repoPath string, args []CmdArg, opts CommitChangesOptions) error { cmd := NewCommandNoGlobals(args...) func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChangesOptions) error { cmd := NewCommandContextNoGlobals(DefaultContext, args...) if opts.Committer != nil { cmd.AddArguments("-c", CmdArg("user.name="+opts.Committer.Name), "-c", CmdArg("user.email="+opts.Committer.Email)) cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) } cmd.AddArguments("commit")
-
@@ -129,9 +129,9 @@ if opts.Author == nil {opts.Author = opts.Committer } if opts.Author != nil { cmd.AddArguments(CmdArg(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email))) cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email) } cmd.AddArguments("-m").AddDynamicArguments(opts.Message) cmd.AddOptionValues("-m", opts.Message) _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) // No stderr but exit status 1 means nothing to commit.
-
-
-
@@ -383,6 +383,6 @@ return fmt.Errorf("failed to get git config %s, err: %w", key, err)} // Fsck verifies the connectivity and validity of the objects in the database func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...CmdArg) error { func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error { return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) }
-
-
-
@@ -0,0 +1,9 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package internal // CmdArg represents a command argument for git command, and it will be used for the git command directly without any further processing. // In most cases, you should use the "AddXxx" functions to add arguments, but not use this type directly. // Casting a risky (user-provided) string to CmdArg would cause security issues if it's injected with a "--xxx" argument. type CmdArg string
-
-
-
@@ -115,7 +115,7 @@ return CloneWithArgs(ctx, globalCommandArgs, from, to, opts)} // CloneWithArgs original repository to target path. func CloneWithArgs(ctx context.Context, args []CmdArg, from, to string, opts CloneRepoOptions) (err error) { func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, opts CloneRepoOptions) (err error) { toDir := path.Dir(to) if err = os.MkdirAll(toDir, os.ModePerm); err != nil { return err
-
-
-
@@ -57,9 +57,9 @@ }cmd := NewCommand(ctx, "archive") if usePrefix { cmd.AddArguments(CmdArg("--prefix=" + filepath.Base(strings.TrimSuffix(repo.Path, ".git")) + "/")) cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/") } cmd.AddArguments(CmdArg("--format=" + format.String())) cmd.AddOptionFormat("--format=%s", format.String()) cmd.AddDynamicArguments(commitID) var stderr strings.Builder
-
-
-
@@ -17,7 +17,7 @@ // CheckAttributeOpts represents the possible options to CheckAttributetype CheckAttributeOpts struct { CachedOnly bool AllAttributes bool Attributes []CmdArg Attributes []string Filenames []string IndexFile string WorkTree string
-
@@ -48,7 +48,7 @@ cmd.AddArguments("-a")} else { for _, attribute := range opts.Attributes { if attribute != "" { cmd.AddArguments(attribute) cmd.AddDynamicArguments(attribute) } } }
-
@@ -95,7 +95,7 @@// CheckAttributeReader provides a reader for check-attribute content that can be long running type CheckAttributeReader struct { // params Attributes []CmdArg Attributes []string Repo *Repository IndexFile string WorkTree string
-
@@ -111,19 +111,6 @@ }// Init initializes the CheckAttributeReader func (c *CheckAttributeReader) Init(ctx context.Context) error { cmdArgs := []CmdArg{"check-attr", "--stdin", "-z"} if len(c.IndexFile) > 0 { cmdArgs = append(cmdArgs, "--cached") c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) } if len(c.WorkTree) > 0 { c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) } c.env = append(c.env, "GIT_FLUSH=1") if len(c.Attributes) == 0 { lw := new(nulSeparatedAttributeWriter) lw.attributes = make(chan attributeTriple)
-
@@ -134,11 +121,22 @@ c.stdOut.Close()return fmt.Errorf("no provided Attributes to check") } cmdArgs = append(cmdArgs, c.Attributes...) cmdArgs = append(cmdArgs, "--") c.ctx, c.cancel = context.WithCancel(ctx) c.cmd = NewCommand(c.ctx, "check-attr", "--stdin", "-z") if len(c.IndexFile) > 0 { c.cmd.AddArguments("--cached") c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) } if len(c.WorkTree) > 0 { c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) } c.env = append(c.env, "GIT_FLUSH=1") c.ctx, c.cancel = context.WithCancel(ctx) c.cmd = NewCommand(c.ctx, cmdArgs...) // The empty "--" comes from #16773 , and it seems unnecessary because nothing else would be added later. c.cmd.AddDynamicArguments(c.Attributes...).AddArguments("--") var err error
-
@@ -294,7 +292,7 @@ return nil, func() {}} checker := &CheckAttributeReader{ Attributes: []CmdArg{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, Repo: repo, IndexFile: indexFilename, WorkTree: worktree,
-
-
-
@@ -3,7 +3,9 @@ // SPDX-License-Identifier: MITpackage git import "fmt" import ( "fmt" ) // FileBlame return the Blame object of file func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) {
-
@@ -14,8 +16,8 @@// LineBlame returns the latest commit at the given line func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { res, _, err := NewCommand(repo.Ctx, "blame"). AddArguments(CmdArg(fmt.Sprintf("-L %d,%d", line, line))). AddArguments("-p").AddDynamicArguments(revision). AddOptionFormat("-L %d,%d", line, line). AddOptionValues("-p", revision). AddDashesAndList(file).RunStdString(&RunOpts{Dir: path}) if err != nil { return nil, err
-
-
-
@@ -50,8 +50,8 @@ }return reference.Type() != plumbing.InvalidReference } // GetBranches returns branches from the repository, skipping skip initial branches and // returning at most limit branches, or all branches if limit is 0. // GetBranches returns branches from the repository, skipping "skip" initial branches and // returning at most "limit" branches, or all branches if "limit" is 0. func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { var branchNames []string
-
-
-
@@ -59,10 +59,10 @@return repo.IsReferenceExist(BranchPrefix + name) } // GetBranchNames returns branches from the repository, skipping skip initial branches and // returning at most limit branches, or all branches if limit is 0. // GetBranchNames returns branches from the repository, skipping "skip" initial branches and // returning at most "limit" branches, or all branches if "limit" is 0. func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { return callShowRef(repo.Ctx, repo.Path, BranchPrefix, []CmdArg{BranchPrefix, "--sort=-committerdate"}, skip, limit) return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) } // WalkReferences walks all the references from the repository
-
@@ -73,19 +73,19 @@// WalkReferences walks all the references from the repository // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { var args []CmdArg var args TrustedCmdArgs switch refType { case ObjectTag: args = []CmdArg{TagPrefix, "--sort=-taggerdate"} args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} case ObjectBranch: args = []CmdArg{BranchPrefix, "--sort=-committerdate"} args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} } return walkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) } // callShowRef return refs, if limit = 0 it will not limit func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs []CmdArg, skip, limit int) (branchNames []string, countAll int, err error) { func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { countAll, err = walkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { branchName = strings.TrimPrefix(branchName, trimPrefix) branchNames = append(branchNames, branchName)
-
@@ -95,7 +95,7 @@ })return branchNames, countAll, err } func walkShowRef(ctx context.Context, repoPath string, extraArgs []CmdArg, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { func walkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { stdoutReader, stdoutWriter := io.Pipe() defer func() { _ = stdoutReader.Close()
-
@@ -104,7 +104,7 @@ }()go func() { stderrBuilder := &strings.Builder{} args := []CmdArg{"for-each-ref", "--format=%(objectname) %(refname)"} args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} args = append(args, extraArgs...) err := NewCommand(ctx, args...).Run(&RunOpts{ Dir: repoPath,
-
-
-
@@ -89,7 +89,7 @@ }func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) { stdout, _, err := NewCommand(repo.Ctx, "log"). AddArguments(CmdArg("--skip="+strconv.Itoa((page-1)*pageSize)), CmdArg("--max-count="+strconv.Itoa(pageSize)), prettyLogFormat). AddOptionFormat("--skip=%d", (page-1)*pageSize).AddOptionFormat("--max-count=%d", pageSize).AddArguments(prettyLogFormat). AddDynamicArguments(id.String()). RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil {
-
@@ -99,33 +99,37 @@ return repo.parsePrettyFormatLogToList(stdout)} func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) { // create new git log command with limit of 100 commis cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) // ignore case args := []CmdArg{"-i"} // add common arguments to git command addCommonSearchArgs := func(c *Command) { // ignore case c.AddArguments("-i") // add authors if present in search query if len(opts.Authors) > 0 { for _, v := range opts.Authors { args = append(args, CmdArg("--author="+v)) // add authors if present in search query if len(opts.Authors) > 0 { for _, v := range opts.Authors { c.AddOptionFormat("--author=%s", v) } } } // add committers if present in search query if len(opts.Committers) > 0 { for _, v := range opts.Committers { args = append(args, CmdArg("--committer="+v)) // add committers if present in search query if len(opts.Committers) > 0 { for _, v := range opts.Committers { c.AddOptionFormat("--committer=%s", v) } } } // add time constraints if present in search query if len(opts.After) > 0 { args = append(args, CmdArg("--after="+opts.After)) } if len(opts.Before) > 0 { args = append(args, CmdArg("--before="+opts.Before)) // add time constraints if present in search query if len(opts.After) > 0 { c.AddOptionFormat("--after=%s", opts.After) } if len(opts.Before) > 0 { c.AddOptionFormat("--before=%s", opts.Before) } } // create new git log command with limit of 100 commits cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) // pretend that all refs along with HEAD were listed on command line as <commis> // https://git-scm.com/docs/git-log#Documentation/git-log.txt---all // note this is done only for command created above
-
@@ -137,12 +141,12 @@ // add remaining keywords from search string// note this is done only for command created above if len(opts.Keywords) > 0 { for _, v := range opts.Keywords { cmd.AddArguments(CmdArg("--grep=" + v)) cmd.AddOptionFormat("--grep=%s", v) } } // search for commits matching given constraints and keywords in commit msg cmd.AddArguments(args...) addCommonSearchArgs(cmd) stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err
-
@@ -160,7 +164,7 @@ if IsValidSHAPattern(v) {// create new git log command with 1 commit limit hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) // add previous arguments except for --grep and --all hashCmd.AddArguments(args...) addCommonSearchArgs(hashCmd) // add keyword as <commit> hashCmd.AddDynamicArguments(v)
-
@@ -213,8 +217,8 @@ }()go func() { stderr := strings.Builder{} gitCmd := NewCommand(repo.Ctx, "rev-list"). AddArguments(CmdArg("--max-count=" + strconv.Itoa(setting.Git.CommitsRangeSize*page))). AddArguments(CmdArg("--skip=" + strconv.Itoa(skip))) AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*page). AddOptionFormat("--skip=%d", skip) gitCmd.AddDynamicArguments(revision) gitCmd.AddDashesAndList(file) err := gitCmd.Run(&RunOpts{
-
@@ -295,21 +299,21 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip int) ([]*Commit, error) {var stdout []byte var err error if before == nil { stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", CmdArg(strconv.Itoa(limit)), "--skip", CmdArg(strconv.Itoa(skip))). stdout, _, err = NewCommand(repo.Ctx, "rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } else { stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", CmdArg(strconv.Itoa(limit)), "--skip", CmdArg(strconv.Itoa(skip))). stdout, _, err = NewCommand(repo.Ctx, "rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list --max-count n before last so let's try that... stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", CmdArg(strconv.Itoa(limit)), "--skip", CmdArg(strconv.Itoa(skip))). stdout, _, err = NewCommand(repo.Ctx, "rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } }
-
@@ -349,12 +353,11 @@ }// commitsBefore the limit is depth, not total number of returned commits. func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { cmd := NewCommand(repo.Ctx, "log") cmd := NewCommand(repo.Ctx, "log", prettyLogFormat) if limit > 0 { cmd.AddArguments(CmdArg("-"+strconv.Itoa(limit)), prettyLogFormat).AddDynamicArguments(id.String()) } else { cmd.AddArguments(prettyLogFormat).AddDynamicArguments(id.String()) cmd.AddOptionFormat("-%d", limit) } cmd.AddDynamicArguments(id.String()) stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if runErr != nil {
-
@@ -393,10 +396,9 @@ }func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { if CheckGitVersionAtLeast("2.7.0") == nil { stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", CmdArg("--count="+strconv.Itoa(limit)), "--format=%(refname:strip=2)", "--contains"). AddDynamicArguments(commit.ID.String(), BranchPrefix). stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). AddOptionFormat("--count=%d", limit). AddOptionValues("--contains", commit.ID.String(), BranchPrefix). RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err
-
@@ -406,7 +408,7 @@ branches := strings.Fields(stdout)return branches, nil } stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains").AddDynamicArguments(commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err }
-
-
-
@@ -172,25 +172,21 @@ }// GetDiffShortStat counts number of changed files, number of additions and deletions func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) { numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base+"..."+head)) numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, nil, base+"..."+head) if err != nil && strings.Contains(err.Error(), "no merge base") { return GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base), CmdArgCheck(head)) return GetDiffShortStat(repo.Ctx, repo.Path, nil, base, head) } return numFiles, totalAdditions, totalDeletions, err } // GetDiffShortStat counts number of changed files, number of additions and deletions func GetDiffShortStat(ctx context.Context, repoPath string, args ...CmdArg) (numFiles, totalAdditions, totalDeletions int, err error) { func GetDiffShortStat(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) { // Now if we call: // $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 // we get: // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" args = append([]CmdArg{ "diff", "--shortstat", }, args...) stdout, _, err := NewCommand(ctx, args...).RunStdString(&RunOpts{Dir: repoPath}) cmd := NewCommand(ctx, "diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return 0, 0, 0, err }
-
-
-
@@ -40,7 +40,7 @@ stats := &CodeActivityStats{}since := fromTime.Format(time.RFC3339) stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since))).RunStdString(&RunOpts{Dir: repo.Path}) stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(&RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr }
-
@@ -60,7 +60,7 @@ _ = stdoutReader.Close()_ = stdoutWriter.Close() }() gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since))) gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since) if len(branch) == 0 { gitCmd.AddArguments("--branches=*") } else {
-
-
-
@@ -121,7 +121,9 @@ stderr := strings.Builder{}rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} go func() { err := NewCommand(repo.Ctx, "for-each-ref", CmdArg("--format="+forEachRefFmt.Flag()), "--sort", "-*creatordate", "refs/tags").Run(rc) err := NewCommand(repo.Ctx, "for-each-ref"). AddOptionFormat("--format=%s", forEachRefFmt.Flag()). AddArguments("--sort", "-*creatordate", "refs/tags").Run(rc) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String())) } else {
-
-
-
@@ -25,7 +25,7 @@// GetTags returns all tags of the repository. // returning at most limit tags, or all if limit is 0. func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, []CmdArg{TagPrefix, "--sort=-taggerdate"}, skip, limit) tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}, skip, limit) return tags, err }
-
-
-
@@ -6,7 +6,6 @@ package gitimport ( "bytes" "fmt" "os" "strings" "time"
-
@@ -45,7 +44,7 @@ _, _ = messageBytes.WriteString(opts.Message)_, _ = messageBytes.WriteString("\n") if opts.KeyID != "" || opts.AlwaysSign { cmd.AddArguments(CmdArg(fmt.Sprintf("-S%s", opts.KeyID))) cmd.AddOptionFormat("-S%s", opts.KeyID) } if opts.NoGPGSign {
-
-
-
@@ -100,14 +100,15 @@ }// listEntriesRecursive returns all entries of current tree recursively including all subtrees // extraArgs could be "-l" to get the size, which is slower func (t *Tree) listEntriesRecursive(extraArgs ...CmdArg) (Entries, error) { func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { if t.entriesRecursiveParsed { return t.entriesRecursive, nil } args := append([]CmdArg{"ls-tree", "-t", "-r"}, extraArgs...) args = append(args, CmdArg(t.ID.String())) stdout, _, runErr := NewCommand(t.repo.Ctx, args...).RunStdBytes(&RunOpts{Dir: t.repo.Path}) stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). AddArguments(extraArgs...). AddDynamicArguments(t.ID.String()). RunStdBytes(&RunOpts{Dir: t.repo.Path}) if runErr != nil { return nil, runErr }
-
@@ -123,10 +124,10 @@ }// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { return t.listEntriesRecursive() return t.listEntriesRecursive(nil) } // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { return t.listEntriesRecursive("--long") return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) }
-
-
-
@@ -7,7 +7,6 @@ import ("bufio" "bytes" "context" "fmt" "os" "strings"
-
@@ -33,12 +32,9 @@ if len(branches) == 0 {graphCmd.AddArguments("--all") } graphCmd.AddArguments( "-C", "-M", git.CmdArg(fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page)), "--date=iso", git.CmdArg(fmt.Sprintf("--pretty=format:%s", format))) graphCmd.AddArguments("-C", "-M", "--date=iso"). AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page). AddOptionFormat("--pretty=format:%s", format) if len(branches) > 0 { graphCmd.AddDynamicArguments(branches...)
-
-
-
@@ -0,0 +1,47 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package hcaptcha const ( ErrMissingInputSecret ErrorCode = "missing-input-secret" ErrInvalidInputSecret ErrorCode = "invalid-input-secret" ErrMissingInputResponse ErrorCode = "missing-input-response" ErrInvalidInputResponse ErrorCode = "invalid-input-response" ErrBadRequest ErrorCode = "bad-request" ErrInvalidOrAlreadySeenResponse ErrorCode = "invalid-or-already-seen-response" ErrNotUsingDummyPasscode ErrorCode = "not-using-dummy-passcode" ErrSitekeySecretMismatch ErrorCode = "sitekey-secret-mismatch" ) // ErrorCode is any possible error from hCaptcha type ErrorCode string // String fulfills the Stringer interface func (err ErrorCode) String() string { switch err { case ErrMissingInputSecret: return "Your secret key is missing." case ErrInvalidInputSecret: return "Your secret key is invalid or malformed." case ErrMissingInputResponse: return "The response parameter (verification token) is missing." case ErrInvalidInputResponse: return "The response parameter (verification token) is invalid or malformed." case ErrBadRequest: return "The request is invalid or malformed." case ErrInvalidOrAlreadySeenResponse: return "The response parameter has already been checked, or has another issue." case ErrNotUsingDummyPasscode: return "You have used a testing sitekey but have not used its matching secret." case ErrSitekeySecretMismatch: return "The sitekey is not registered with the provided secret." default: return "" } } // Error fulfills the error interface func (err ErrorCode) Error() string { return err.String() }
-
-
-
@@ -5,20 +5,127 @@ package hcaptchaimport ( "context" "io" "net/http" "net/url" "strings" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" ) const verifyURL = "https://hcaptcha.com/siteverify" // Client is an hCaptcha client type Client struct { ctx context.Context http *http.Client secret string } // PostOptions are optional post form values type PostOptions struct { RemoteIP string Sitekey string } // ClientOption is a func to modify a new Client type ClientOption func(*Client) // WithHTTP sets the http.Client of a Client func WithHTTP(httpClient *http.Client) func(*Client) { return func(hClient *Client) { hClient.http = httpClient } } // WithContext sets the context.Context of a Client func WithContext(ctx context.Context) func(*Client) { return func(hClient *Client) { hClient.ctx = ctx } } // New returns a new hCaptcha Client func New(secret string, options ...ClientOption) (*Client, error) { if strings.TrimSpace(secret) == "" { return nil, ErrMissingInputSecret } client := &Client{ ctx: context.Background(), http: http.DefaultClient, secret: secret, } for _, opt := range options { opt(client) } return client, nil } "go.jolheiser.com/hcaptcha" ) // Response is an hCaptcha response type Response struct { Success bool `json:"success"` ChallengeTS string `json:"challenge_ts"` Hostname string `json:"hostname"` Credit bool `json:"credit,omitempty"` ErrorCodes []ErrorCode `json:"error-codes"` } // Verify checks the response against the hCaptcha API func (c *Client) Verify(token string, opts PostOptions) (*Response, error) { if strings.TrimSpace(token) == "" { return nil, ErrMissingInputResponse } post := url.Values{ "secret": []string{c.secret}, "response": []string{token}, } if strings.TrimSpace(opts.RemoteIP) != "" { post.Add("remoteip", opts.RemoteIP) } if strings.TrimSpace(opts.Sitekey) != "" { post.Add("sitekey", opts.Sitekey) } // Basically a copy of http.PostForm, but with a context req, err := http.NewRequestWithContext(c.ctx, http.MethodPost, verifyURL, strings.NewReader(post.Encode())) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.http.Do(req) if err != nil { return nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() var response *Response if err := json.Unmarshal(body, &response); err != nil { return nil, err } return response, nil } // Verify calls hCaptcha API to verify token func Verify(ctx context.Context, response string) (bool, error) { client, err := hcaptcha.New(setting.Service.HcaptchaSecret, hcaptcha.WithContext(ctx)) client, err := New(setting.Service.HcaptchaSecret, WithContext(ctx)) if err != nil { return false, err } resp, err := client.Verify(response, hcaptcha.PostOptions{ resp, err := client.Verify(response, PostOptions{ Sitekey: setting.Service.HcaptchaSitekey, }) if err != nil {
-
-
-
@@ -0,0 +1,106 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package hcaptcha import ( "net/http" "os" "strings" "testing" "time" ) const ( dummySiteKey = "10000000-ffff-ffff-ffff-000000000001" dummySecret = "0x0000000000000000000000000000000000000000" dummyToken = "10000000-aaaa-bbbb-cccc-000000000001" ) func TestMain(m *testing.M) { os.Exit(m.Run()) } func TestCaptcha(t *testing.T) { tt := []struct { Name string Secret string Token string Error ErrorCode }{ { Name: "Success", Secret: dummySecret, Token: dummyToken, }, { Name: "Missing Secret", Token: dummyToken, Error: ErrMissingInputSecret, }, { Name: "Missing Token", Secret: dummySecret, Error: ErrMissingInputResponse, }, { Name: "Invalid Token", Secret: dummySecret, Token: "test", Error: ErrInvalidInputResponse, }, } for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { client, err := New(tc.Secret, WithHTTP(&http.Client{ Timeout: time.Second * 5, })) if err != nil { // The only error that can be returned from creating a client if tc.Error == ErrMissingInputSecret && err == ErrMissingInputSecret { return } t.Log(err) t.FailNow() } resp, err := client.Verify(tc.Token, PostOptions{ Sitekey: dummySiteKey, }) if err != nil { // The only error that can be returned prior to the request if tc.Error == ErrMissingInputResponse && err == ErrMissingInputResponse { return } t.Log(err) t.FailNow() } if tc.Error.String() != "" { if resp.Success { t.Log("Verification should fail.") t.Fail() } if len(resp.ErrorCodes) == 0 { t.Log("hCaptcha should have returned an error.") t.Fail() } var hasErr bool for _, err := range resp.ErrorCodes { if strings.EqualFold(err.String(), tc.Error.String()) { hasErr = true break } } if !hasErr { t.Log("hCaptcha did not return the error being tested") t.Fail() } } else if !resp.Success { t.Log("Verification should succeed.") t.Fail() } }) } }
-
-
-
@@ -19,14 +19,16 @@ // AddCacheControlToHeader adds suitable cache-control headers to responsefunc AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) { directives := make([]string, 0, 2+len(additionalDirectives)) // "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store" // because browsers may restore some input fields after navigate-back / reload a page. if setting.IsProd { if maxAge == 0 { directives = append(directives, "no-store") directives = append(directives, "max-age=0", "private", "must-revalidate") } else { directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds()))) } } else { directives = append(directives, "no-store") directives = append(directives, "max-age=0", "private", "must-revalidate") // to remind users they are using non-prod setting. h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
-
-
-
@@ -383,6 +383,13 @@ fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value))s.Write(*cv.resetBytes) } // ColorFormatAsString returns the result of the ColorFormat without the color func ColorFormatAsString(colorVal ColorFormatted) string { s := new(strings.Builder) _, _ = ColorFprintf(&protectedANSIWriter{w: s, mode: removeColor}, "%-v", colorVal) return s.String() } // SetColorBytes will allow a user to set the colorBytes of a colored value func (cv *ColoredValue) SetColorBytes(colorBytes []byte) { cv.colorBytes = &colorBytes
-
-
-
@@ -9,6 +9,8 @@ "os""runtime" "strings" "sync" "code.gitea.io/gitea/modules/process" ) type loggerMap struct {
-
@@ -285,6 +287,15 @@ }} func init() { process.Trace = func(start bool, pid process.IDType, description string, parentPID process.IDType, typ string) { if start && parentPID != "" { Log(1, TRACE, "Start %s: %s (from %s) (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(parentPID, FgYellow), NewColoredValue(typ, Reset)) } else if start { Log(1, TRACE, "Start %s: %s (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(typ, Reset)) } else { Log(1, TRACE, "Done %s: %s", NewColoredValue(pid, FgHiYellow), NewColoredValue(description, Reset)) } } _, filename, _, _ := runtime.Caller(0) prefix = strings.TrimSuffix(filename, "modules/log/log.go") if prefix == filename {
-
-
-
@@ -164,6 +164,7 @@ shortLinkProcessor,linkProcessor, mentionProcessor, issueIndexPatternProcessor, commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emailAddressProcessor, emojiProcessor,
-
@@ -190,6 +191,7 @@ fullSha1PatternProcessor,linkProcessor, mentionProcessor, issueIndexPatternProcessor, commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emailAddressProcessor, emojiProcessor,
-
@@ -221,6 +223,7 @@ fullSha1PatternProcessor,linkProcessor, mentionProcessor, issueIndexPatternProcessor, commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emojiShortCodeProcessor, emojiProcessor,
-
@@ -257,6 +260,7 @@ title string,) (string, error) { return renderProcessString(ctx, []processor{ issueIndexPatternProcessor, commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emojiShortCodeProcessor, emojiProcessor,
-
@@ -354,10 +358,17 @@ return nil} func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) { // Add user-content- to IDs if they don't already have them // Add user-content- to IDs and "#" links if they don't already have them for idx, attr := range node.Attr { if attr.Key == "id" && !(strings.HasPrefix(attr.Val, "user-content-") || blackfridayExtRegex.MatchString(attr.Val)) { val := strings.TrimPrefix(attr.Val, "#") notHasPrefix := !(strings.HasPrefix(val, "user-content-") || blackfridayExtRegex.MatchString(val)) if attr.Key == "id" && notHasPrefix { node.Attr[idx].Val = "user-content-" + attr.Val } if attr.Key == "href" && strings.HasPrefix(attr.Val, "#") && notHasPrefix { node.Attr[idx].Val = "#user-content-" + val } if attr.Key == "class" && attr.Val == "emoji" {
-
@@ -904,6 +915,23 @@ Data: node.Data[ref.ActionLocation.End:ref.RefLocation.Start],} replaceContentList(node, ref.ActionLocation.Start, ref.RefLocation.End, []*html.Node{keyword, spaces, link}) node = node.NextSibling.NextSibling.NextSibling.NextSibling } } func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { next := node.NextSibling for node != nil && node != next { found, ref := references.FindRenderizableCommitCrossReference(node.Data) if !found { return } reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) link := createLink(util.URLJoin(setting.AppSubURL, ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) node = node.NextSibling.NextSibling } }
-
-
-
@@ -10,6 +10,7 @@ issues_model "code.gitea.io/gitea/models/issues"packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification/action" "code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/indexer"
-
@@ -106,6 +107,13 @@ }// NotifyNewPullRequest notifies new pull request to notifiers func NotifyNewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) { if err := pr.LoadIssue(ctx); err != nil { log.Error("%v", err) return } if err := pr.Issue.LoadPoster(ctx); err != nil { return } for _, notifier := range notifiers { notifier.NotifyNewPullRequest(ctx, pr, mentions) }
-
@@ -120,6 +128,10 @@ }// NotifyPullRequestReview notifies new pull request review func NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) { if err := review.LoadReviewer(ctx); err != nil { log.Error("%v", err) return } for _, notifier := range notifiers { notifier.NotifyPullRequestReview(ctx, pr, review, comment, mentions) }
-
@@ -127,6 +139,10 @@ }// NotifyPullRequestCodeComment notifies new pull request code comment func NotifyPullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, comment *issues_model.Comment, mentions []*user_model.User) { if err := comment.LoadPoster(ctx); err != nil { log.Error("LoadPoster: %v", err) return } for _, notifier := range notifiers { notifier.NotifyPullRequestCodeComment(ctx, pr, comment, mentions) }
-
@@ -169,6 +185,10 @@ }// NotifyNewRelease notifies new release to notifiers func NotifyNewRelease(ctx context.Context, rel *repo_model.Release) { if err := rel.LoadAttributes(ctx); err != nil { log.Error("LoadPublisher: %v", err) return } for _, notifier := range notifiers { notifier.NotifyNewRelease(ctx, rel) }
-
-
-
@@ -97,6 +97,7 @@ func (ns *notificationService) NotifyIssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) {_ = ns.issueQueue.Push(issueNotificationOpts{ IssueID: issue.ID, NotificationAuthorID: doer.ID, CommentID: actionComment.ID, }) }
-
-
-
@@ -0,0 +1,243 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package conda import ( "archive/tar" "archive/zip" "compress/bzip2" "io" "strings" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" "github.com/klauspost/compress/zstd" ) var ( ErrInvalidStructure = util.SilentWrap{Message: "package structure is invalid", Err: util.ErrInvalidArgument} ErrInvalidName = util.SilentWrap{Message: "package name is invalid", Err: util.ErrInvalidArgument} ErrInvalidVersion = util.SilentWrap{Message: "package version is invalid", Err: util.ErrInvalidArgument} ) const ( PropertyName = "conda.name" PropertyChannel = "conda.channel" PropertySubdir = "conda.subdir" PropertyMetadata = "conda.metdata" ) // Package represents a Conda package type Package struct { Name string Version string Subdir string VersionMetadata *VersionMetadata FileMetadata *FileMetadata } // VersionMetadata represents the metadata of a Conda package type VersionMetadata struct { Description string `json:"description,omitempty"` Summary string `json:"summary,omitempty"` ProjectURL string `json:"project_url,omitempty"` RepositoryURL string `json:"repository_url,omitempty"` DocumentationURL string `json:"documentation_url,omitempty"` License string `json:"license,omitempty"` LicenseFamily string `json:"license_family,omitempty"` } // FileMetadata represents the metadata of a Conda package file type FileMetadata struct { IsCondaPackage bool `json:"is_conda"` Architecture string `json:"architecture,omitempty"` NoArch string `json:"noarch,omitempty"` Build string `json:"build,omitempty"` BuildNumber int64 `json:"build_number,omitempty"` Dependencies []string `json:"dependencies,omitempty"` Platform string `json:"platform,omitempty"` Timestamp int64 `json:"timestamp,omitempty"` } type index struct { Name string `json:"name"` Version string `json:"version"` Architecture string `json:"arch"` NoArch string `json:"noarch"` Build string `json:"build"` BuildNumber int64 `json:"build_number"` Dependencies []string `json:"depends"` License string `json:"license"` LicenseFamily string `json:"license_family"` Platform string `json:"platform"` Subdir string `json:"subdir"` Timestamp int64 `json:"timestamp"` } type about struct { Description string `json:"description"` Summary string `json:"summary"` ProjectURL string `json:"home"` RepositoryURL string `json:"dev_url"` DocumentationURL string `json:"doc_url"` } type ReaderAndReaderAt interface { io.Reader io.ReaderAt } // ParsePackageBZ2 parses the Conda package file compressed with bzip2 func ParsePackageBZ2(r io.Reader) (*Package, error) { gzr := bzip2.NewReader(r) return parsePackageTar(gzr) } // ParsePackageConda parses the Conda package file compressed with zip and zstd func ParsePackageConda(r io.ReaderAt, size int64) (*Package, error) { zr, err := zip.NewReader(r, size) if err != nil { return nil, err } for _, file := range zr.File { if strings.HasPrefix(file.Name, "info-") && strings.HasSuffix(file.Name, ".tar.zst") { f, err := zr.Open(file.Name) if err != nil { return nil, err } defer f.Close() dec, err := zstd.NewReader(f) if err != nil { return nil, err } defer dec.Close() p, err := parsePackageTar(dec) if p != nil { p.FileMetadata.IsCondaPackage = true } return p, err } } return nil, ErrInvalidStructure } func parsePackageTar(r io.Reader) (*Package, error) { var i *index var a *about tr := tar.NewReader(r) for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { return nil, err } if hdr.Typeflag != tar.TypeReg { continue } if hdr.Name == "info/index.json" { if err := json.NewDecoder(tr).Decode(&i); err != nil { return nil, err } if !checkName(i.Name) { return nil, ErrInvalidName } if !checkVersion(i.Version) { return nil, ErrInvalidVersion } if a != nil { break // stop loop if both files were found } } else if hdr.Name == "info/about.json" { if err := json.NewDecoder(tr).Decode(&a); err != nil { return nil, err } if !validation.IsValidURL(a.ProjectURL) { a.ProjectURL = "" } if !validation.IsValidURL(a.RepositoryURL) { a.RepositoryURL = "" } if !validation.IsValidURL(a.DocumentationURL) { a.DocumentationURL = "" } if i != nil { break // stop loop if both files were found } } } if i == nil { return nil, ErrInvalidStructure } if a == nil { a = &about{} } return &Package{ Name: i.Name, Version: i.Version, Subdir: i.Subdir, VersionMetadata: &VersionMetadata{ License: i.License, LicenseFamily: i.LicenseFamily, Description: a.Description, Summary: a.Summary, ProjectURL: a.ProjectURL, RepositoryURL: a.RepositoryURL, DocumentationURL: a.DocumentationURL, }, FileMetadata: &FileMetadata{ Architecture: i.Architecture, NoArch: i.NoArch, Build: i.Build, BuildNumber: i.BuildNumber, Dependencies: i.Dependencies, Platform: i.Platform, Timestamp: i.Timestamp, }, }, nil } // https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1393 func checkName(name string) bool { if name == "" { return false } if name != strings.ToLower(name) { return false } return !checkBadCharacters(name, "!") } // https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1403 func checkVersion(version string) bool { if version == "" { return false } return !checkBadCharacters(version, "-") } func checkBadCharacters(s, additional string) bool { if strings.ContainsAny(s, "=@#$%^&*:;\"'\\|<>?/ ") { return true } return strings.ContainsAny(s, additional) }
-
-
-
@@ -0,0 +1,150 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package conda import ( "archive/tar" "archive/zip" "bytes" "io" "testing" "github.com/dsnet/compress/bzip2" "github.com/klauspost/compress/zstd" "github.com/stretchr/testify/assert" ) const ( packageName = "gitea" packageVersion = "1.0.1" description = "Package Description" projectURL = "https://gitea.io" repositoryURL = "https://gitea.io/gitea/gitea" documentationURL = "https://docs.gitea.io" ) func TestParsePackage(t *testing.T) { createArchive := func(files map[string][]byte) *bytes.Buffer { var buf bytes.Buffer tw := tar.NewWriter(&buf) for filename, content := range files { hdr := &tar.Header{ Name: filename, Mode: 0o600, Size: int64(len(content)), } tw.WriteHeader(hdr) tw.Write(content) } tw.Close() return &buf } t.Run("MissingIndexFile", func(t *testing.T) { buf := createArchive(map[string][]byte{"dummy.txt": {}}) p, err := parsePackageTar(buf) assert.Nil(t, p) assert.ErrorIs(t, err, ErrInvalidStructure) }) t.Run("MissingAboutFile", func(t *testing.T) { buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"name","version":"1.0"}`)}) p, err := parsePackageTar(buf) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, "name", p.Name) assert.Equal(t, "1.0", p.Version) assert.Empty(t, p.VersionMetadata.ProjectURL) }) t.Run("InvalidName", func(t *testing.T) { for _, name := range []string{"", "name!", "nAMe"} { buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"` + name + `","version":"1.0"}`)}) p, err := parsePackageTar(buf) assert.Nil(t, p) assert.ErrorIs(t, err, ErrInvalidName) } }) t.Run("InvalidVersion", func(t *testing.T) { for _, version := range []string{"", "1.0-2"} { buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"name","version":"` + version + `"}`)}) p, err := parsePackageTar(buf) assert.Nil(t, p) assert.ErrorIs(t, err, ErrInvalidVersion) } }) t.Run("Valid", func(t *testing.T) { buf := createArchive(map[string][]byte{ "info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `","subdir":"linux-64"}`), "info/about.json": []byte(`{"description":"` + description + `","dev_url":"` + repositoryURL + `","doc_url":"` + documentationURL + `","home":"` + projectURL + `"}`), }) p, err := parsePackageTar(buf) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, packageName, p.Name) assert.Equal(t, packageVersion, p.Version) assert.Equal(t, "linux-64", p.Subdir) assert.Equal(t, description, p.VersionMetadata.Description) assert.Equal(t, projectURL, p.VersionMetadata.ProjectURL) assert.Equal(t, repositoryURL, p.VersionMetadata.RepositoryURL) assert.Equal(t, documentationURL, p.VersionMetadata.DocumentationURL) }) t.Run(".tar.bz2", func(t *testing.T) { tarArchive := createArchive(map[string][]byte{ "info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `"}`), }) var buf bytes.Buffer bw, _ := bzip2.NewWriter(&buf, nil) io.Copy(bw, tarArchive) bw.Close() br := bytes.NewReader(buf.Bytes()) p, err := ParsePackageBZ2(br) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, packageName, p.Name) assert.Equal(t, packageVersion, p.Version) assert.False(t, p.FileMetadata.IsCondaPackage) }) t.Run(".conda", func(t *testing.T) { tarArchive := createArchive(map[string][]byte{ "info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `"}`), }) var infoBuf bytes.Buffer zsw, _ := zstd.NewWriter(&infoBuf) io.Copy(zsw, tarArchive) zsw.Close() var buf bytes.Buffer zpw := zip.NewWriter(&buf) w, _ := zpw.Create("info-x.tar.zst") w.Write(infoBuf.Bytes()) zpw.Close() br := bytes.NewReader(buf.Bytes()) p, err := ParsePackageConda(br, int64(br.Len())) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, packageName, p.Name) assert.Equal(t, packageVersion, p.Version) assert.True(t, p.FileMetadata.IsCondaPackage) }) }
-
-
-
@@ -6,9 +6,8 @@import ( "context" "code.gitea.io/gitea/modules/password/pwn" "code.gitea.io/gitea/modules/setting" "go.jolheiser.com/pwn" ) // IsPwned checks whether a password has been pwned
-
-
-
@@ -0,0 +1,118 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package pwn import ( "context" "crypto/sha1" "encoding/hex" "errors" "fmt" "io" "net/http" "strconv" "strings" "code.gitea.io/gitea/modules/setting" ) const passwordURL = "https://api.pwnedpasswords.com/range/" // ErrEmptyPassword is an empty password error var ErrEmptyPassword = errors.New("password cannot be empty") // Client is a HaveIBeenPwned client type Client struct { ctx context.Context http *http.Client } // New returns a new HaveIBeenPwned Client func New(options ...ClientOption) *Client { client := &Client{ ctx: context.Background(), http: http.DefaultClient, } for _, opt := range options { opt(client) } return client } // ClientOption is a way to modify a new Client type ClientOption func(*Client) // WithHTTP will set the http.Client of a Client func WithHTTP(httpClient *http.Client) func(pwnClient *Client) { return func(pwnClient *Client) { pwnClient.http = httpClient } } // WithContext will set the context.Context of a Client func WithContext(ctx context.Context) func(pwnClient *Client) { return func(pwnClient *Client) { pwnClient.ctx = ctx } } func newRequest(ctx context.Context, method, url string, body io.ReadCloser) (*http.Request, error) { req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return nil, err } req.Header.Add("User-Agent", "Gitea "+setting.AppVer) return req, nil } // CheckPassword returns the number of times a password has been compromised // Adding padding will make requests more secure, however is also slower // because artificial responses will be added to the response // For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/ func (c *Client) CheckPassword(pw string, padding bool) (int, error) { if strings.TrimSpace(pw) == "" { return -1, ErrEmptyPassword } sha := sha1.New() sha.Write([]byte(pw)) enc := hex.EncodeToString(sha.Sum(nil)) prefix, suffix := enc[:5], enc[5:] req, err := newRequest(c.ctx, http.MethodGet, fmt.Sprintf("%s%s", passwordURL, prefix), nil) if err != nil { return -1, nil } if padding { req.Header.Add("Add-Padding", "true") } resp, err := c.http.Do(req) if err != nil { return -1, err } body, err := io.ReadAll(resp.Body) if err != nil { return -1, err } defer resp.Body.Close() for _, pair := range strings.Split(string(body), "\n") { parts := strings.Split(pair, ":") if len(parts) != 2 { continue } if strings.EqualFold(suffix, parts[0]) { count, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64) if err != nil { return -1, err } return int(count), nil } } return 0, nil }
-
-
-
@@ -0,0 +1,142 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package pwn import ( "errors" "math/rand" "net/http" "os" "strings" "testing" "time" ) var client = New(WithHTTP(&http.Client{ Timeout: time.Second * 2, })) func TestMain(m *testing.M) { rand.Seed(time.Now().Unix()) os.Exit(m.Run()) } func TestPassword(t *testing.T) { // Check input error _, err := client.CheckPassword("", false) if err == nil { t.Log("blank input should return an error") t.Fail() } if !errors.Is(err, ErrEmptyPassword) { t.Log("blank input should return ErrEmptyPassword") t.Fail() } // Should fail fail := "password1234" count, err := client.CheckPassword(fail, false) if err != nil { t.Log(err) t.Fail() } if count == 0 { t.Logf("%s should fail as a password\n", fail) t.Fail() } // Should fail (with padding) failPad := "administrator" count, err = client.CheckPassword(failPad, true) if err != nil { t.Log(err) t.Fail() } if count == 0 { t.Logf("%s should fail as a password\n", failPad) t.Fail() } // Checking for a "good" password isn't going to be perfect, but we can give it a good try // with hopefully minimal error. Try five times? var good bool var pw string for idx := 0; idx <= 5; idx++ { pw = testPassword() count, err = client.CheckPassword(pw, false) if err != nil { t.Log(err) t.Fail() } if count == 0 { good = true break } } if !good { t.Log("no generated passwords passed. there is a chance this is a fluke") t.Fail() } // Again, but with padded responses good = false for idx := 0; idx <= 5; idx++ { pw = testPassword() count, err = client.CheckPassword(pw, true) if err != nil { t.Log(err) t.Fail() } if count == 0 { good = true break } } if !good { t.Log("no generated passwords passed. there is a chance this is a fluke") t.Fail() } } // Credit to https://golangbyexample.com/generate-random-password-golang/ // DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR var ( lowerCharSet = "abcdedfghijklmnopqrst" upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" specialCharSet = "!@#$%&*" numberSet = "0123456789" allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet ) func testPassword() string { var password strings.Builder // Set special character for i := 0; i < 5; i++ { random := rand.Intn(len(specialCharSet)) password.WriteString(string(specialCharSet[random])) } // Set numeric for i := 0; i < 5; i++ { random := rand.Intn(len(numberSet)) password.WriteString(string(numberSet[random])) } // Set uppercase for i := 0; i < 5; i++ { random := rand.Intn(len(upperCharSet)) password.WriteString(string(upperCharSet[random])) } for i := 0; i < 5; i++ { random := rand.Intn(len(allCharSet)) password.WriteString(string(allCharSet[random])) } inRune := []rune(password.String()) rand.Shuffle(len(inRune), func(i, j int) { inRune[i], inRune[j] = inRune[j], inRune[i] }) return string(inRune) }
-
-
-
@@ -57,6 +57,7 @@ GitPushOptions GitPushOptionsPullRequestID int64 DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user. IsWiki bool ActionPerm int } // SSHLogOption ssh log options
-
-
-
@@ -6,6 +6,7 @@ package processimport ( "context" "log" "runtime/pprof" "strconv" "sync"
-
@@ -42,6 +43,18 @@// FinishedFunc is a function that marks that the process is finished and can be removed from the process table // - it is simply an alias for context.CancelFunc and is only for documentary purposes type FinishedFunc = context.CancelFunc var Trace = defaultTrace // this global can be overridden by particular logging packages - thus avoiding import cycles func defaultTrace(start bool, pid IDType, description string, parentPID IDType, typ string) { if start && parentPID != "" { log.Printf("start process %s: %s (from %s) (%s)", pid, description, parentPID, typ) } else if start { log.Printf("start process %s: %s (%s)", pid, description, typ) } else { log.Printf("end process %s: %s", pid, description) } } // Manager manages all processes and counts PIDs. type Manager struct {
-
@@ -154,6 +167,7 @@ }pm.processMap[pid] = process pm.mutex.Unlock() Trace(true, pid, description, parentPID, processType) pprofCtx := pprof.WithLabels(ctx, pprof.Labels(DescriptionPProfLabel, description, PPIDPProfLabel, string(parentPID), PIDPProfLabel, string(pid), ProcessTypePProfLabel, processType)) if currentlyRunning {
-
@@ -185,18 +199,12 @@ pid = IDType(string(pid) + "-" + strconv.FormatInt(pm.next, 10))return start, pid } // Remove a process from the ProcessManager. func (pm *Manager) Remove(pid IDType) { pm.mutex.Lock() delete(pm.processMap, pid) pm.mutex.Unlock() } func (pm *Manager) remove(process *process) { pm.mutex.Lock() defer pm.mutex.Unlock() if p := pm.processMap[process.PID]; p == process { delete(pm.processMap, process.PID) Trace(false, process.PID, process.Description, process.ParentPID, process.Type) } }
-
-
-
@@ -82,7 +82,7 @@ defer finished()assert.NotEqual(t, GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetPID(), "expected to get different pids got %s == %s", GetContext(p2Ctx).GetPID(), GetContext(p1Ctx).GetPID()) pm.Remove(GetPID(p2Ctx)) finished() _, exists := pm.processMap[GetPID(p2Ctx)] assert.False(t, exists, "PID %d is in the list but shouldn't", GetPID(p2Ctx))
-
-
-
@@ -35,9 +35,12 @@ issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\')([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. gogits/gogs#12345 // e.g. org/repo#12345 crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) // spaceTrimmedPattern let's us find the trailing space // crossReferenceCommitPattern matches a string that references a commit in a different repository // e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters) crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,40})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) // spaceTrimmedPattern let's find the trailing space spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`) // timeLogPattern matches string for time tracking timeLogPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@([0-9]+([\.,][0-9]+)?(w|d|m|h))+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
-
@@ -92,6 +95,7 @@ type RenderizableReference struct {Issue string Owner string Name string CommitSha string IsPull bool RefLocation *RefSpan Action XRefAction
-
@@ -347,6 +351,21 @@ IsPull: r.isPull,RefLocation: r.refLocation, Action: r.action, ActionLocation: r.actionLocation, } } // FindRenderizableCommitCrossReference returns the first unvalidated commit cross reference found in a string. func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableReference) { m := crossReferenceCommitPattern.FindStringSubmatchIndex(content) if len(m) < 8 { return false, nil } return true, &RenderizableReference{ Owner: content[m[2]:m[3]], Name: content[m[4]:m[5]], CommitSha: content[m[6]:m[7]], RefLocation: &RefSpan{Start: m[2], End: m[7]}, } }
-
-
-
@@ -303,6 +303,67 @@ {Start: 22, End: 27},}, res) } func TestFindRenderizableCommitCrossReference(t *testing.T) { cases := []struct { Input string Expected *RenderizableReference }{ { Input: "", Expected: nil, }, { Input: "test", Expected: nil, }, { Input: "go-gitea/gitea@test", Expected: nil, }, { Input: "go-gitea/gitea@ab1234", Expected: nil, }, { Input: "go-gitea/gitea@abcd1234", Expected: &RenderizableReference{ Owner: "go-gitea", Name: "gitea", CommitSha: "abcd1234", RefLocation: &RefSpan{Start: 0, End: 23}, }, }, { Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd1234", Expected: &RenderizableReference{ Owner: "go-gitea", Name: "gitea", CommitSha: "abcd1234abcd1234abcd1234abcd1234abcd1234", RefLocation: &RefSpan{Start: 0, End: 55}, }, }, { Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd12340", // longer than 40 characters Expected: nil, }, { Input: "test go-gitea/gitea@abcd1234 test", Expected: &RenderizableReference{ Owner: "go-gitea", Name: "gitea", CommitSha: "abcd1234", RefLocation: &RefSpan{Start: 5, End: 28}, }, }, } for _, c := range cases { found, ref := FindRenderizableCommitCrossReference(c.Input) assert.Equal(t, ref != nil, found) assert.Equal(t, c.Expected, ref) } } func TestRegExp_mentionPattern(t *testing.T) { trueTestCases := []struct { pat string
-
-
-
@@ -30,7 +30,7 @@ "code.gitea.io/gitea/modules/util") // CreateRepositoryByExample creates a repository for the user/organization. func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) { func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) { if err = repo_model.IsUsableRepoName(repo.Name); err != nil { return err }
-
@@ -67,8 +67,12 @@ return err} // insert units for repo units := make([]repo_model.RepoUnit, 0, len(unit.DefaultRepoUnits)) for _, tp := range unit.DefaultRepoUnits { defaultUnits := unit.DefaultRepoUnits if isFork { defaultUnits = unit.DefaultForkRepoUnits } units := make([]repo_model.RepoUnit, 0, len(defaultUnits)) for _, tp := range defaultUnits { if tp == unit.TypeIssues { units = append(units, repo_model.RepoUnit{ RepoID: repo.ID,
-
@@ -212,7 +216,7 @@var rollbackRepo *repo_model.Repository if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { if err := CreateRepositoryByExample(ctx, doer, u, repo, false); err != nil { if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { return err }
-
-
-
@@ -27,6 +27,7 @@ EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"EnvPRID = "GITEA_PR_ID" EnvIsInternal = "GITEA_INTERNAL_PUSH" EnvAppURL = "GITEA_ROOT_URL" EnvActionPerm = "GITEA_ACTION_PERM" ) // InternalPushingEnvironment returns an os environment to switch off hooks on push
-
-
-
@@ -319,7 +319,7 @@ TemplateID: templateRepo.ID,TrustModel: templateRepo.TrustModel, } if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false); err != nil { if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { return nil, err }
-
-
-
@@ -316,14 +316,13 @@ log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)return fmt.Errorf("git add --all: %w", err) } cmd := git.NewCommand(ctx, "commit", git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email)), "-m", "Initial commit", ) cmd := git.NewCommand(ctx, "commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). AddOptionValues("-m", "Initial commit") sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) if sign { cmd.AddArguments(git.CmdArg("-S" + keyID)) cmd.AddOptionFormat("-S%s", keyID) if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { // need to set the committer to the KeyID owner
-
-
-
@@ -0,0 +1,29 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "code.gitea.io/gitea/modules/log" ) // Actions settings var ( Actions = struct { Storage // how the created logs should be stored Enabled bool DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"` }{ Enabled: false, DefaultActionsURL: "https://gitea.com", } ) func newActions() { sec := Cfg.Section("actions") if err := sec.MapTo(&Actions); err != nil { log.Fatal("Failed to map Actions settings: %v", err) } Actions.Storage = getStorage("actions_log", "", nil) }
-
-
-
@@ -27,6 +27,7 @@ LimitTotalOwnerCount int64LimitTotalOwnerSize int64 LimitSizeComposer int64 LimitSizeConan int64 LimitSizeConda int64 LimitSizeContainer int64 LimitSizeGeneric int64 LimitSizeHelm int64
-
@@ -66,6 +67,7 @@Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE") Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER") Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN") Packages.LimitSizeConda = mustBytes(sec, "LIMIT_SIZE_CONDA") Packages.LimitSizeContainer = mustBytes(sec, "LIMIT_SIZE_CONTAINER") Packages.LimitSizeGeneric = mustBytes(sec, "LIMIT_SIZE_GENERIC") Packages.LimitSizeHelm = mustBytes(sec, "LIMIT_SIZE_HELM")
-
-
-
@@ -41,6 +41,7 @@ EnablePushCreateUser boolEnablePushCreateOrg bool DisabledRepoUnits []string DefaultRepoUnits []string DefaultForkRepoUnits []string PrefixArchiveFiles bool DisableMigrations bool DisableStars bool `ini:"DISABLE_STARS"`
-
@@ -157,6 +158,7 @@ EnablePushCreateUser: false,EnablePushCreateOrg: false, DisabledRepoUnits: []string{}, DefaultRepoUnits: []string{}, DefaultForkRepoUnits: []string{}, PrefixArchiveFiles: true, DisableMigrations: false, DisableStars: false,
-
-
-
@@ -6,6 +6,7 @@ package settingimport ( "encoding/base64" "errors" "fmt" "math" "net"
-
@@ -240,7 +241,6 @@ CustomEmojis []stringCustomEmojisMap map[string]string `ini:"-"` SearchRepoDescription bool UseServiceWorker bool OnlyShowRelevantRepos bool Notification struct { MinTimeout time.Duration
-
@@ -466,8 +466,7 @@ appPath, err = exec.LookPath(os.Args[0])} if err != nil { // FIXME: Once we switch to go 1.19 use !errors.Is(err, exec.ErrDot) if !strings.Contains(err.Error(), "cannot run executable found relative to current directory") { if !errors.Is(err, exec.ErrDot) { return "", err } appPath, err = filepath.Abs(os.Args[0])
-
@@ -1074,6 +1073,8 @@ newPictureService()newPackages() newActions() if err = Cfg.Section("ui").MapTo(&UI); err != nil { log.Fatal("Failed to map UI settings: %v", err) } else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
-
@@ -1121,7 +1122,6 @@ UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true)UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) UI.SearchRepoDescription = Cfg.Section("ui").Key("SEARCH_REPO_DESCRIPTION").MustBool(true) UI.UseServiceWorker = Cfg.Section("ui").Key("USE_SERVICE_WORKER").MustBool(false) UI.OnlyShowRelevantRepos = Cfg.Section("ui").Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false) HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt")) if err != nil {
-
-
-
@@ -125,6 +125,9 @@ RepoArchives ObjectStorage = uninitializedStorage// Packages represents packages storage Packages ObjectStorage = uninitializedStorage // Actions represents actions storage Actions ObjectStorage = uninitializedStorage ) // Init init the stoarge
-
@@ -136,6 +139,7 @@ initRepoAvatars,initLFS, initRepoArchives, initPackages, initActions, } { if err := f(); err != nil { return err
-
@@ -204,3 +208,13 @@ log.Info("Initialising Packages storage with type: %s", setting.Packages.Storage.Type)Packages, err = NewStorage(setting.Packages.Storage.Type, &setting.Packages.Storage) return err } func initActions() (err error) { if !setting.Actions.Enabled { Actions = discardStorage("Actions isn't enabled") return nil } log.Info("Initialising Actions storage with type: %s", setting.Actions.Storage.Type) Actions, err = NewStorage(setting.Actions.Storage.Type, &setting.Actions.Storage) return err }
-
-
-
@@ -18,6 +18,8 @@ // CommitStatusFailure is for when the CommitStatus is FailureCommitStatusFailure CommitStatusState = "failure" // CommitStatusWarning is for when the CommitStatus is Warning CommitStatusWarning CommitStatusState = "warning" // CommitStatusRunning is for when the CommitStatus is Running CommitStatusRunning CommitStatusState = "running" ) // NoBetterThan returns true if this State is no better than the given State
-
-
-
@@ -472,6 +472,9 @@ util.PathEscapeSegments(baseRepo.DefaultBranch),curBranch, ) }, "RefShortName": func(ref string) string { return git.RefName(ref).ShortName() }, }} }
-
-
-
@@ -4,6 +4,7 @@package util import ( "errors" "io" )
-
@@ -17,3 +18,24 @@ err = nil} return n, err } // ErrNotEmpty is an error reported when there is a non-empty reader var ErrNotEmpty = errors.New("not-empty") // IsEmptyReader reads a reader and ensures it is empty func IsEmptyReader(r io.Reader) (err error) { var buf [1]byte for { n, err := r.Read(buf[:]) if err != nil { if err == io.EOF { return nil } return err } if n > 0 { return ErrNotEmpty } } }
-
-
-
@@ -111,6 +111,12 @@ never = Neverrss_feed = RSS Feed [aria] navbar = Navigation Bar footer = Footer footer.software = About Software footer.links = Links [filter] string.asc = A - Z string.desc = Z - A
-
@@ -322,6 +328,7 @@ email_not_associate = The email address is not associated with any account.send_reset_mail = Send Account Recovery Email reset_password = Account Recovery invalid_code = Your confirmation code is invalid or has expired. invalid_password = Your password does not match the password that was used to create the account. reset_password_helper = Recover Account reset_password_wrong_user = You are signed in as %s, but the account recovery link is for %s password_too_short = Password length cannot be less than %d characters.
-
@@ -511,6 +518,7 @@invalid_ssh_key = Cannot verify your SSH key: %s invalid_gpg_key = Cannot verify your GPG key: %s invalid_ssh_principal = Invalid principal: %s must_use_public_key = The key you provided is a private key. Please do not upload your private key anywhere. Use your public key instead. unable_verify_ssh_key = "Cannot verify the SSH key; double-check it for mistakes." auth_failed = Authentication failed: %v
-
@@ -1124,6 +1132,7 @@ editor.commit_directly_to_this_branch = Commit directly to the <strong class="branch-name">%s</strong> branch.editor.create_new_branch = Create a <strong>new branch</strong> for this commit and start a pull request. editor.create_new_branch_np = Create a <strong>new branch</strong> for this commit. editor.propose_file_change = Propose file change editor.new_branch_name = Name the new branch for this commit editor.new_branch_name_desc = New branch name… editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty.
-
@@ -1296,6 +1305,9 @@ issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> to exclude labels`issues.filter_label_no_select = All labels issues.filter_milestone = Milestone issues.filter_milestone_no_select = All milestones issues.filter_project = Project issues.filter_project_all = All projects issues.filter_project_none = No project issues.filter_assignee = Assignee issues.filter_assginee_no_select = All assignees issues.filter_poster = Author
-
@@ -1327,6 +1339,8 @@ issues.action_milestone = Milestoneissues.action_milestone_no_select = No milestone issues.action_assignee = Assignee issues.action_assignee_no_select = No assignee issues.action_check = Check/Uncheck issues.action_check_all = Check/Uncheck all items issues.opened_by = opened %[1]s by <a href="%[2]s">%[3]s</a> pulls.merged_by = by <a href="%[2]s">%[3]s</a> was merged %[1]s pulls.merged_by_fake = by %[2]s was merged %[1]s
-
@@ -1861,8 +1875,10 @@ settings.pulls.allow_manual_merge = Enable Mark PR as manually mergedsettings.pulls.enable_autodetect_manual_merge = Enable autodetect manual merge (Note: In some special cases, misjudgments can occur) settings.pulls.allow_rebase_update = Enable updating pull request branch by rebase settings.pulls.default_delete_branch_after_merge = Delete pull request branch after merge by default settings.releases_desc = Enable Repository Releases settings.packages_desc = Enable Repository Packages Registry settings.projects_desc = Enable Repository Projects settings.actions_desc = Enable Repository Actions settings.admin_settings = Administrator Settings settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) settings.admin_code_indexer = Code Indexer
-
@@ -2418,7 +2434,7 @@ teams.leave.detail = Leave %s?teams.can_create_org_repo = Create repositories teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository. teams.none_access = No Access teams.none_access_helper = Members cannot view or do any other action on this unit. teams.none_access_helper = Members cannot view or do any other action on this unit. It has no effect for public repositories. teams.general_access = General Access teams.general_access_helper = Members permissions will be decided by below permission table. teams.read_access = Read
-
@@ -2557,6 +2573,9 @@ dashboard.delete_old_actions.started = Delete all old actions from database started.dashboard.update_checker = Update checker dashboard.delete_old_system_notices = Delete all old system notices from database dashboard.gc_lfs = Garbage collect LFS meta objects dashboard.stop_zombie_tasks = Stop zombie tasks dashboard.stop_endless_tasks = Stop endless tasks dashboard.cancel_abandoned_jobs = Cancel abandoned jobs users.user_manage_panel = User Account Management users.new_account = Create User Account
-
@@ -3142,6 +3161,11 @@ conan.details.repository = Repositoryconan.registry = Setup this registry from the command line: conan.install = To install the package using Conan, run the following command: conan.documentation = For more information on the Conan registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">the documentation</a>. conda.registry = Setup this registry as a Conda repository in your <code>.condarc</code> file: conda.install = To install the package using Conda, run the following command: conda.documentation = For more information on the Conda registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conda/">the documentation</a>. conda.details.repository_site = Repository Site conda.details.documentation_site = Documentation Site container.details.type = Image Type container.details.platform = Platform container.details.repository_site = Repository Site
-
@@ -3237,6 +3261,62 @@ creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.creation.success = The secret '%s' has been added. creation.failed = Failed to add secret. deletion = Remove secret deletion.description = Removing a secret will revoke its access to repositories. Continue? deletion.description = Removing a secret is permanent and cannot be undone. Continue? deletion.success = The secret has been removed. deletion.failed = Failed to remove secret. [actions] actions = Actions unit.desc = Manage actions status.unknown = "Unknown" status.waiting = "Waiting" status.running = "Running" status.success = "Success" status.failure = "Failure" status.cancelled = "Cancelled" status.skipped = "Skipped" status.blocked = "Blocked" runners = Runners runners.runner_manage_panel = Runners Management runners.new = Create new Runner runners.new_notice = How to start a runner runners.status = Status runners.id = ID runners.name = Name runners.owner_type = Type runners.description = Description runners.labels = Labels runners.last_online = Last Online Time runners.agent_labels = Agent Labels runners.custom_labels = Custom Labels runners.custom_labels_helper = Custom labels are labels that are added manually by an administrator. A comma separates labels, whitespace at the start and end of each label is ignored. runners.runner_title = Runner runners.task_list = Recent tasks on this runner runners.task_list.run = Run runners.task_list.status = Status runners.task_list.repository = Repository runners.task_list.commit = Commit runners.task_list.done_at = Done At runners.edit_runner = Edit Runner runners.update_runner = Update Changes runners.update_runner_success = Runner updated successfully runners.update_runner_failed = Failed to update runner runners.delete_runner = Delete this runner runners.delete_runner_success = Runner deleted successfully runners.delete_runner_failed = Failed to delete runner runners.delete_runner_header = Confirm to delete this runner runners.delete_runner_notice = If a task is running on this runner, it will be terminated and mark as failed. It may break building workflow. runners.none = No runners available runners.status.unspecified = Unknown runners.status.idle = Idle runners.status.active = Active runners.status.offline = Offline runs.all_workflows = All Workflows runs.open_tab = %d Open runs.closed_tab = %d Closed runs.commit = Commit runs.pushed_by = Pushed by
-
-
-
@@ -16,6 +16,7 @@ "@mcaptcha/vanilla-glue": "0.1.0-alpha-3","@primer/octicons": "17.10.2", "@vue/compiler-sfc": "3.2.45", "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.0.1", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2",
-
@@ -2012,6 +2013,28 @@ "node": ">=8"}, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/ansi-to-html": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz", "integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==", "dependencies": { "entities": "^2.2.0" }, "bin": { "ansi-to-html": "bin/ansi-to-html" }, "engines": { "node": ">=8.0.0" } }, "node_modules/ansi-to-html/node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/argparse": {
-
@@ -11284,6 +11307,21 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz","integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" } }, "ansi-to-html": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz", "integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==", "requires": { "entities": "^2.2.0" }, "dependencies": { "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" } } }, "argparse": {
-
-
-
@@ -16,6 +16,7 @@ "@mcaptcha/vanilla-glue": "0.1.0-alpha-3","@primer/octicons": "17.10.2", "@vue/compiler-sfc": "3.2.45", "add-asset-webpack-plugin": "2.0.1", "ansi-to-html": "0.7.2", "asciinema-player": "3.0.1", "css-loader": "6.7.3", "dropzone": "6.0.0-beta.2",
-
-
-
@@ -0,0 +1,1 @@<svg viewBox="0 0 32 32" class="svg gitea-conda" width="16" height="16" aria-hidden="true"><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068" d="M16.559 8.137a7.2 7.2 0 0 0-1.234-1.708 7.586 7.586 0 0 0-.19 2.183 5.161 5.161 0 0 1 1.424-.475ZM13.617 9.466a7.992 7.992 0 0 0-1.993-1.2 8.123 8.123 0 0 0 .885 2.183c0 .063.443-.475 1.108-.981ZM17.445 7.188a9.143 9.143 0 0 1 1.3-2.246A7.585 7.585 0 0 0 17 2.854a8.35 8.35 0 0 0-1.3 2.278 8.451 8.451 0 0 1 1.74 2.056ZM11.592 11.744a10.276 10.276 0 0 0-2.692-.158 7.478 7.478 0 0 0 1.93 1.9 6.858 6.858 0 0 1 .759-1.74zM6.878 15.161a7.44 7.44 0 0 1 2.942-1.139 10.019 10.019 0 0 1-2.056-2.278 7.639 7.639 0 0 0-2.847 1.2 7.11 7.11 0 0 0 1.961 2.215zM10.516 14.876a6.16 6.16 0 0 0-2.815.886 9.936 9.936 0 0 0 2.815 1.2 7.683 7.683 0 0 1 0-2.088zM14.281 5.543A7.839 7.839 0 0 0 11.592 4.4 8.361 8.361 0 0 0 11.4 7a8.875 8.875 0 0 1 2.47 1.264 10.292 10.292 0 0 1 .411-2.721ZM24.025 3.234a20.488 20.488 0 0 1 .917 4.112 6.823 6.823 0 0 0-3.068 1.519 7.443 7.443 0 0 1 1.55 1.044 1.351 1.351 0 0 0 1.645.316 36.938 36.938 0 0 0 2.721-2.72 1.273 1.273 0 0 0-.159-1.835 20.521 20.521 0 0 0-3.606-2.436ZM4.379 12.06a8.67 8.67 0 0 1 2.847-1.26 7.763 7.763 0 0 1-.759-2.974 14.687 14.687 0 0 0-2.088 4.234ZM11.339 10.668a9.991 9.991 0 0 1-.949-2.784 7.928 7.928 0 0 0-2.911-.126 7.312 7.312 0 0 0 .791 2.879 9.664 9.664 0 0 1 3.069.031ZM6.119 15.73a8.894 8.894 0 0 1-2.025-2.373 14.208 14.208 0 0 0-.063 4.9 8.522 8.522 0 0 1 2.088-2.527Z"/><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068" d="M22.538 3.487A7.581 7.581 0 0 0 20.323 5.1a11.789 11.789 0 0 1 .823 2.5 9.775 9.775 0 0 1 2.309-1.329 6.593 6.593 0 0 0-.917-2.784ZM19.374 6.3a8.608 8.608 0 0 0-.822 1.676 9.645 9.645 0 0 1 1.329.19 7.568 7.568 0 0 0-.507-1.866ZM19.659 3.9a9.577 9.577 0 0 1 2.056-1.487A15.38 15.38 0 0 0 18.046 2a9.709 9.709 0 0 1 1.613 1.9Z"/><path fill="#43b02a" d="M27.378 23.892c-1.993-1.9-2.4-3.132-4.081-1.835a7.837 7.837 0 0 1-12.591-4.144A10.179 10.179 0 0 1 6.878 16.3a9.427 9.427 0 0 0-2.562 3.321h-.032C7.163 30.5 21.178 33.035 27.663 26.233c1.076-1.139.095-1.933-.285-2.341ZM6.309 20.855a7.559 7.559 0 0 1 .917-2.025 6.872 6.872 0 0 0 2.151.538c1.013 2.689 4.556 6.264 8.922 6.264a9.632 9.632 0 0 0 6.3-2.309 12.841 12.841 0 0 1 1.772 1.771c.095.127.095.159.095.159-5.766 5.03-15.538 4.302-20.157-4.398Z"/><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".067" d="M10.67 4.11a19.934 19.934 0 0 0-.214 2.509 10.512 10.512 0 0 0-2.689-.093A18 18 0 0 1 10.67 4.11ZM12.26 3.274a9.107 9.107 0 0 1 2.445 1.053 14.083 14.083 0 0 1 1.253-2.137 12.106 12.106 0 0 0-3.698 1.084z"/></svg>
-
-
-
@@ -0,0 +1,25 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "net/http" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/actions/ping" "code.gitea.io/gitea/routers/api/actions/runner" ) func Routes(_ context.Context, prefix string) *web.Route { m := web.NewRoute() path, handler := ping.NewPingServiceHandler() m.Post(path+"*", http.StripPrefix(prefix, handler).ServeHTTP) path, handler = runner.NewRunnerServiceHandler() m.Post(path+"*", http.StripPrefix(prefix, handler).ServeHTTP) return m }
-
-
-
@@ -0,0 +1,38 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package ping import ( "context" "fmt" "net/http" "code.gitea.io/gitea/modules/log" pingv1 "code.gitea.io/actions-proto-go/ping/v1" "code.gitea.io/actions-proto-go/ping/v1/pingv1connect" "github.com/bufbuild/connect-go" ) func NewPingServiceHandler() (string, http.Handler) { return pingv1connect.NewPingServiceHandler(&Service{}) } var _ pingv1connect.PingServiceHandler = (*Service)(nil) type Service struct { pingv1connect.UnimplementedPingServiceHandler } func (s *Service) Ping( ctx context.Context, req *connect.Request[pingv1.PingRequest], ) (*connect.Response[pingv1.PingResponse], error) { log.Trace("Content-Type: %s", req.Header().Get("Content-Type")) log.Trace("User-Agent: %s", req.Header().Get("User-Agent")) res := connect.NewResponse(&pingv1.PingResponse{ Data: fmt.Sprintf("Hello, %s!", req.Msg.Data), }) return res, nil }
-
-
-
@@ -0,0 +1,61 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package ping import ( "context" "net/http" "net/http/httptest" "testing" pingv1 "code.gitea.io/actions-proto-go/ping/v1" "code.gitea.io/actions-proto-go/ping/v1/pingv1connect" "github.com/bufbuild/connect-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestService(t *testing.T) { mux := http.NewServeMux() mux.Handle(pingv1connect.NewPingServiceHandler( &Service{}, )) MainServiceTest(t, mux) } func MainServiceTest(t *testing.T, h http.Handler) { t.Parallel() server := httptest.NewUnstartedServer(h) server.EnableHTTP2 = true server.StartTLS() defer server.Close() connectClient := pingv1connect.NewPingServiceClient( server.Client(), server.URL, ) grpcClient := pingv1connect.NewPingServiceClient( server.Client(), server.URL, connect.WithGRPC(), ) grpcWebClient := pingv1connect.NewPingServiceClient( server.Client(), server.URL, connect.WithGRPCWeb(), ) clients := []pingv1connect.PingServiceClient{connectClient, grpcClient, grpcWebClient} t.Run("ping request", func(t *testing.T) { for _, client := range clients { result, err := client.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{ Data: "foobar", })) require.NoError(t, err) assert.Equal(t, "Hello, foobar!", result.Msg.Data) } }) }
-
-
-
@@ -0,0 +1,79 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package runner import ( "context" "crypto/subtle" "errors" "strings" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "github.com/bufbuild/connect-go" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) const ( uuidHeaderKey = "x-runner-uuid" tokenHeaderKey = "x-runner-token" ) var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unaryFunc connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) { methodName := getMethodName(request) if methodName == "Register" { return unaryFunc(ctx, request) } uuid := request.Header().Get(uuidHeaderKey) token := request.Header().Get(tokenHeaderKey) runner, err := actions_model.GetRunnerByUUID(ctx, uuid) if err != nil { if errors.Is(err, util.ErrNotExist) { return nil, status.Error(codes.Unauthenticated, "unregistered runner") } return nil, status.Error(codes.Internal, err.Error()) } if subtle.ConstantTimeCompare([]byte(runner.TokenHash), []byte(auth_model.HashToken(token, runner.TokenSalt))) != 1 { return nil, status.Error(codes.Unauthenticated, "unregistered runner") } cols := []string{"last_online"} runner.LastOnline = timeutil.TimeStampNow() if methodName == "UpdateTask" || methodName == "UpdateLog" { runner.LastActive = timeutil.TimeStampNow() cols = append(cols, "last_active") } if err := actions_model.UpdateRunner(ctx, runner, cols...); err != nil { log.Error("can't update runner status: %v", err) } ctx = context.WithValue(ctx, runnerCtxKey{}, runner) return unaryFunc(ctx, request) } })) func getMethodName(req connect.AnyRequest) string { splits := strings.Split(req.Spec().Procedure, "/") if len(splits) > 0 { return splits[len(splits)-1] } return "" } type runnerCtxKey struct{} func GetRunner(ctx context.Context) *actions_model.ActionRunner { if v := ctx.Value(runnerCtxKey{}); v != nil { if r, ok := v.(*actions_model.ActionRunner); ok { return r } } return nil }
-
-
-
@@ -0,0 +1,221 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package runner import ( "context" "errors" "net/http" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" actions_service "code.gitea.io/gitea/services/actions" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect" "github.com/bufbuild/connect-go" gouuid "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func NewRunnerServiceHandler() (string, http.Handler) { return runnerv1connect.NewRunnerServiceHandler( &Service{}, connect.WithCompressMinBytes(1024), withRunner, ) } var _ runnerv1connect.RunnerServiceClient = (*Service)(nil) type Service struct { runnerv1connect.UnimplementedRunnerServiceHandler } // Register for new runner. func (s *Service) Register( ctx context.Context, req *connect.Request[runnerv1.RegisterRequest], ) (*connect.Response[runnerv1.RegisterResponse], error) { if req.Msg.Token == "" || req.Msg.Name == "" { return nil, errors.New("missing runner token, name") } runnerToken, err := actions_model.GetRunnerToken(ctx, req.Msg.Token) if err != nil { return nil, errors.New("runner token not found") } if runnerToken.IsActive { return nil, errors.New("runner token has already activated") } // create new runner runner := &actions_model.ActionRunner{ UUID: gouuid.New().String(), Name: req.Msg.Name, OwnerID: runnerToken.OwnerID, RepoID: runnerToken.RepoID, AgentLabels: req.Msg.AgentLabels, CustomLabels: req.Msg.CustomLabels, } if err := runner.GenerateToken(); err != nil { return nil, errors.New("can't generate token") } // create new runner if err := actions_model.CreateRunner(ctx, runner); err != nil { return nil, errors.New("can't create new runner") } // update token status runnerToken.IsActive = true if err := actions_model.UpdateRunnerToken(ctx, runnerToken, "is_active"); err != nil { return nil, errors.New("can't update runner token status") } res := connect.NewResponse(&runnerv1.RegisterResponse{ Runner: &runnerv1.Runner{ Id: runner.ID, Uuid: runner.UUID, Token: runner.Token, Name: runner.Name, AgentLabels: runner.AgentLabels, CustomLabels: runner.CustomLabels, }, }) return res, nil } // FetchTask assigns a task to the runner func (s *Service) FetchTask( ctx context.Context, req *connect.Request[runnerv1.FetchTaskRequest], ) (*connect.Response[runnerv1.FetchTaskResponse], error) { runner := GetRunner(ctx) var task *runnerv1.Task if t, ok, err := pickTask(ctx, runner); err != nil { log.Error("pick task failed: %v", err) return nil, status.Errorf(codes.Internal, "pick task: %v", err) } else if ok { task = t } res := connect.NewResponse(&runnerv1.FetchTaskResponse{ Task: task, }) return res, nil } // UpdateTask updates the task status. func (s *Service) UpdateTask( ctx context.Context, req *connect.Request[runnerv1.UpdateTaskRequest], ) (*connect.Response[runnerv1.UpdateTaskResponse], error) { { // to debug strange runner behaviors, it could be removed if all problems have been solved. stateMsg, _ := json.Marshal(req.Msg.State) log.Trace("update task with state: %s", stateMsg) } // Get Task first task, err := actions_model.GetTaskByID(ctx, req.Msg.State.Id) if err != nil { return nil, status.Errorf(codes.Internal, "can't find the task: %v", err) } if task.Status.IsCancelled() { return connect.NewResponse(&runnerv1.UpdateTaskResponse{ State: &runnerv1.TaskState{ Id: req.Msg.State.Id, Result: task.Status.AsResult(), }, }), nil } task, err = actions_model.UpdateTaskByState(ctx, req.Msg.State) if err != nil { return nil, status.Errorf(codes.Internal, "update task: %v", err) } if err := task.LoadJob(ctx); err != nil { return nil, status.Errorf(codes.Internal, "load job: %v", err) } if err := actions_service.CreateCommitStatus(ctx, task.Job); err != nil { log.Error("Update commit status failed: %v", err) // go on } if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED { if err := actions_service.EmitJobsIfReady(task.Job.RunID); err != nil { log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err) } } return connect.NewResponse(&runnerv1.UpdateTaskResponse{ State: &runnerv1.TaskState{ Id: req.Msg.State.Id, Result: task.Status.AsResult(), }, }), nil } // UpdateLog uploads log of the task. func (s *Service) UpdateLog( ctx context.Context, req *connect.Request[runnerv1.UpdateLogRequest], ) (*connect.Response[runnerv1.UpdateLogResponse], error) { res := connect.NewResponse(&runnerv1.UpdateLogResponse{}) task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId) if err != nil { return nil, status.Errorf(codes.Internal, "get task: %v", err) } ack := task.LogLength if len(req.Msg.Rows) == 0 || req.Msg.Index > ack || int64(len(req.Msg.Rows))+req.Msg.Index <= ack { res.Msg.AckIndex = ack return res, nil } if task.LogInStorage { return nil, status.Errorf(codes.AlreadyExists, "log file has been archived") } rows := req.Msg.Rows[ack-req.Msg.Index:] ns, err := actions.WriteLogs(ctx, task.LogFilename, task.LogSize, rows) if err != nil { return nil, status.Errorf(codes.Internal, "write logs: %v", err) } task.LogLength += int64(len(rows)) for _, n := range ns { task.LogIndexes = append(task.LogIndexes, task.LogSize) task.LogSize += int64(n) } res.Msg.AckIndex = task.LogLength var remove func() if req.Msg.NoMore { task.LogInStorage = true remove, err = actions.TransferLogs(ctx, task.LogFilename) if err != nil { return nil, status.Errorf(codes.Internal, "transfer logs: %v", err) } } if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_length", "log_size", "log_in_storage"); err != nil { return nil, status.Errorf(codes.Internal, "update task: %v", err) } if remove != nil { remove() } return res, nil }
-
-
-
@@ -0,0 +1,122 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package runner import ( "context" "fmt" actions_model "code.gitea.io/gitea/models/actions" secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" secret_module "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "google.golang.org/protobuf/types/known/structpb" ) func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv1.Task, bool, error) { t, ok, err := actions_model.CreateTaskForRunner(ctx, runner) if err != nil { return nil, false, fmt.Errorf("CreateTaskForRunner: %w", err) } if !ok { return nil, false, nil } task := &runnerv1.Task{ Id: t.ID, WorkflowPayload: t.Job.WorkflowPayload, Context: generateTaskContext(t), Secrets: getSecretsOfTask(ctx, t), } return task, true, nil } func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string { secrets := map[string]string{} if task.Job.Run.IsForkPullRequest { // ignore secrets for fork pull request return secrets } ownerSecrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID}) if err != nil { log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err) // go on } repoSecrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{RepoID: task.Job.Run.RepoID}) if err != nil { log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err) // go on } for _, secret := range append(ownerSecrets, repoSecrets...) { if v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data); err != nil { log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) // go on } else { secrets[secret.Name] = v } } if _, ok := secrets["GITHUB_TOKEN"]; !ok { secrets["GITHUB_TOKEN"] = task.Token } if _, ok := secrets["GITEA_TOKEN"]; !ok { secrets["GITEA_TOKEN"] = task.Token } return secrets } func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { event := map[string]interface{}{} _ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event) taskContext, _ := structpb.NewStruct(map[string]interface{}{ // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context "action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. "action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action. "action_ref": "", // string, For a step executing an action, this is the ref of the action being executed. For example, v2. "action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. "action_status": "", // string, For a composite action, the current result of the composite action. "actor": t.Job.Run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. "api_url": "", // string, The URL of the GitHub REST API. "base_ref": "", // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. "env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." "event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload. "event_name": t.Job.Run.Event.Event(), // string, The name of the event that triggered the workflow run. "event_path": "", // string, The path to the file on the runner that contains the full event webhook payload. "graphql_url": "", // string, The URL of the GitHub GraphQL API. "head_ref": "", // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. "job": fmt.Sprint(t.JobID), // string, The job_id of the current job. "ref": t.Job.Run.Ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge, and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1. "ref_name": t.Job.Run.Ref, // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run. "ref_type": "", // string, The type of ref that triggered the workflow run. Valid values are branch or tag. "path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." "repository": t.Job.Run.Repo.OwnerName + "/" + t.Job.Run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World. "repository_owner": t.Job.Run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat. "repositoryUrl": t.Job.Run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git. "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept. "run_id": fmt.Sprint(t.Job.RunID), // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. "run_number": fmt.Sprint(t.Job.Run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. "run_attempt": fmt.Sprint(t.Job.Attempt), // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. "sha": t.Job.Run.CommitSHA, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53. "token": t.Token, // string, A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see "Automatic token authentication." "triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. "workflow": t.Job.Run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository. "workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action. // additional contexts "gitea_default_actions_url": setting.Actions.DefaultActionsURL, }) return taskContext }
-
-
-
@@ -16,6 +16,7 @@ "code.gitea.io/gitea/modules/setting""code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/packages/composer" "code.gitea.io/gitea/routers/api/packages/conan" "code.gitea.io/gitea/routers/api/packages/conda" "code.gitea.io/gitea/routers/api/packages/container" "code.gitea.io/gitea/routers/api/packages/generic" "code.gitea.io/gitea/routers/api/packages/helm"
-
@@ -165,6 +166,43 @@ })}) }, conan.ExtractPathParameters) }) }) }, reqPackageAccess(perm.AccessModeRead)) r.Group("/conda", func() { var ( downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`) uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`) ) r.Get("/*", func(ctx *context.Context) { m := downloadPattern.FindStringSubmatch(ctx.Params("*")) if len(m) == 0 { ctx.Status(http.StatusNotFound) return } ctx.SetParams("channel", strings.TrimSuffix(m[1], "/")) ctx.SetParams("architecture", m[2]) ctx.SetParams("filename", m[3]) switch m[3] { case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2": conda.EnumeratePackages(ctx) default: conda.DownloadPackageFile(ctx) } }) r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) { m := uploadPattern.FindStringSubmatch(ctx.Params("*")) if len(m) == 0 { ctx.Status(http.StatusNotFound) return } ctx.SetParams("channel", strings.TrimSuffix(m[1], "/")) ctx.SetParams("filename", m[2]) conda.UploadPackageFile(ctx) }) }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() {
-
-
-
@@ -0,0 +1,306 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package conda import ( "errors" "fmt" "io" "net/http" "strings" packages_model "code.gitea.io/gitea/models/packages" conda_model "code.gitea.io/gitea/models/packages/conda" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" conda_module "code.gitea.io/gitea/modules/packages/conda" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/packages/helper" packages_service "code.gitea.io/gitea/services/packages" "github.com/dsnet/compress/bzip2" ) func apiError(ctx *context.Context, status int, obj interface{}) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, struct { Reason string `json:"reason"` Message string `json:"message"` }{ Reason: http.StatusText(status), Message: message, }) }) } func EnumeratePackages(ctx *context.Context) { type Info struct { Subdir string `json:"subdir"` } type PackageInfo struct { Name string `json:"name"` Version string `json:"version"` NoArch string `json:"noarch"` Subdir string `json:"subdir"` Timestamp int64 `json:"timestamp"` Build string `json:"build"` BuildNumber int64 `json:"build_number"` Dependencies []string `json:"depends"` License string `json:"license"` LicenseFamily string `json:"license_family"` HashMD5 string `json:"md5"` HashSHA256 string `json:"sha256"` Size int64 `json:"size"` } type RepoData struct { Info Info `json:"info"` Packages map[string]*PackageInfo `json:"packages"` PackagesConda map[string]*PackageInfo `json:"packages.conda"` Removed map[string]*PackageInfo `json:"removed"` } repoData := &RepoData{ Info: Info{ Subdir: ctx.Params("architecture"), }, Packages: make(map[string]*PackageInfo), PackagesConda: make(map[string]*PackageInfo), Removed: make(map[string]*PackageInfo), } pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{ OwnerID: ctx.Package.Owner.ID, Channel: ctx.Params("channel"), Subdir: repoData.Info.Subdir, }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } if len(pfs) == 0 { apiError(ctx, http.StatusNotFound, nil) return } pds := make(map[int64]*packages_model.PackageDescriptor) for _, pf := range pfs { pd, exists := pds[pf.VersionID] if !exists { pv, err := packages_model.GetVersionByID(ctx, pf.VersionID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } pd, err = packages_model.GetPackageDescriptor(ctx, pv) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } pds[pf.VersionID] = pd } var pfd *packages_model.PackageFileDescriptor for _, d := range pd.Files { if d.File.ID == pf.ID { pfd = d break } } var fileMetadata *conda_module.FileMetadata if err := json.Unmarshal([]byte(pfd.Properties.GetByName(conda_module.PropertyMetadata)), &fileMetadata); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } versionMetadata := pd.Metadata.(*conda_module.VersionMetadata) pi := &PackageInfo{ Name: pd.PackageProperties.GetByName(conda_module.PropertyName), Version: pd.Version.Version, NoArch: fileMetadata.NoArch, Subdir: repoData.Info.Subdir, Timestamp: fileMetadata.Timestamp, Build: fileMetadata.Build, BuildNumber: fileMetadata.BuildNumber, Dependencies: fileMetadata.Dependencies, License: versionMetadata.License, LicenseFamily: versionMetadata.LicenseFamily, HashMD5: pfd.Blob.HashMD5, HashSHA256: pfd.Blob.HashSHA256, Size: pfd.Blob.Size, } if fileMetadata.IsCondaPackage { repoData.PackagesConda[pfd.File.Name] = pi } else { repoData.Packages[pfd.File.Name] = pi } } resp := ctx.Resp var w io.Writer = resp if strings.HasSuffix(ctx.Params("filename"), ".json") { resp.Header().Set("Content-Type", "application/json") } else { resp.Header().Set("Content-Type", "application/x-bzip2") zw, err := bzip2.NewWriter(w, nil) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } defer zw.Close() w = zw } resp.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(repoData); err != nil { log.Error("JSON encode: %v", err) } } func UploadPackageFile(ctx *context.Context) { upload, close, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } if close { defer upload.Close() } buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } defer buf.Close() var pck *conda_module.Package if strings.HasSuffix(strings.ToLower(ctx.Params("filename")), ".tar.bz2") { pck, err = conda_module.ParsePackageBZ2(buf) } else { pck, err = conda_module.ParsePackageConda(buf, buf.Size()) } if err != nil { if errors.Is(err, util.ErrInvalidArgument) { apiError(ctx, http.StatusBadRequest, err) } else { apiError(ctx, http.StatusInternalServerError, err) } return } if _, err := buf.Seek(0, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } fullName := pck.Name channel := ctx.Params("channel") if channel != "" { fullName = channel + "/" + pck.Name } extension := ".tar.bz2" if pck.FileMetadata.IsCondaPackage { extension = ".conda" } fileMetadataRaw, err := json.Marshal(pck.FileMetadata) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } _, _, err = packages_service.CreatePackageOrAddFileToExisting( &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: ctx.Package.Owner, PackageType: packages_model.TypeConda, Name: fullName, Version: pck.Version, }, SemverCompatible: false, Creator: ctx.Doer, Metadata: pck.VersionMetadata, PackageProperties: map[string]string{ conda_module.PropertyName: pck.Name, conda_module.PropertyChannel: channel, }, }, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ Filename: fmt.Sprintf("%s-%s-%s%s", pck.Name, pck.Version, pck.FileMetadata.Build, extension), CompositeKey: pck.Subdir, }, Creator: ctx.Doer, Data: buf, IsLead: true, Properties: map[string]string{ conda_module.PropertySubdir: pck.Subdir, conda_module.PropertyMetadata: string(fileMetadataRaw), }, }, ) if err != nil { switch err { case packages_model.ErrDuplicatePackageFile: apiError(ctx, http.StatusConflict, err) case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) } return } ctx.Status(http.StatusCreated) } func DownloadPackageFile(ctx *context.Context) { pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{ OwnerID: ctx.Package.Owner.ID, Channel: ctx.Params("channel"), Subdir: ctx.Params("architecture"), Filename: ctx.Params("filename"), }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } if len(pfs) != 1 { apiError(ctx, http.StatusNotFound, nil) return } pf := pfs[0] s, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } defer s.Close() ctx.ServeContent(s, &context.ServeHeaderOptions{ Filename: pf.Name, LastModified: pf.CreatedUnix.AsLocalTime(), }) }
-
-
-
@@ -26,14 +26,18 @@ var uploadVersionMutex sync.Mutex// saveAsPackageBlob creates a package blob from an upload // The uploaded blob gets stored in a special upload version to link them to the package/image func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) { func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { if err := packages_service.CheckSizeQuotaExceeded(db.DefaultContext, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil { return nil, err } pb := packages_service.NewPackageBlob(hsr) exists := false contentStore := packages_module.NewContentStore() uploadVersion, err := getOrCreateUploadVersion(pi) uploadVersion, err := getOrCreateUploadVersion(&pci.PackageInfo) if err != nil { return nil, err }
-
-
-
@@ -227,8 +227,22 @@ apiErrorDefined(ctx, errDigestInvalid)return } if _, err := saveAsPackageBlob(buf, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil { apiError(ctx, http.StatusInternalServerError, err) if _, err := saveAsPackageBlob( buf, &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: ctx.Package.Owner, Name: image, }, Creator: ctx.Doer, }, ); err != nil { switch err { case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) } return }
-
@@ -358,8 +372,22 @@ apiErrorDefined(ctx, errDigestInvalid)return } if _, err := saveAsPackageBlob(uploader, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil { apiError(ctx, http.StatusInternalServerError, err) if _, err := saveAsPackageBlob( uploader, &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: ctx.Package.Owner, Name: image, }, Creator: ctx.Doer, }, ); err != nil { switch err { case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) } return }
-
@@ -526,7 +554,12 @@ apiErrorDefined(ctx, namedError)} else if errors.Is(err, container_model.ErrContainerBlobNotExist) { apiErrorDefined(ctx, errBlobUnknown) } else { apiError(ctx, http.StatusInternalServerError, err) switch err { case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) } } return }
-
-
-
@@ -327,6 +327,10 @@ return nil, err} } if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil { return nil, err } if mci.IsTagged { if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil { log.Error("Error setting package version property: %v", err)
-
-
-
@@ -0,0 +1,174 @@// Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package admin import ( "net/http" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" webhook_service "code.gitea.io/gitea/services/webhook" ) // ListHooks list system's webhooks func ListHooks(ctx *context.APIContext) { // swagger:operation GET /admin/hooks admin adminListHooks // --- // summary: List system's webhooks // produces: // - application/json // parameters: // - name: page // in: query // description: page number of results to return (1-based) // type: integer // - name: limit // in: query // description: page size of results // type: integer // responses: // "200": // "$ref": "#/responses/HookList" sysHooks, err := webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone) if err != nil { ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err) return } hooks := make([]*api.Hook, len(sysHooks)) for i, hook := range sysHooks { h, err := webhook_service.ToHook(setting.AppURL+"/admin", hook) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) return } hooks[i] = h } ctx.JSON(http.StatusOK, hooks) } // GetHook get an organization's hook by id func GetHook(ctx *context.APIContext) { // swagger:operation GET /admin/hooks/{id} admin adminGetHook // --- // summary: Get a hook // produces: // - application/json // parameters: // - name: id // in: path // description: id of the hook to get // type: integer // format: int64 // required: true // responses: // "200": // "$ref": "#/responses/Hook" hookID := ctx.ParamsInt64(":id") hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) return } h, err := webhook_service.ToHook("/admin/", hook) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) return } ctx.JSON(http.StatusOK, h) } // CreateHook create a hook for an organization func CreateHook(ctx *context.APIContext) { // swagger:operation POST /admin/hooks admin adminCreateHook // --- // summary: Create a hook // consumes: // - application/json // produces: // - application/json // parameters: // - name: body // in: body // required: true // schema: // "$ref": "#/definitions/CreateHookOption" // responses: // "201": // "$ref": "#/responses/Hook" form := web.GetForm(ctx).(*api.CreateHookOption) // TODO in body params if !utils.CheckCreateHookOption(ctx, form) { return } utils.AddSystemHook(ctx, form) } // EditHook modify a hook of a repository func EditHook(ctx *context.APIContext) { // swagger:operation PATCH /admin/hooks/{id} admin adminEditHook // --- // summary: Update a hook // consumes: // - application/json // produces: // - application/json // parameters: // - name: id // in: path // description: id of the hook to update // type: integer // format: int64 // required: true // - name: body // in: body // schema: // "$ref": "#/definitions/EditHookOption" // responses: // "200": // "$ref": "#/responses/Hook" form := web.GetForm(ctx).(*api.EditHookOption) // TODO in body params hookID := ctx.ParamsInt64(":id") utils.EditSystemHook(ctx, form, hookID) } // DeleteHook delete a system hook func DeleteHook(ctx *context.APIContext) { // swagger:operation DELETE /amdin/hooks/{id} admin adminDeleteHook // --- // summary: Delete a hook // produces: // - application/json // parameters: // - name: id // in: path // description: id of the hook to delete // type: integer // format: int64 // required: true // responses: // "204": // "$ref": "#/responses/empty" hookID := ctx.ParamsInt64(":id") if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil { if webhook.IsErrWebhookNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "DeleteDefaultSystemWebhook", err) } return } ctx.Status(http.StatusNoContent) }
-
-
-
@@ -69,6 +69,7 @@ "fmt""net/http" "strings" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm"
-
@@ -184,10 +185,39 @@repo.Owner = owner ctx.Repo.Repository = repo ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID { taskID := ctx.Data["ActionsTaskID"].(int64) task, err := actions_model.GetTaskByID(ctx, taskID) if err != nil { ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err) return } if task.RepoID != repo.ID { ctx.NotFound() return } if task.IsForkPullRequest { ctx.Repo.Permission.AccessMode = perm.AccessModeRead } else { ctx.Repo.Permission.AccessMode = perm.AccessModeWrite } if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadUnits", err) return } ctx.Repo.Permission.Units = ctx.Repo.Repository.Units ctx.Repo.Permission.UnitsMode = make(map[unit.Type]perm.AccessMode) for _, u := range ctx.Repo.Repository.Units { ctx.Repo.Permission.UnitsMode[u.Type] = ctx.Repo.Permission.AccessMode } } else { ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return } } if !ctx.Repo.HasAccess() {
-
@@ -209,6 +239,11 @@// Contexter middleware already checks token for user sign in process. func reqToken(requiredScope auth_model.AccessTokenScope) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { // If actions token is present if true == ctx.Data["IsActionsToken"] { return } // If OAuth2 token is present if _, ok := ctx.Data["ApiTokenScope"]; ctx.Data["IsApiToken"] == true && ok { // no scope required
-
@@ -1234,6 +1269,13 @@ m.Group("/unadopted", func() {m.Get("", admin.ListUnadoptedRepositories) m.Post("/{username}/{reponame}", admin.AdoptRepository) m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository) }) m.Group("/hooks", func() { m.Combo("").Get(admin.ListHooks). Post(bind(api.CreateHookOption{}), admin.CreateHook) m.Combo("/{id}").Get(admin.GetHook). Patch(bind(api.EditHookOption{}), admin.EditHook). Delete(admin.DeleteHook) }) }, reqToken(auth_model.AccessTokenScopeSudo), reqSiteAdmin())
-
-
-
@@ -40,7 +40,7 @@ // - name: type// in: query // description: package type filter // type: string // enum: [composer, conan, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant] // enum: [composer, conan, conda, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant] // - name: q // in: query // description: name filter
-
-
-
@@ -11,6 +11,7 @@"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook"
-
@@ -65,6 +66,19 @@ ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")return false } return true } // AddSystemHook add a system hook func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) { hook, ok := addHook(ctx, form, 0, 0) if ok { h, err := webhook_service.ToHook(setting.AppSubURL+"/admin", hook) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) return } ctx.JSON(http.StatusCreated, h) } } // AddOrgHook add a hook to an organization. Writes to `ctx` accordingly
-
@@ -194,6 +208,30 @@ ctx.Error(http.StatusInternalServerError, "CreateWebhook", err)return nil, false } return w, true } // EditSystemHook edit system webhook `w` according to `form`. Writes to `ctx` accordingly func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) return } if !editHook(ctx, form, hook) { ctx.Error(http.StatusInternalServerError, "editHook", err) return } updated, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) return } h, err := webhook_service.ToHook(setting.AppURL+"/admin", updated) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) return } ctx.JSON(http.StatusOK, h) } // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly
-
-
-
@@ -30,11 +30,13 @@ "code.gitea.io/gitea/modules/templates""code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" actions_router "code.gitea.io/gitea/routers/api/actions" packages_router "code.gitea.io/gitea/routers/api/packages" apiv1 "code.gitea.io/gitea/routers/api/v1" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/private" web_routers "code.gitea.io/gitea/routers/web" actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/automerge"
-
@@ -172,6 +174,8 @@auth.Init() svg.Init() actions_service.Init() // Finally start up the cron cron.NewContext(ctx) }
-
@@ -197,5 +201,11 @@// This implements the OCI API (Note this is not preceded by /api but is instead /v2) r.Mount("/v2", packages_router.ContainerRoutes(ctx)) } if setting.Actions.Enabled { prefix := "/api/actions" r.Mount(prefix, actions_router.Routes(ctx, prefix)) } return r }
-
-
-
@@ -173,13 +173,6 @@ if ctx.Written() {return } if !repo.AllowsPulls() { // We can stop there's no need to go any further ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ RepoWasEmpty: wasEmpty, }) return } baseRepo = repo if repo.IsFork {
-
@@ -191,7 +184,17 @@ RepoWasEmpty: wasEmpty,}) return } baseRepo = repo.BaseRepo if repo.BaseRepo.AllowsPulls() { baseRepo = repo.BaseRepo } } if !baseRepo.AllowsPulls() { // We can stop there's no need to go any further ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ RepoWasEmpty: wasEmpty, }) return } }
-
@@ -217,14 +220,14 @@ if repo.IsFork {branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) } results = append(results, private.HookPostReceiveBranchResult{ Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(), Create: true, Branch: branch, URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), }) } else { results = append(results, private.HookPostReceiveBranchResult{ Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(), Create: false, Branch: branch, URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
-
-
-
@@ -472,25 +472,41 @@ if ctx.loadedPusher {return true } user, err := user_model.GetUserByID(ctx, ctx.opts.UserID) if err != nil { log.Error("Unable to get User id %d Error: %v", ctx.opts.UserID, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to get User id %d Error: %v", ctx.opts.UserID, err), }) return false } ctx.user = user userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user) if err != nil { log.Error("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err), }) return false if ctx.opts.UserID == user_model.ActionsUserID { ctx.user = user_model.NewActionsUser() ctx.userPerm.AccessMode = perm_model.AccessMode(ctx.opts.ActionPerm) if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil { log.Error("Unable to get User id %d Error: %v", ctx.opts.UserID, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to get User id %d Error: %v", ctx.opts.UserID, err), }) return false } ctx.userPerm.Units = ctx.Repo.Repository.Units ctx.userPerm.UnitsMode = make(map[unit.Type]perm_model.AccessMode) for _, u := range ctx.Repo.Repository.Units { ctx.userPerm.UnitsMode[u.Type] = ctx.userPerm.AccessMode } } else { user, err := user_model.GetUserByID(ctx, ctx.opts.UserID) if err != nil { log.Error("Unable to get User id %d Error: %v", ctx.opts.UserID, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to get User id %d Error: %v", ctx.opts.UserID, err), }) return false } ctx.user = user userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user) if err != nil { log.Error("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err), }) return false } ctx.userPerm = userPerm } ctx.userPerm = userPerm if ctx.opts.DeployKeyID != 0 { deployKey, err := asymkey_model.GetDeployKeyByID(ctx, ctx.opts.DeployKeyID)
-
-
-
@@ -22,6 +22,7 @@ // DefaultOrSystemWebhooks renders both admin default and system webhook list pagesfunc DefaultOrSystemWebhooks(ctx *context.Context) { var err error ctx.Data["Title"] = ctx.Tr("admin.hooks") ctx.Data["PageIsAdminSystemHooks"] = true ctx.Data["PageIsAdminDefaultHooks"] = true
-
@@ -60,7 +61,7 @@ }// DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook func DeleteDefaultOrSystemWebhook(ctx *context.Context) { if err := webhook.DeleteDefaultSystemWebhook(ctx.FormInt64("id")); err != nil { if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
-
-
-
@@ -0,0 +1,78 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package admin import ( "net/url" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" actions_shared "code.gitea.io/gitea/routers/web/shared/actions" ) const ( tplRunners base.TplName = "admin/runners/base" tplRunnerEdit base.TplName = "admin/runners/edit" ) // Runners show all the runners func Runners(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } opts := actions_model.FindRunnerOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: 100, }, Sort: ctx.Req.URL.Query().Get("sort"), Filter: ctx.Req.URL.Query().Get("q"), } actions_shared.RunnersList(ctx, tplRunners, opts) } // EditRunner show editing runner page func EditRunner(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } actions_shared.RunnerDetails(ctx, tplRunnerEdit, page, ctx.ParamsInt64(":runnerid"), 0, 0) } // EditRunnerPost response for editing runner func EditRunnerPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners.edit") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRunners"] = true actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), 0, 0, setting.AppSubURL+"/admin/runners/"+url.PathEscape(ctx.Params(":runnerid"))) } // DeleteRunnerPost response for deleting a runner func DeleteRunnerPost(ctx *context.Context) { actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), setting.AppSubURL+"/admin/runners/", setting.AppSubURL+"/admin/runners/"+url.PathEscape(ctx.Params(":runnerid")), ) } func ResetRunnerRegistrationToken(ctx *context.Context) { actions_shared.RunnerResetRegistrationToken(ctx, 0, 0, setting.AppSubURL+"/admin/runners/") }
-
-
-
@@ -633,7 +633,7 @@user := user_model.VerifyUserActiveCode(code) // if code is wrong if user == nil { ctx.Data["IsActivateFailed"] = true ctx.Data["IsCodeInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return }
-
@@ -660,7 +660,7 @@user := user_model.VerifyUserActiveCode(code) // if code is wrong if user == nil { ctx.Data["IsActivateFailed"] = true ctx.Data["IsCodeInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return }
-
@@ -675,7 +675,7 @@ ctx.HTML(http.StatusOK, TplActivate)return } if !user.ValidatePassword(password) { ctx.Data["IsActivateFailed"] = true ctx.Data["IsPasswordInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return }
-
-
-
@@ -17,7 +17,8 @@ )const ( // tplExploreRepos explore repositories page template tplExploreRepos base.TplName = "explore/repos" tplExploreRepos base.TplName = "explore/repos" relevantReposOnlyParam string = "no_filter" ) // RepoSearchOptions when calling search repositories
-
@@ -81,13 +82,11 @@ orderBy = db.SearchOrderByForksdefault: ctx.Data["SortType"] = "recentupdate" orderBy = db.SearchOrderByRecentUpdated onlyShowRelevant = setting.UI.OnlyShowRelevantRepos && !ctx.FormBool("no_filter") } onlyShowRelevant = !ctx.FormBool(relevantReposOnlyParam) keyword := ctx.FormTrim("q") if keyword != "" { onlyShowRelevant = false } ctx.Data["OnlyShowRelevant"] = onlyShowRelevant
-
@@ -139,7 +138,7 @@ pager := context.NewPagination(int(count), opts.PageSize, page, 5)pager.SetDefaultParams(ctx) pager.AddParam(ctx, "topic", "TopicOnly") pager.AddParam(ctx, "language", "Language") pager.AddParamString("no_filter", ctx.FormString("no_filter")) pager.AddParamString(relevantReposOnlyParam, ctx.FormString(relevantReposOnlyParam)) ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, opts.TplName)
-
-
-
@@ -0,0 +1,78 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package org import ( "net/url" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" actions_shared "code.gitea.io/gitea/routers/web/shared/actions" ) // Runners render runners page func Runners(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("org.runners") ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } opts := actions_model.FindRunnerOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: 100, }, Sort: ctx.Req.URL.Query().Get("sort"), Filter: ctx.Req.URL.Query().Get("q"), OwnerID: ctx.Org.Organization.ID, WithAvailable: true, } actions_shared.RunnersList(ctx, tplSettingsRunners, opts) } // ResetRunnerRegistrationToken reset runner registration token func ResetRunnerRegistrationToken(ctx *context.Context) { actions_shared.RunnerResetRegistrationToken(ctx, ctx.Org.Organization.ID, 0, ctx.Org.OrgLink+"/settings/runners") } // RunnersEdit render runner edit page func RunnersEdit(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("org.runners.edit") ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } actions_shared.RunnerDetails(ctx, tplSettingsRunnersEdit, page, ctx.ParamsInt64(":runnerid"), ctx.Org.Organization.ID, 0, ) } // RunnersEditPost response for editing runner func RunnersEditPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("org.runners.edit") ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsRunners"] = true actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), ctx.Org.Organization.ID, 0, ctx.Org.OrgLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) } // RunnerDeletePost response for deleting runner func RunnerDeletePost(ctx *context.Context) { actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), ctx.Org.OrgLink+"/settings/runners", ctx.Org.OrgLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) }
-
-
-
@@ -12,7 +12,6 @@"code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base"
-
@@ -38,8 +37,10 @@ // tplSettingsHooks template path for render hook settingstplSettingsHooks base.TplName = "org/settings/hooks" // tplSettingsLabels template path for render labels settings tplSettingsLabels base.TplName = "org/settings/labels" // tplSettingsSecrets template path for render secrets settings tplSettingsSecrets base.TplName = "org/settings/secrets" // tplSettingsRunners template path for render runners settings tplSettingsRunners base.TplName = "org/settings/runners" // tplSettingsRunnersEdit template path for render runners edit settings tplSettingsRunnersEdit base.TplName = "org/settings/runners_edit" ) // Settings render the main settings page
-
@@ -249,51 +250,3 @@ ctx.Data["RequireTribute"] = truectx.Data["LabelTemplates"] = repo_module.LabelTemplates ctx.HTML(http.StatusOK, tplSettingsLabels) } // Secrets render organization secrets page func Secrets(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.secrets") ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsSecrets"] = true secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: ctx.Org.Organization.ID}) if err != nil { ctx.ServerError("FindSecrets", err) return } ctx.Data["Secrets"] = secrets ctx.HTML(http.StatusOK, tplSettingsSecrets) } // SecretsPost add secrets func SecretsPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AddSecretForm) _, err := secret_model.InsertEncryptedSecret(ctx, ctx.Org.Organization.ID, 0, form.Title, form.Content) if err != nil { ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) log.Error("validate secret: %v", err) ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets") return } log.Trace("Org %d: secret added", ctx.Org.Organization.ID) ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title)) ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets") } // SecretsDelete delete secrets func SecretsDelete(ctx *context.Context) { id := ctx.FormInt64("id") if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) log.Error("delete secret %d: %v", id, err) } else { ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) } ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Org.OrgLink + "/settings/secrets", }) }
-
-
-
@@ -0,0 +1,48 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package org import ( "net/http" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" shared "code.gitea.io/gitea/routers/web/shared/secrets" ) const ( tplSettingsSecrets base.TplName = "org/settings/secrets" ) // Secrets render organization secrets page func Secrets(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("secrets.secrets") ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsSecrets"] = true shared.SetSecretsContext(ctx, ctx.ContextUser.ID, 0) if ctx.Written() { return } ctx.HTML(http.StatusOK, tplSettingsSecrets) } // SecretsPost add secrets func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, ctx.ContextUser.ID, 0, ctx.Org.OrgLink+"/settings/secrets", ) } // SecretsDelete delete secrets func SecretsDelete(ctx *context.Context) { shared.PerformSecretsDelete( ctx, ctx.Org.OrgLink+"/settings/secrets", ) }
-
-
-
@@ -0,0 +1,139 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "net/http" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/convert" ) const ( tplListActions base.TplName = "repo/actions/list" tplViewActions base.TplName = "repo/actions/view" ) // MustEnableActions check if actions are enabled in settings func MustEnableActions(ctx *context.Context) { if !setting.Actions.Enabled { ctx.NotFound("MustEnableActions", nil) return } if unit.TypeActions.UnitGlobalDisabled() { ctx.NotFound("MustEnableActions", nil) return } if ctx.Repo.Repository != nil { if !ctx.Repo.CanRead(unit.TypeActions) { ctx.NotFound("MustEnableActions", nil) return } } } func List(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.actions") ctx.Data["PageIsActions"] = true var workflows git.Entries if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } else if !empty { defaultBranch, err := ctx.Repo.GitRepo.GetDefaultBranch() if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } commit, err := ctx.Repo.GitRepo.GetBranchCommit(defaultBranch) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } workflows, err = actions.ListWorkflows(commit) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } } ctx.Data["workflows"] = workflows ctx.Data["RepoLink"] = ctx.Repo.Repository.HTMLURL() page := ctx.FormInt("page") if page <= 0 { page = 1 } workflow := ctx.FormString("workflow") ctx.Data["CurWorkflow"] = workflow opts := actions_model.FindRunOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), }, RepoID: ctx.Repo.Repository.ID, WorkflowFileName: workflow, } // open counts opts.IsClosed = util.OptionalBoolFalse numOpenRuns, err := actions_model.CountRuns(ctx, opts) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } ctx.Data["NumOpenActionRuns"] = numOpenRuns // closed counts opts.IsClosed = util.OptionalBoolTrue numClosedRuns, err := actions_model.CountRuns(ctx, opts) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } ctx.Data["NumClosedActionRuns"] = numClosedRuns opts.IsClosed = util.OptionalBoolNone if ctx.FormString("state") == "closed" { opts.IsClosed = util.OptionalBoolTrue ctx.Data["IsShowClosed"] = true } else { opts.IsClosed = util.OptionalBoolFalse } runs, total, err := actions_model.FindRuns(ctx, opts) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } for _, run := range runs { run.Repo = ctx.Repo.Repository } if err := runs.LoadTriggerUser(ctx); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } ctx.Data["Runs"] = runs pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplListActions) }
-
-
-
@@ -0,0 +1,297 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "errors" "fmt" "net/http" "time" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/actions" context_module "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" actions_service "code.gitea.io/gitea/services/actions" "xorm.io/builder" ) func View(ctx *context_module.Context) { ctx.Data["PageIsActions"] = true runIndex := ctx.ParamsInt64("run") jobIndex := ctx.ParamsInt64("job") ctx.Data["RunIndex"] = runIndex ctx.Data["JobIndex"] = jobIndex ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions" if getRunJobs(ctx, runIndex, jobIndex); ctx.Written() { return } ctx.HTML(http.StatusOK, tplViewActions) } type ViewRequest struct { LogCursors []struct { Step int `json:"step"` Cursor int64 `json:"cursor"` Expanded bool `json:"expanded"` } `json:"logCursors"` } type ViewResponse struct { State struct { Run struct { Link string `json:"link"` Title string `json:"title"` CanCancel bool `json:"canCancel"` Done bool `json:"done"` Jobs []*ViewJob `json:"jobs"` } `json:"run"` CurrentJob struct { Title string `json:"title"` Detail string `json:"detail"` Steps []*ViewJobStep `json:"steps"` } `json:"currentJob"` } `json:"state"` Logs struct { StepsLog []*ViewStepLog `json:"stepsLog"` } `json:"logs"` } type ViewJob struct { ID int64 `json:"id"` Name string `json:"name"` Status string `json:"status"` CanRerun bool `json:"canRerun"` } type ViewJobStep struct { Summary string `json:"summary"` Duration string `json:"duration"` Status string `json:"status"` } type ViewStepLog struct { Step int `json:"step"` Cursor int64 `json:"cursor"` Lines []*ViewStepLogLine `json:"lines"` } type ViewStepLogLine struct { Index int64 `json:"index"` Message string `json:"message"` Timestamp float64 `json:"timestamp"` } func ViewPost(ctx *context_module.Context) { req := web.GetForm(ctx).(*ViewRequest) runIndex := ctx.ParamsInt64("run") jobIndex := ctx.ParamsInt64("job") current, jobs := getRunJobs(ctx, runIndex, jobIndex) if ctx.Written() { return } run := current.Run resp := &ViewResponse{} resp.State.Run.Title = run.Title resp.State.Run.Link = run.Link() resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.Done = run.Status.IsDone() resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json for _, v := range jobs { resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{ ID: v.ID, Name: v.Name, Status: v.Status.String(), CanRerun: v.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions), }) } var task *actions_model.ActionTask if current.TaskID > 0 { var err error task, err = actions_model.GetTaskByID(ctx, current.TaskID) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } task.Job = current if err := task.LoadAttributes(ctx); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } } resp.State.CurrentJob.Title = current.Name resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale) resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json if task != nil { steps := actions.FullSteps(task) for _, v := range steps { resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &ViewJobStep{ Summary: v.Name, Duration: v.Duration().String(), Status: v.Status.String(), }) } for _, cursor := range req.LogCursors { if !cursor.Expanded { continue } step := steps[cursor.Step] logLines := make([]*ViewStepLogLine, 0) // marshal to '[]' instead fo 'null' in json if c := cursor.Cursor; c < step.LogLength && c >= 0 { index := step.LogIndex + c length := step.LogLength - cursor.Cursor offset := task.LogIndexes[index] var err error logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } for i, row := range logRows { logLines = append(logLines, &ViewStepLogLine{ Index: cursor.Cursor + int64(i) + 1, // start at 1 Message: row.Content, Timestamp: float64(row.Time.AsTime().UnixNano()) / float64(time.Second), }) } } resp.Logs.StepsLog = append(resp.Logs.StepsLog, &ViewStepLog{ Step: cursor.Step, Cursor: cursor.Cursor + int64(len(logLines)), Lines: logLines, }) } } ctx.JSON(http.StatusOK, resp) } func Rerun(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") jobIndex := ctx.ParamsInt64("job") job, _ := getRunJobs(ctx, runIndex, jobIndex) if ctx.Written() { return } status := job.Status if !status.IsDone() { ctx.JSON(http.StatusOK, struct{}{}) return } job.TaskID = 0 job.Status = actions_model.StatusWaiting job.Started = 0 job.Stopped = 0 if err := db.WithTx(ctx, func(ctx context.Context) error { if _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped"); err != nil { return err } return actions_service.CreateCommitStatus(ctx, job) }); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } ctx.JSON(http.StatusOK, struct{}{}) } func Cancel(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") _, jobs := getRunJobs(ctx, runIndex, -1) if ctx.Written() { return } if err := db.WithTx(ctx, func(ctx context.Context) error { for _, job := range jobs { status := job.Status if status.IsDone() { continue } if job.TaskID == 0 { job.Status = actions_model.StatusCancelled job.Stopped = timeutil.TimeStampNow() n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped") if err != nil { return err } if n == 0 { return fmt.Errorf("job has changed, try again") } continue } if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil { return err } if err := actions_service.CreateCommitStatus(ctx, job); err != nil { return err } } return nil }); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } ctx.JSON(http.StatusOK, struct{}{}) } // getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs. // Any error will be written to the ctx. // It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0. func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.Error(http.StatusNotFound, err.Error()) return nil, nil } ctx.Error(http.StatusInternalServerError, err.Error()) return nil, nil } run.Repo = ctx.Repo.Repository jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return nil, nil } if len(jobs) == 0 { ctx.Error(http.StatusNotFound, err.Error()) return nil, nil } for _, v := range jobs { v.Run = run } if jobIndex >= 0 && jobIndex < int64(len(jobs)) { return jobs[jobIndex], jobs } return jobs[0], jobs }
-
-
-
@@ -217,7 +217,7 @@ defer deleteTemporaryFile()filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ CachedOnly: true, Attributes: []git.CmdArg{"linguist-language", "gitlab-language"}, Attributes: []string{"linguist-language", "gitlab-language"}, Filenames: []string{ctx.Repo.TreePath}, IndexFile: indexFilename, WorkTree: worktree,
-
-
-
@@ -30,6 +30,7 @@ "code.gitea.io/gitea/modules/git""code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/gitdiff"
-
@@ -559,7 +560,7 @@ // PrepareCompareDiff renders compare diff pagefunc PrepareCompareDiff( ctx *context.Context, ci *CompareInfo, whitespaceBehavior git.CmdArg, whitespaceBehavior git.TrustedCmdArgs, ) bool { var ( repo = ctx.Repo.Repository
-
@@ -789,15 +790,28 @@ if len(templateErrs) > 0 {ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true) } // If a template content is set, prepend the "content". In this case that's only // applicable if you have one commit to compare and that commit has a message. // In that case the commit message will be prepend to the template body. if templateContent, ok := ctx.Data[pullRequestTemplateKey].(string); ok && templateContent != "" { if content, ok := ctx.Data["content"].(string); ok && content != "" { if content, ok := ctx.Data["content"].(string); ok && content != "" { // If a template content is set, prepend the "content". In this case that's only // applicable if you have one commit to compare and that commit has a message. // In that case the commit message will be prepend to the template body. if templateContent, ok := ctx.Data[pullRequestTemplateKey].(string); ok && templateContent != "" { // Re-use the same key as that's priortized over the "content" key. // Add two new lines between the content to ensure there's always at least // one empty line between them. ctx.Data[pullRequestTemplateKey] = content + "\n\n" + templateContent } // When using form fields, also add content to field with id "body". if fields, ok := ctx.Data["Fields"].([]*api.IssueFormField); ok { for _, field := range fields { if field.ID == "body" { if fieldValue, ok := field.Attributes["value"].(string); ok && fieldValue != "" { field.Attributes["value"] = content + "\n\n" + fieldValue } else { field.Attributes["value"] = content } } } } }
-
-
-
@@ -18,6 +18,7 @@ "strings""sync" "time" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access"
-
@@ -163,7 +164,7 @@ ctx.Error(http.StatusUnauthorized)return } if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true { if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true { _, err = auth.GetTwoFactorByUID(ctx.Doer.ID) if err == nil { // TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
-
@@ -180,35 +181,62 @@ ctx.PlainText(http.StatusForbidden, "Your account is disabled.")return } environ = []string{ repo_module.EnvRepoUsername + "=" + username, repo_module.EnvRepoName + "=" + reponame, repo_module.EnvPusherName + "=" + ctx.Doer.Name, repo_module.EnvPusherID + fmt.Sprintf("=%d", ctx.Doer.ID), repo_module.EnvAppURL + "=" + setting.AppURL, } if repoExist { p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } // Because of special ref "refs/for" .. , need delay write permission check if git.SupportProcReceive { accessMode = perm.AccessModeRead } if !p.CanAccess(accessMode, unitType) { ctx.PlainText(http.StatusForbidden, "User permission denied") return if ctx.Data["IsActionsToken"] == true { taskID := ctx.Data["ActionsTaskID"].(int64) task, err := actions_model.GetTaskByID(ctx, taskID) if err != nil { ctx.ServerError("GetTaskByID", err) return } if task.RepoID != repo.ID { ctx.PlainText(http.StatusForbidden, "User permission denied") return } if task.IsForkPullRequest { if accessMode > perm.AccessModeRead { ctx.PlainText(http.StatusForbidden, "User permission denied") return } environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead)) } else { if accessMode > perm.AccessModeWrite { ctx.PlainText(http.StatusForbidden, "User permission denied") return } environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite)) } } else { p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } if !p.CanAccess(accessMode, unitType) { ctx.PlainText(http.StatusForbidden, "User permission denied") return } } if !isPull && repo.IsMirror { ctx.PlainText(http.StatusForbidden, "mirror repository is read-only") return } } environ = []string{ repo_module.EnvRepoUsername + "=" + username, repo_module.EnvRepoName + "=" + reponame, repo_module.EnvPusherName + "=" + ctx.Doer.Name, repo_module.EnvPusherID + fmt.Sprintf("=%d", ctx.Doer.ID), repo_module.EnvAppURL + "=" + setting.AppURL, } if !ctx.Doer.KeepEmailPrivate {
-
@@ -470,7 +498,8 @@ h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)} var stderr bytes.Buffer cmd := git.NewCommand(h.r.Context(), git.CmdArgCheck(service), "--stateless-rpc").AddDynamicArguments(h.dir) // the service is generated by ourselves, so it's safe to trust it cmd := git.NewCommand(h.r.Context(), git.ToTrustedCmdArgs([]string{service})...).AddArguments("--stateless-rpc").AddDynamicArguments(h.dir) cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir)) if err := cmd.Run(&git.RunOpts{ Dir: h.dir,
-
@@ -542,7 +571,8 @@ h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)} h.environ = append(os.Environ(), h.environ...) refs, _, err := git.NewCommand(ctx, git.CmdArgCheck(service), "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir}) // the service is generated by ourselves, so we can trust it refs, _, err := git.NewCommand(ctx, git.ToTrustedCmdArgs([]string{service})...).AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) }
-
-
-
@@ -203,6 +203,7 @@ issueStats, err = issues_model.GetIssueStats(&issues_model.IssueStatsOptions{RepoID: repo.ID, Labels: selectLabels, MilestoneID: milestoneID, ProjectID: projectID, AssigneeID: assigneeID, MentionedID: mentionedID, PosterID: posterID,
-
@@ -362,17 +363,9 @@ }return 0 } if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") { projects, _, err := project_model.FindProjects(ctx, project_model.SearchOptions{ RepoID: repo.ID, Type: project_model.TypeRepository, IsClosed: util.OptionalBoolOf(isShowClosed), }) if err != nil { ctx.ServerError("GetProjects", err) return } ctx.Data["Projects"] = projects retrieveProjects(ctx, repo) if ctx.Written() { return } ctx.Data["IssueStats"] = issueStats
-
@@ -381,6 +374,7 @@ ctx.Data["SelectLabels"] = selectLabelsctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType ctx.Data["MilestoneID"] = milestoneID ctx.Data["ProjectID"] = projectID ctx.Data["AssigneeID"] = assigneeID ctx.Data["PosterID"] = posterID ctx.Data["IsShowClosed"] = isShowClosed
-
@@ -397,6 +391,7 @@ pager.AddParam(ctx, "sort", "SortType")pager.AddParam(ctx, "state", "State") pager.AddParam(ctx, "labels", "SelectLabels") pager.AddParam(ctx, "milestone", "MilestoneID") pager.AddParam(ctx, "project", "ProjectID") pager.AddParam(ctx, "assignee", "AssigneeID") pager.AddParam(ctx, "poster", "PosterID") ctx.Data["Page"] = pager
-
@@ -783,6 +778,14 @@ ctx.Data[issueTemplateTitleKey] = template.Titlectx.Data[ctxDataKey] = template.Content if template.Type() == api.IssueTemplateTypeYaml { // Replace field default values by values from query for _, field := range template.Fields { fieldValue := ctx.FormString("field:" + field.ID) if fieldValue != "" { field.Attributes["value"] = fieldValue } } ctx.Data["Fields"] = template.Fields ctx.Data["TemplateFile"] = template.FileName }
-
@@ -2352,6 +2355,8 @@ if len(milestones) > 0 {includedMilestones = strings.Split(milestones, ",") } projectID := ctx.FormInt64("project") // this api is also used in UI, // so the default limit is set to fit UI needs limit := ctx.FormInt("limit")
-
@@ -2374,6 +2379,7 @@ IsClosed: isClosed,IssueIDs: issueIDs, IncludedLabelNames: includedLabelNames, IncludeMilestones: includedMilestones, ProjectID: projectID, SortType: "priorityrepo", PriorityRepoID: ctx.FormInt64("priority_repo_id"), IsPull: isPull,
-
@@ -2511,6 +2517,8 @@ ctx.Error(http.StatusInternalServerError, err.Error())} } projectID := ctx.FormInt64("project") listOptions := db.ListOptions{ Page: ctx.FormInt("page"), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
-
@@ -2550,6 +2558,7 @@ IsClosed: isClosed,IssueIDs: issueIDs, LabelIDs: labelIDs, MilestoneIDs: mileIDs, ProjectID: projectID, IsPull: isPull, UpdatedBeforeUnix: before, UpdatedAfterUnix: since,
-
-
-
@@ -146,7 +146,7 @@ return} name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ Attributes: []git.CmdArg{"lockable"}, Attributes: []string{"lockable"}, Filenames: filenames, CachedOnly: true, })
-
-
-
@@ -926,59 +926,54 @@pr := issue.PullRequest pr.Issue = issue pr.Issue.Repo = ctx.Repo.Repository manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged manualMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged forceMerge := form.ForceMerge != nil && *form.ForceMerge // start with merging by checking if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, forceMerge); err != nil { if errors.Is(err, pull_service.ErrIsClosed) { if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manualMerge, forceMerge); err != nil { switch { case errors.Is(err, pull_service.ErrIsClosed): if issue.IsPull { ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) ctx.Redirect(issue.Link()) } else { ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) ctx.Redirect(issue.Link()) } } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { case errors.Is(err, pull_service.ErrUserNotAllowedToMerge): ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) ctx.Redirect(issue.Link()) } else if errors.Is(err, pull_service.ErrHasMerged) { case errors.Is(err, pull_service.ErrHasMerged): ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) ctx.Redirect(issue.Link()) } else if errors.Is(err, pull_service.ErrIsWorkInProgress) { case errors.Is(err, pull_service.ErrIsWorkInProgress): ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) ctx.Redirect(issue.Link()) } else if errors.Is(err, pull_service.ErrNotMergableState) { case errors.Is(err, pull_service.ErrNotMergableState): ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) ctx.Redirect(issue.Link()) } else if models.IsErrDisallowedToMerge(err) { case models.IsErrDisallowedToMerge(err): ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) ctx.Redirect(issue.Link()) } else if asymkey_service.IsErrWontSign(err) { ctx.Flash.Error(err.Error()) // has not translation ... ctx.Redirect(issue.Link()) } else if errors.Is(err, pull_service.ErrDependenciesLeft) { case asymkey_service.IsErrWontSign(err): ctx.Flash.Error(err.Error()) // has no translation ... case errors.Is(err, pull_service.ErrDependenciesLeft): ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) ctx.Redirect(issue.Link()) } else { default: ctx.ServerError("WebCheck", err) return } ctx.Redirect(issue.Link()) return } // handle manually-merged mark if manuallMerge { if manualMerge { if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if models.IsErrInvalidMergeStyle(err) { switch { case models.IsErrInvalidMergeStyle(err): ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Redirect(issue.Link()) } else if strings.Contains(err.Error(), "Wrong commit ID") { case strings.Contains(err.Error(), "Wrong commit ID"): ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id")) ctx.Redirect(issue.Link()) } else { default: ctx.ServerError("MergedManually", err) return } return } ctx.Redirect(issue.Link())
-
@@ -1395,7 +1390,7 @@ deleteBranch(ctx, pr, gitRepo)} func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) { fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch if err := repo_service.DeleteBranch(ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err):
-
-
-
@@ -328,6 +328,14 @@ ctx.Data["attachments"] = rel.Attachments} } ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled var err error // Get assignees. ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) if err != nil { ctx.ServerError("GetAssignees", err) return } upload.AddUploadContext(ctx, "release") ctx.HTML(http.StatusOK, tplReleaseNew) }
-
@@ -483,6 +491,13 @@ ctx.ServerError("LoadAttributes", err)return } ctx.Data["attachments"] = rel.Attachments // Get assignees. ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, rel.Repo) if err != nil { ctx.ServerError("GetAssignees", err) return } ctx.HTML(http.StatusOK, tplReleaseNew) }
-
-
-
@@ -0,0 +1,76 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repo import ( "net/url" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" actions_shared "code.gitea.io/gitea/routers/web/shared/actions" ) const ( tplRunners = "repo/settings/runners" tplRunnerEdit = "repo/settings/runner_edit" ) // Runners render runners page func Runners(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners") ctx.Data["PageIsSettingsRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } opts := actions_model.FindRunnerOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: 100, }, Sort: ctx.Req.URL.Query().Get("sort"), Filter: ctx.Req.URL.Query().Get("q"), RepoID: ctx.Repo.Repository.ID, WithAvailable: true, } actions_shared.RunnersList(ctx, tplRunners, opts) } func RunnersEdit(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners") ctx.Data["PageIsSettingsRunners"] = true page := ctx.FormInt("page") if page <= 1 { page = 1 } actions_shared.RunnerDetails(ctx, tplRunnerEdit, page, ctx.ParamsInt64(":runnerid"), 0, ctx.Repo.Repository.ID, ) } func RunnersEditPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.runners") ctx.Data["PageIsSettingsRunners"] = true actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), 0, ctx.Repo.Repository.ID, ctx.Repo.RepoLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) } func ResetRunnerRegistrationToken(ctx *context.Context) { actions_shared.RunnerResetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID, ctx.Repo.RepoLink+"/settings/runners") } // RunnerDeletePost response for deleting runner func RunnerDeletePost(ctx *context.Context) { actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), ctx.Repo.RepoLink+"/settings/runners", ctx.Repo.RepoLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) }
-
-
-
@@ -19,7 +19,6 @@ "code.gitea.io/gitea/models/db""code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" secret_model "code.gitea.io/gitea/models/secret" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base"
-
@@ -61,7 +60,7 @@// SettingsCtxData is a middleware that sets all the general context data for the // settings template. func SettingsCtxData(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings.options") ctx.Data["PageIsSettingsOptions"] = true ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
-
@@ -488,6 +487,15 @@ } else if !unit_model.TypeProjects.UnitGlobalDisabled() {deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) } if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: unit_model.TypeReleases, }) } else if !unit_model.TypeReleases.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) } if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { units = append(units, repo_model.RepoUnit{ RepoID: repo.ID,
-
@@ -497,6 +505,15 @@ } else if !unit_model.TypePackages.UnitGlobalDisabled() {deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) } if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: unit_model.TypeActions, }) } else if !unit_model.TypeActions.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) } if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { units = append(units, repo_model.RepoUnit{ RepoID: repo.ID,
-
@@ -862,7 +879,7 @@ }// Collaboration render a repository's collaboration page func Collaboration(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration") ctx.Data["PageIsSettingsCollaboration"] = true users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
-
@@ -1102,7 +1119,7 @@ }// DeployKeys render the deploy keys list of a repository page func DeployKeys(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled
-
@@ -1113,37 +1130,12 @@ return} ctx.Data["Deploykeys"] = keys secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{RepoID: ctx.Repo.Repository.ID}) if err != nil { ctx.ServerError("FindSecrets", err) return } ctx.Data["Secrets"] = secrets ctx.HTML(http.StatusOK, tplDeployKeys) } // SecretsPost response for creating a new secret func SecretsPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AddSecretForm) _, err := secret_model.InsertEncryptedSecret(ctx, 0, ctx.Repo.Repository.ID, form.Title, form.Content) if err != nil { ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) log.Error("validate secret: %v", err) ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") return } log.Trace("Secret added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title)) ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") } // DeployKeysPost response for adding a deploy key of a repository func DeployKeysPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AddKeyForm) ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled
-
@@ -1166,6 +1158,10 @@ if db.IsErrSSHDisabled(err) {ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) } else if asymkey_model.IsErrKeyUnableVerify(err) { ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) } else if err == asymkey_model.ErrKeyIsPrivate { ctx.Data["HasError"] = true ctx.Data["Err_Content"] = true ctx.Flash.Error(ctx.Tr("form.must_use_public_key")) } else { ctx.Data["HasError"] = true ctx.Data["Err_Content"] = true
-
@@ -1200,20 +1196,6 @@log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") } func DeleteSecret(ctx *context.Context) { id := ctx.FormInt64("id") if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) log.Error("delete secret %d: %v", id, err) } else { ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) } ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Repo.RepoLink + "/settings/keys", }) } // DeleteDeployKey response for deleting a deploy key
-
-
-
@@ -31,7 +31,7 @@ )// ProtectedBranchRules render the page to protect the repository func ProtectedBranchRules(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings.branches") ctx.Data["PageIsSettingsBranches"] = true rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
-
@@ -46,7 +46,7 @@ }// SetDefaultBranchPost set default branch func SetDefaultBranchPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings.branches.update_default_branch") ctx.Data["PageIsSettingsBranches"] = true repo := ctx.Repo.Repository
-
-
-
@@ -0,0 +1,46 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repo import ( "net/http" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" shared "code.gitea.io/gitea/routers/web/shared/secrets" ) const ( tplSecrets base.TplName = "repo/settings/secrets" ) func Secrets(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("secrets.secrets") ctx.Data["PageIsSettingsSecrets"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled shared.SetSecretsContext(ctx, 0, ctx.Repo.Repository.ID) if ctx.Written() { return } ctx.HTML(http.StatusOK, tplSecrets) } func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, 0, ctx.Repo.Repository.ID, ctx.Repo.RepoLink+"/settings/secrets", ) } func DeleteSecret(ctx *context.Context) { shared.PerformSecretsDelete( ctx, ctx.Repo.RepoLink+"/settings/secrets", ) }
-
-
-
@@ -133,7 +133,7 @@ ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags")} func setTagsContext(ctx *context.Context) error { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings.tags") ctx.Data["PageIsSettingsTags"] = true protectedTags, err := git_model.GetProtectedTags(ctx, ctx.Repo.Repository.ID)
-
-
-
@@ -509,7 +509,7 @@ defer deleteTemporaryFile()filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ CachedOnly: true, Attributes: []git.CmdArg{"linguist-language", "gitlab-language"}, Attributes: []string{"linguist-language", "gitlab-language"}, Filenames: []string{ctx.Repo.TreePath}, IndexFile: indexFilename, WorkTree: worktree,
-
-
-
@@ -591,7 +591,7 @@ w, err = webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))} else if orCtx.OrgID > 0 { w, err = webhook.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) } else if orCtx.IsAdmin { w, err = webhook.GetSystemOrDefaultWebhook(ctx.ParamsInt64(":id")) w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) } if err != nil || w == nil { if webhook.IsErrWebhookNotExist(err) {
-
-
-
@@ -0,0 +1,190 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "errors" "net/http" "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" ) // RunnersList render common runners list page func RunnersList(ctx *context.Context, tplName base.TplName, opts actions_model.FindRunnerOptions) { count, err := actions_model.CountRunners(ctx, opts) if err != nil { ctx.ServerError("AdminRunners", err) return } runners, err := actions_model.FindRunners(ctx, opts) if err != nil { ctx.ServerError("AdminRunners", err) return } if err := runners.LoadAttributes(ctx); err != nil { ctx.ServerError("LoadAttributes", err) return } // ownid=0,repo_id=0,means this token is used for global var token *actions_model.ActionRunnerToken token, err = actions_model.GetUnactivatedRunnerToken(ctx, opts.OwnerID, opts.RepoID) if errors.Is(err, util.ErrNotExist) { token, err = actions_model.NewRunnerToken(ctx, opts.OwnerID, opts.RepoID) if err != nil { ctx.ServerError("CreateRunnerToken", err) return } } else if err != nil { ctx.ServerError("GetUnactivatedRunnerToken", err) return } ctx.Data["Keyword"] = opts.Filter ctx.Data["Runners"] = runners ctx.Data["Total"] = count ctx.Data["RegistrationToken"] = token.Token ctx.Data["RunnerOnwerID"] = opts.OwnerID ctx.Data["RunnerRepoID"] = opts.RepoID pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplName) } // RunnerDetails render runner details page func RunnerDetails(ctx *context.Context, tplName base.TplName, page int, runnerID, ownerID, repoID int64) { runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { ctx.ServerError("GetRunnerByID", err) return } if err := runner.LoadAttributes(ctx); err != nil { ctx.ServerError("LoadAttributes", err) return } if !runner.Editable(ownerID, repoID) { err = errors.New("no permission to edit this runner") ctx.NotFound("RunnerDetails", err) return } ctx.Data["Runner"] = runner opts := actions_model.FindTaskOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: 30, }, Status: actions_model.StatusUnknown, // Unknown means all IDOrderDesc: true, RunnerID: runner.ID, } count, err := actions_model.CountTasks(ctx, opts) if err != nil { ctx.ServerError("CountTasks", err) return } tasks, err := actions_model.FindTasks(ctx, opts) if err != nil { ctx.ServerError("FindTasks", err) return } if err = tasks.LoadAttributes(ctx); err != nil { ctx.ServerError("TasksLoadAttributes", err) return } ctx.Data["Tasks"] = tasks pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplName) } // RunnerDetailsEditPost response for edit runner details func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64, redirectTo string) { runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { log.Warn("RunnerDetailsEditPost.GetRunnerByID failed: %v, url: %s", err, ctx.Req.URL) ctx.ServerError("RunnerDetailsEditPost.GetRunnerByID", err) return } if !runner.Editable(ownerID, repoID) { ctx.NotFound("RunnerDetailsEditPost.Editable", util.NewPermissionDeniedErrorf("no permission to edit this runner")) return } form := web.GetForm(ctx).(*forms.EditRunnerForm) runner.Description = form.Description runner.CustomLabels = splitLabels(form.CustomLabels) err = actions_model.UpdateRunner(ctx, runner, "description", "custom_labels") if err != nil { log.Warn("RunnerDetailsEditPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) ctx.Flash.Warning(ctx.Tr("actions.runners.update_runner_failed")) ctx.Redirect(redirectTo) return } log.Debug("RunnerDetailsEditPost success: %s", ctx.Req.URL) ctx.Flash.Success(ctx.Tr("actions.runners.update_runner_success")) ctx.Redirect(redirectTo) } // RunnerResetRegistrationToken reset registration token func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, redirectTo string) { _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID) if err != nil { ctx.ServerError("ResetRunnerRegistrationToken", err) return } ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success")) ctx.Redirect(redirectTo) } // RunnerDeletePost response for deleting a runner func RunnerDeletePost(ctx *context.Context, runnerID int64, successRedirectTo, failedRedirectTo string, ) { if err := actions_model.DeleteRunner(ctx, runnerID); err != nil { log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed")) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": failedRedirectTo, }) return } log.Info("DeleteRunnerPost success: %s", ctx.Req.URL) ctx.Flash.Success(ctx.Tr("actions.runners.delete_runner_success")) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": successRedirectTo, }) } func splitLabels(s string) []string { labels := strings.Split(s, ",") for i, v := range labels { labels[i] = strings.TrimSpace(v) } return labels }
-
-
-
@@ -0,0 +1,54 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package secrets import ( "net/http" "code.gitea.io/gitea/models/db" secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" ) func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: ownerID, RepoID: repoID}) if err != nil { ctx.ServerError("FindSecrets", err) return } ctx.Data["Secrets"] = secrets } func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { form := web.GetForm(ctx).(*forms.AddSecretForm) s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, form.Title, form.Content) if err != nil { log.Error("InsertEncryptedSecret: %v", err) ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) } else { ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name)) } ctx.Redirect(redirectURL) } func PerformSecretsDelete(ctx *context.Context, redirectURL string) { id := ctx.FormInt64("id") if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { log.Error("Delete secret %d failed: %v", id, err) ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) } else { ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) } ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": redirectURL, }) }
-
-
-
@@ -30,7 +30,7 @@ )// Account renders change user's password, user's email and user suicide page func Account(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.account") ctx.Data["PageIsSettingsAccount"] = true ctx.Data["Email"] = ctx.Doer.Email ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
-
-
-
@@ -17,7 +17,7 @@ )// AdoptOrDeleteRepository adopts or deletes a repository func AdoptOrDeleteRepository(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.adopt") ctx.Data["PageIsSettingsRepos"] = true allowAdopt := ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories ctx.Data["allowAdopt"] = allowAdopt
-
-
-
@@ -21,7 +21,7 @@ )// Applications render manage access token page func Applications(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.applications") ctx.Data["PageIsSettingsApplications"] = true loadApplicationsData(ctx)
-
-
-
@@ -23,7 +23,7 @@ )// Keys render user's SSH/GPG public keys page func Keys(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.ssh_gpg_keys") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
-
@@ -159,6 +159,8 @@ if db.IsErrSSHDisabled(err) {ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) } else if asymkey_model.IsErrKeyUnableVerify(err) { ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) } else if err == asymkey_model.ErrKeyIsPrivate { ctx.Flash.Error(ctx.Tr("form.must_use_public_key")) } else { ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error())) }
-
-
-
@@ -42,7 +42,7 @@ )// Profile render user's profile page func Profile(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.profile") ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
-
@@ -219,7 +219,7 @@ }// Organization render all the organization of the user func Organization(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.organization") ctx.Data["PageIsSettingsOrganization"] = true opts := organization.FindOrgOptions{
-
@@ -254,7 +254,7 @@ }// Repos display a list of all repositories of the user func Repos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.repos") ctx.Data["PageIsSettingsRepos"] = true ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
-
@@ -360,7 +360,7 @@ }// Appearance render user's appearance settings func Appearance(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.appearance") ctx.Data["PageIsSettingsAppearance"] = true var hiddenCommentTypes *big.Int
-
-
-
@@ -0,0 +1,45 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "net/http" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" shared "code.gitea.io/gitea/routers/web/shared/secrets" ) const ( tplSettingsSecrets base.TplName = "user/settings/secrets" ) func Secrets(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("secrets.secrets") ctx.Data["PageIsSettingsSecrets"] = true shared.SetSecretsContext(ctx, ctx.Doer.ID, 0) if ctx.Written() { return } ctx.HTML(http.StatusOK, tplSettingsSecrets) } func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, ctx.Doer.ID, 0, setting.AppSubURL+"/user/settings/secrets", ) } func SecretsDelete(ctx *context.Context) { shared.PerformSecretsDelete( ctx, setting.AppSubURL+"/user/settings/secrets", ) }
-
-
-
@@ -22,7 +22,7 @@ )// Security render change user's password page and 2FA func Security(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings.security") ctx.Data["PageIsSettingsSecurity"] = true if ctx.FormString("openid.return_to") != "" {
-
-
-
@@ -34,6 +34,7 @@ "code.gitea.io/gitea/routers/web/healthcheck""code.gitea.io/gitea/routers/web/misc" "code.gitea.io/gitea/routers/web/org" "code.gitea.io/gitea/routers/web/repo" "code.gitea.io/gitea/routers/web/repo/actions" "code.gitea.io/gitea/routers/web/user" user_setting "code.gitea.io/gitea/routers/web/user/setting" "code.gitea.io/gitea/routers/web/user/setting/security"
-
@@ -468,6 +469,11 @@ m.Get("/preview", user_setting.PackagesRulePreview)}) }) }, packagesEnabled) m.Group("/secrets", func() { m.Get("", user_setting.Secrets) m.Post("", web.Bind(forms.AddSecretForm{}), user_setting.SecretsPost) m.Post("/delete", user_setting.SecretsDelete) }) m.Get("/organization", user_setting.Organization) m.Get("/repos", user_setting.Repos) m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository)
-
@@ -620,6 +626,13 @@ ctx.Error(http.StatusForbidden)return } }) m.Group("/runners", func() { m.Get("", admin.Runners) m.Get("/reset_registration_token", admin.ResetRunnerRegistrationToken) m.Combo("/{runnerid}").Get(admin.EditRunner).Post(web.Bind(forms.EditRunnerForm{}), admin.EditRunnerPost) m.Post("/{runnerid}/delete", admin.DeleteRunnerPost) }, actions.MustEnableActions) }, func(ctx *context.Context) { ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable ctx.Data["EnablePackages"] = setting.Packages.Enabled
-
@@ -661,6 +674,8 @@ reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests)reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests) reqRepoProjectsReader := context.RequireRepoReader(unit.TypeProjects) reqRepoProjectsWriter := context.RequireRepoWriter(unit.TypeProjects) reqRepoActionsReader := context.RequireRepoReader(unit.TypeActions) reqRepoActionsWriter := context.RequireRepoWriter(unit.TypeActions) reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { return func(ctx *context.Context) {
-
@@ -774,6 +789,14 @@ m.Post("/delete", org.DeleteLabel)m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels) }) m.Group("/runners", func() { m.Get("", org.Runners) m.Combo("/{runnerid}").Get(org.RunnersEdit). Post(web.Bind(forms.EditRunnerForm{}), org.RunnersEditPost) m.Post("/{runnerid}/delete", org.RunnerDeletePost) m.Get("/reset_registration_token", org.ResetRunnerRegistrationToken) }, actions.MustEnableActions) m.Group("/secrets", func() { m.Get("", org.Secrets) m.Post("", web.Bind(forms.AddSecretForm{}), org.SecretsPost)
-
@@ -964,10 +987,12 @@ m.Group("/keys", func() {m.Combo("").Get(repo.DeployKeys). Post(web.Bind(forms.AddKeyForm{}), repo.DeployKeysPost) m.Post("/delete", repo.DeleteDeployKey) m.Group("/secrets", func() { m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost) m.Post("/delete", repo.DeleteSecret) }) }) m.Group("/secrets", func() { m.Get("", repo.Secrets) m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost) m.Post("/delete", repo.DeleteSecret) }) m.Group("/lfs", func() {
-
@@ -983,6 +1008,14 @@ m.Post("/", repo.LFSLockFile)m.Post("/{lid}/unlock", repo.LFSUnlock) }) }) m.Group("/runners", func() { m.Get("", repo.Runners) m.Combo("/{runnerid}").Get(repo.RunnersEdit). Post(web.Bind(forms.EditRunnerForm{}), repo.RunnersEditPost) m.Post("/{runnerid}/delete", repo.RunnerDeletePost) m.Get("/reset_registration_token", repo.ResetRunnerRegistrationToken) }, actions.MustEnableActions) }, func(ctx *context.Context) { ctx.Data["PageIsSettings"] = true ctx.Data["LFSStartServer"] = setting.LFS.StartServer
-
@@ -1229,6 +1262,23 @@ })}) }, reqRepoProjectsWriter, context.RepoMustNotBeArchived()) }, reqRepoProjectsReader, repo.MustEnableProjects) m.Group("/actions", func() { m.Get("", actions.List) m.Group("/runs/{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.Post("/cancel", reqRepoActionsWriter, actions.Cancel) }) }, reqRepoActionsReader, actions.MustEnableActions) m.Group("/wiki", func() { m.Combo("/").
-
-
-
@@ -0,0 +1,94 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "time" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" ) const ( zombieTaskTimeout = 10 * time.Minute endlessTaskTimeout = 3 * time.Hour abandonedJobTimeout = 24 * time.Hour ) // StopZombieTasks stops the task which have running status, but haven't been updated for a long time func StopZombieTasks(ctx context.Context) error { return stopTasks(ctx, actions_model.FindTaskOptions{ Status: actions_model.StatusRunning, UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-zombieTaskTimeout).Unix()), }) } // StopEndlessTasks stops the tasks which have running status and continuous updates, but don't end for a long time func StopEndlessTasks(ctx context.Context) error { return stopTasks(ctx, actions_model.FindTaskOptions{ Status: actions_model.StatusRunning, StartedBefore: timeutil.TimeStamp(time.Now().Add(-endlessTaskTimeout).Unix()), }) } func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error { tasks, err := actions_model.FindTasks(ctx, opts) if err != nil { return fmt.Errorf("find tasks: %w", err) } for _, task := range tasks { if err := db.WithTx(ctx, func(ctx context.Context) error { if err := actions_model.StopTask(ctx, task.ID, actions_model.StatusFailure); err != nil { return err } if err := task.LoadJob(ctx); err != nil { return err } return CreateCommitStatus(ctx, task.Job) }); err != nil { log.Warn("Cannot stop task %v: %v", task.ID, err) // go on } else if remove, err := actions.TransferLogs(ctx, task.LogFilename); err != nil { log.Warn("Cannot transfer logs of task %v: %v", task.ID, err) } else { remove() } } return nil } // CancelAbandonedJobs cancels the jobs which have waiting status, but haven't been picked by a runner for a long time func CancelAbandonedJobs(ctx context.Context) error { jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{ Statuses: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusBlocked}, UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-abandonedJobTimeout).Unix()), }) if err != nil { log.Warn("find abandoned tasks: %v", err) return err } now := timeutil.TimeStampNow() for _, job := range jobs { job.Status = actions_model.StatusCancelled job.Stopped = now if err := db.WithTx(ctx, func(ctx context.Context) error { if _, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped"); err != nil { return err } return CreateCommitStatus(ctx, job) }); err != nil { log.Warn("cancel abandoned job %v: %v", job.ID, err) // go on } } return nil }
-
-
-
@@ -0,0 +1,88 @@// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" ) func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) error { if err := job.LoadAttributes(ctx); err != nil { return fmt.Errorf("load run: %w", err) } run := job.Run if run.Event != webhook_module.HookEventPush { return nil } payload, err := run.GetPushEventPayload() if err != nil { return fmt.Errorf("GetPushEventPayload: %w", err) } creator, err := user_model.GetUserByID(ctx, payload.Pusher.ID) if err != nil { return fmt.Errorf("GetUserByID: %w", err) } repo := run.Repo sha := payload.HeadCommit.ID ctxname := job.Name state := toCommitStatus(job.Status) if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil { for _, v := range statuses { if v.Context == ctxname { if v.State == state { return nil } break } } } else { return fmt.Errorf("GetLatestCommitStatus: %w", err) } if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ Repo: repo, SHA: payload.HeadCommit.ID, Creator: creator, CommitStatus: &git_model.CommitStatus{ SHA: sha, TargetURL: run.HTMLURL(), Description: "", Context: ctxname, CreatorID: payload.Pusher.ID, State: state, }, }); err != nil { return fmt.Errorf("NewCommitStatus: %w", err) } return nil } func toCommitStatus(status actions_model.Status) api.CommitStatusState { switch status { case actions_model.StatusSuccess: return api.CommitStatusSuccess case actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped: return api.CommitStatusFailure case actions_model.StatusWaiting, actions_model.StatusBlocked: return api.CommitStatusPending case actions_model.StatusRunning: return api.CommitStatusRunning default: return api.CommitStatusError } }
-
-
services/actions/init.go (new)
-
@@ -0,0 +1,22 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" ) func Init() { if !setting.Actions.Enabled { return } jobEmitterQueue = queue.CreateUniqueQueue("actions_ready_job", jobEmitterQueueHandle, new(jobUpdate)) go graceful.GetManager().RunWithShutdownFns(jobEmitterQueue.Run) notification.RegisterNotifier(NewNotifier()) }
-
-
-
@@ -0,0 +1,140 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "errors" "fmt" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/queue" "xorm.io/builder" ) var jobEmitterQueue queue.UniqueQueue type jobUpdate struct { RunID int64 } func EmitJobsIfReady(runID int64) error { err := jobEmitterQueue.Push(&jobUpdate{ RunID: runID, }) if errors.Is(err, queue.ErrAlreadyInQueue) { return nil } return err } func jobEmitterQueueHandle(data ...queue.Data) []queue.Data { ctx := graceful.GetManager().ShutdownContext() var ret []queue.Data for _, d := range data { update := d.(*jobUpdate) if err := checkJobsOfRun(ctx, update.RunID); err != nil { ret = append(ret, d) } } return ret } func checkJobsOfRun(ctx context.Context, runID int64) error { return db.WithTx(ctx, func(ctx context.Context) error { jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: runID}) if err != nil { return err } idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) for _, job := range jobs { idToJobs[job.JobID] = append(idToJobs[job.JobID], job) } updates := newJobStatusResolver(jobs).Resolve() for _, job := range jobs { if status, ok := updates[job.ID]; ok { job.Status = status if n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": actions_model.StatusBlocked}, "status"); err != nil { return err } else if n != 1 { return fmt.Errorf("no affected for updating blocked job %v", job.ID) } } } return nil }) } type jobStatusResolver struct { statuses map[int64]actions_model.Status needs map[int64][]int64 } func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) for _, job := range jobs { idToJobs[job.JobID] = append(idToJobs[job.JobID], job) } statuses := make(map[int64]actions_model.Status, len(jobs)) needs := make(map[int64][]int64, len(jobs)) for _, job := range jobs { statuses[job.ID] = job.Status for _, need := range job.Needs { for _, v := range idToJobs[need] { needs[job.ID] = append(needs[job.ID], v.ID) } } } return &jobStatusResolver{ statuses: statuses, needs: needs, } } func (r *jobStatusResolver) Resolve() map[int64]actions_model.Status { ret := map[int64]actions_model.Status{} for i := 0; i < len(r.statuses); i++ { updated := r.resolve() if len(updated) == 0 { return ret } for k, v := range updated { ret[k] = v r.statuses[k] = v } } return ret } func (r *jobStatusResolver) resolve() map[int64]actions_model.Status { ret := map[int64]actions_model.Status{} for id, status := range r.statuses { if status != actions_model.StatusBlocked { continue } allDone, allSucceed := true, true for _, need := range r.needs[id] { needStatus := r.statuses[need] if !needStatus.IsDone() { allDone = false } if needStatus.In(actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped) { allSucceed = false } } if allDone { if allSucceed { ret[id] = actions_model.StatusWaiting } else { ret[id] = actions_model.StatusSkipped } } } return ret }
-
-
-
@@ -0,0 +1,80 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "testing" actions_model "code.gitea.io/gitea/models/actions" "github.com/stretchr/testify/assert" ) func Test_jobStatusResolver_Resolve(t *testing.T) { tests := []struct { name string jobs actions_model.ActionJobList want map[int64]actions_model.Status }{ { name: "no blocked", jobs: actions_model.ActionJobList{ {ID: 1, JobID: "1", Status: actions_model.StatusWaiting, Needs: []string{}}, {ID: 2, JobID: "2", Status: actions_model.StatusWaiting, Needs: []string{}}, {ID: 3, JobID: "3", Status: actions_model.StatusWaiting, Needs: []string{}}, }, want: map[int64]actions_model.Status{}, }, { name: "single blocked", jobs: actions_model.ActionJobList{ {ID: 1, JobID: "1", Status: actions_model.StatusSuccess, Needs: []string{}}, {ID: 2, JobID: "2", Status: actions_model.StatusBlocked, Needs: []string{"1"}}, {ID: 3, JobID: "3", Status: actions_model.StatusWaiting, Needs: []string{}}, }, want: map[int64]actions_model.Status{ 2: actions_model.StatusWaiting, }, }, { name: "multiple blocked", jobs: actions_model.ActionJobList{ {ID: 1, JobID: "1", Status: actions_model.StatusSuccess, Needs: []string{}}, {ID: 2, JobID: "2", Status: actions_model.StatusBlocked, Needs: []string{"1"}}, {ID: 3, JobID: "3", Status: actions_model.StatusBlocked, Needs: []string{"1"}}, }, want: map[int64]actions_model.Status{ 2: actions_model.StatusWaiting, 3: actions_model.StatusWaiting, }, }, { name: "chain blocked", jobs: actions_model.ActionJobList{ {ID: 1, JobID: "1", Status: actions_model.StatusFailure, Needs: []string{}}, {ID: 2, JobID: "2", Status: actions_model.StatusBlocked, Needs: []string{"1"}}, {ID: 3, JobID: "3", Status: actions_model.StatusBlocked, Needs: []string{"2"}}, }, want: map[int64]actions_model.Status{ 2: actions_model.StatusSkipped, 3: actions_model.StatusSkipped, }, }, { name: "loop need", jobs: actions_model.ActionJobList{ {ID: 1, JobID: "1", Status: actions_model.StatusBlocked, Needs: []string{"3"}}, {ID: 2, JobID: "2", Status: actions_model.StatusBlocked, Needs: []string{"1"}}, {ID: 3, JobID: "3", Status: actions_model.StatusBlocked, Needs: []string{"2"}}, }, want: map[int64]actions_model.Status{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := newJobStatusResolver(tt.jobs) assert.Equal(t, tt.want, r.Resolve()) }) } }
-
-
-
@@ -0,0 +1,528 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" packages_model "code.gitea.io/gitea/models/packages" perm_model "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/services/convert" ) type actionsNotifier struct { base.NullNotifier } var _ base.Notifier = &actionsNotifier{} // NewNotifier create a new actionsNotifier notifier func NewNotifier() base.Notifier { return &actionsNotifier{} } // NotifyNewIssue notifies issue created event func (n *actionsNotifier) NotifyNewIssue(ctx context.Context, issue *issues_model.Issue, _ []*user_model.User) { ctx = withMethod(ctx, "NotifyNewIssue") if err := issue.LoadRepo(ctx); err != nil { log.Error("issue.LoadRepo: %v", err) return } if err := issue.LoadPoster(ctx); err != nil { log.Error("issue.LoadPoster: %v", err) return } mode, _ := access_model.AccessLevel(ctx, issue.Poster, issue.Repo) newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).WithPayload(&api.IssuePayload{ Action: api.HookIssueOpened, Index: issue.Index, Issue: convert.ToAPIIssue(ctx, issue), Repository: convert.ToRepo(ctx, issue.Repo, mode), Sender: convert.ToUser(issue.Poster, nil), }).Notify(withMethod(ctx, "NotifyNewIssue")) } // NotifyIssueChangeStatus notifies close or reopen issue to notifiers func (n *actionsNotifier) NotifyIssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, _ *issues_model.Comment, isClosed bool) { ctx = withMethod(ctx, "NotifyIssueChangeStatus") mode, _ := access_model.AccessLevel(ctx, issue.Poster, issue.Repo) if issue.IsPull { if err := issue.LoadPullRequest(ctx); err != nil { log.Error("LoadPullRequest: %v", err) return } // Merge pull request calls issue.changeStatus so we need to handle separately. apiPullRequest := &api.PullRequestPayload{ Index: issue.Index, PullRequest: convert.ToAPIPullRequest(db.DefaultContext, issue.PullRequest, nil), Repository: convert.ToRepo(ctx, issue.Repo, mode), Sender: convert.ToUser(doer, nil), CommitID: commitID, } if isClosed { apiPullRequest.Action = api.HookIssueClosed } else { apiPullRequest.Action = api.HookIssueReOpened } newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequest). WithDoer(doer). WithPayload(apiPullRequest). Notify(ctx) return } apiIssue := &api.IssuePayload{ Index: issue.Index, Issue: convert.ToAPIIssue(ctx, issue), Repository: convert.ToRepo(ctx, issue.Repo, mode), Sender: convert.ToUser(doer, nil), } if isClosed { apiIssue.Action = api.HookIssueClosed } else { apiIssue.Action = api.HookIssueReOpened } newNotifyInputFromIssue(issue, webhook_module.HookEventIssues). WithDoer(doer). WithPayload(apiIssue). Notify(ctx) } func (n *actionsNotifier) NotifyIssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, _, _ []*issues_model.Label, ) { ctx = withMethod(ctx, "NotifyIssueChangeLabels") var err error if err = issue.LoadRepo(ctx); err != nil { log.Error("LoadRepo: %v", err) return } if err = issue.LoadPoster(ctx); err != nil { log.Error("LoadPoster: %v", err) return } mode, _ := access_model.AccessLevel(ctx, issue.Poster, issue.Repo) if issue.IsPull { if err = issue.LoadPullRequest(ctx); err != nil { log.Error("loadPullRequest: %v", err) return } if err = issue.PullRequest.LoadIssue(ctx); err != nil { log.Error("LoadIssue: %v", err) return } newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestLabel). WithDoer(doer). WithPayload(&api.PullRequestPayload{ Action: api.HookIssueLabelUpdated, Index: issue.Index, PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), Repository: convert.ToRepo(ctx, issue.Repo, perm_model.AccessModeNone), Sender: convert.ToUser(doer, nil), }). Notify(ctx) return } newNotifyInputFromIssue(issue, webhook_module.HookEventIssueLabel). WithDoer(doer). WithPayload(&api.IssuePayload{ Action: api.HookIssueLabelUpdated, Index: issue.Index, Issue: convert.ToAPIIssue(ctx, issue), Repository: convert.ToRepo(ctx, issue.Repo, mode), Sender: convert.ToUser(doer, nil), }). Notify(ctx) } // NotifyCreateIssueComment notifies comment on an issue to notifiers func (n *actionsNotifier) NotifyCreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, comment *issues_model.Comment, _ []*user_model.User, ) { ctx = withMethod(ctx, "NotifyCreateIssueComment") mode, _ := access_model.AccessLevel(ctx, doer, repo) if issue.IsPull { newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestComment). WithDoer(doer). WithPayload(&api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(ctx, issue), Comment: convert.ToComment(comment), Repository: convert.ToRepo(ctx, repo, mode), Sender: convert.ToUser(doer, nil), IsPull: true, }). Notify(ctx) return } newNotifyInputFromIssue(issue, webhook_module.HookEventIssueComment). WithDoer(doer). WithPayload(&api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(ctx, issue), Comment: convert.ToComment(comment), Repository: convert.ToRepo(ctx, repo, mode), Sender: convert.ToUser(doer, nil), IsPull: false, }). Notify(ctx) } func (n *actionsNotifier) NotifyNewPullRequest(ctx context.Context, pull *issues_model.PullRequest, _ []*user_model.User) { ctx = withMethod(ctx, "NotifyNewPullRequest") if err := pull.LoadIssue(ctx); err != nil { log.Error("pull.LoadIssue: %v", err) return } if err := pull.Issue.LoadRepo(ctx); err != nil { log.Error("pull.Issue.LoadRepo: %v", err) return } if err := pull.Issue.LoadPoster(ctx); err != nil { log.Error("pull.Issue.LoadPoster: %v", err) return } mode, _ := access_model.AccessLevel(ctx, pull.Issue.Poster, pull.Issue.Repo) newNotifyInputFromIssue(pull.Issue, webhook_module.HookEventPullRequest). WithPayload(&api.PullRequestPayload{ Action: api.HookIssueOpened, Index: pull.Issue.Index, PullRequest: convert.ToAPIPullRequest(ctx, pull, nil), Repository: convert.ToRepo(ctx, pull.Issue.Repo, mode), Sender: convert.ToUser(pull.Issue.Poster, nil), }). WithPullRequest(pull). Notify(ctx) } func (n *actionsNotifier) NotifyCreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) { ctx = withMethod(ctx, "NotifyCreateRepository") newNotifyInput(repo, doer, webhook_module.HookEventRepository).WithPayload(&api.RepositoryPayload{ Action: api.HookRepoCreated, Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), Organization: convert.ToUser(u, nil), Sender: convert.ToUser(doer, nil), }).Notify(ctx) } func (n *actionsNotifier) NotifyForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) { ctx = withMethod(ctx, "NotifyForkRepository") oldMode, _ := access_model.AccessLevel(ctx, doer, oldRepo) mode, _ := access_model.AccessLevel(ctx, doer, repo) // forked webhook newNotifyInput(oldRepo, doer, webhook_module.HookEventFork).WithPayload(&api.ForkPayload{ Forkee: convert.ToRepo(ctx, oldRepo, oldMode), Repo: convert.ToRepo(ctx, repo, mode), Sender: convert.ToUser(doer, nil), }).Notify(ctx) u := repo.MustOwner(ctx) // Add to hook queue for created repo after session commit. if u.IsOrganization() { newNotifyInput(repo, doer, webhook_module.HookEventRepository). WithRef(oldRepo.DefaultBranch). WithPayload(&api.RepositoryPayload{ Action: api.HookRepoCreated, Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), Organization: convert.ToUser(u, nil), Sender: convert.ToUser(doer, nil), }).Notify(ctx) } } func (n *actionsNotifier) NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, _ *issues_model.Comment, _ []*user_model.User) { ctx = withMethod(ctx, "NotifyPullRequestReview") var reviewHookType webhook_module.HookEventType switch review.Type { case issues_model.ReviewTypeApprove: reviewHookType = webhook_module.HookEventPullRequestReviewApproved case issues_model.ReviewTypeComment: reviewHookType = webhook_module.HookEventPullRequestComment case issues_model.ReviewTypeReject: reviewHookType = webhook_module.HookEventPullRequestReviewRejected default: // unsupported review webhook type here log.Error("Unsupported review webhook type") return } if err := pr.LoadIssue(ctx); err != nil { log.Error("pr.LoadIssue: %v", err) return } mode, err := access_model.AccessLevel(ctx, review.Issue.Poster, review.Issue.Repo) if err != nil { log.Error("models.AccessLevel: %v", err) return } newNotifyInput(review.Issue.Repo, review.Reviewer, reviewHookType). WithRef(review.CommitID). WithPayload(&api.PullRequestPayload{ Action: api.HookIssueReviewed, Index: review.Issue.Index, PullRequest: convert.ToAPIPullRequest(db.DefaultContext, pr, nil), Repository: convert.ToRepo(ctx, review.Issue.Repo, mode), Sender: convert.ToUser(review.Reviewer, nil), Review: &api.ReviewPayload{ Type: string(reviewHookType), Content: review.Content, }, }).Notify(ctx) } func (*actionsNotifier) NotifyMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { ctx = withMethod(ctx, "NotifyMergePullRequest") // Reload pull request information. if err := pr.LoadAttributes(ctx); err != nil { log.Error("LoadAttributes: %v", err) return } if err := pr.LoadIssue(ctx); err != nil { log.Error("LoadAttributes: %v", err) return } if err := pr.Issue.LoadRepo(db.DefaultContext); err != nil { log.Error("pr.Issue.LoadRepo: %v", err) return } mode, err := access_model.AccessLevel(ctx, doer, pr.Issue.Repo) if err != nil { log.Error("models.AccessLevel: %v", err) return } // Merge pull request calls issue.changeStatus so we need to handle separately. apiPullRequest := &api.PullRequestPayload{ Index: pr.Issue.Index, PullRequest: convert.ToAPIPullRequest(db.DefaultContext, pr, nil), Repository: convert.ToRepo(ctx, pr.Issue.Repo, mode), Sender: convert.ToUser(doer, nil), Action: api.HookIssueClosed, } newNotifyInput(pr.Issue.Repo, doer, webhook_module.HookEventPullRequest). WithRef(pr.MergedCommitID). WithPayload(apiPullRequest). WithPullRequest(pr). Notify(ctx) } func (n *actionsNotifier) NotifyPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) { ctx = withMethod(ctx, "NotifyPushCommits") apiPusher := convert.ToUser(pusher, nil) apiCommits, apiHeadCommit, err := commits.ToAPIPayloadCommits(ctx, repo.RepoPath(), repo.HTMLURL()) if err != nil { log.Error("commits.ToAPIPayloadCommits failed: %v", err) return } newNotifyInput(repo, pusher, webhook_module.HookEventPush). WithRef(opts.RefFullName). WithPayload(&api.PushPayload{ Ref: opts.RefFullName, Before: opts.OldCommitID, After: opts.NewCommitID, CompareURL: setting.AppURL + commits.CompareURL, Commits: apiCommits, HeadCommit: apiHeadCommit, Repo: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), Pusher: apiPusher, Sender: apiPusher, }). Notify(ctx) } func (n *actionsNotifier) NotifyCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) { ctx = withMethod(ctx, "NotifyCreateRef") apiPusher := convert.ToUser(pusher, nil) apiRepo := convert.ToRepo(ctx, repo, perm_model.AccessModeNone) refName := git.RefEndName(refFullName) newNotifyInput(repo, pusher, webhook_module.HookEventCreate). WithRef(refName). WithPayload(&api.CreatePayload{ Ref: refName, Sha: refID, RefType: refType, Repo: apiRepo, Sender: apiPusher, }). Notify(ctx) } func (n *actionsNotifier) NotifyDeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName string) { ctx = withMethod(ctx, "NotifyDeleteRef") apiPusher := convert.ToUser(pusher, nil) apiRepo := convert.ToRepo(ctx, repo, perm_model.AccessModeNone) refName := git.RefEndName(refFullName) newNotifyInput(repo, pusher, webhook_module.HookEventDelete). WithRef(refName). WithPayload(&api.DeletePayload{ Ref: refName, RefType: refType, PusherType: api.PusherTypeUser, Repo: apiRepo, Sender: apiPusher, }). Notify(ctx) } func (n *actionsNotifier) NotifySyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) { ctx = withMethod(ctx, "NotifySyncPushCommits") apiPusher := convert.ToUser(pusher, nil) apiCommits, apiHeadCommit, err := commits.ToAPIPayloadCommits(db.DefaultContext, repo.RepoPath(), repo.HTMLURL()) if err != nil { log.Error("commits.ToAPIPayloadCommits failed: %v", err) return } newNotifyInput(repo, pusher, webhook_module.HookEventPush). WithRef(opts.RefFullName). WithPayload(&api.PushPayload{ Ref: opts.RefFullName, Before: opts.OldCommitID, After: opts.NewCommitID, CompareURL: setting.AppURL + commits.CompareURL, Commits: apiCommits, TotalCommits: commits.Len, HeadCommit: apiHeadCommit, Repo: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), Pusher: apiPusher, Sender: apiPusher, }). Notify(ctx) } func (n *actionsNotifier) NotifySyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) { ctx = withMethod(ctx, "NotifySyncCreateRef") n.NotifyCreateRef(ctx, pusher, repo, refType, refFullName, refID) } func (n *actionsNotifier) NotifySyncDeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName string) { ctx = withMethod(ctx, "NotifySyncDeleteRef") n.NotifyDeleteRef(ctx, pusher, repo, refType, refFullName) } func (n *actionsNotifier) NotifyNewRelease(ctx context.Context, rel *repo_model.Release) { ctx = withMethod(ctx, "NotifyNewRelease") notifyRelease(ctx, rel.Publisher, rel, rel.Sha1, api.HookReleasePublished) } func (n *actionsNotifier) NotifyUpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) { ctx = withMethod(ctx, "NotifyUpdateRelease") notifyRelease(ctx, doer, rel, rel.Sha1, api.HookReleaseUpdated) } func (n *actionsNotifier) NotifyDeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) { ctx = withMethod(ctx, "NotifyDeleteRelease") notifyRelease(ctx, doer, rel, rel.Sha1, api.HookReleaseDeleted) } func (n *actionsNotifier) NotifyPackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { ctx = withMethod(ctx, "NotifyPackageCreate") notifyPackage(ctx, doer, pd, api.HookPackageCreated) } func (n *actionsNotifier) NotifyPackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { ctx = withMethod(ctx, "NotifyPackageDelete") notifyPackage(ctx, doer, pd, api.HookPackageDeleted) } func (n *actionsNotifier) NotifyAutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { ctx = withMethod(ctx, "NotifyAutoMergePullRequest") n.NotifyMergePullRequest(ctx, doer, pr) } func (n *actionsNotifier) NotifyPullRequestSynchronized(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { ctx = withMethod(ctx, "NotifyPullRequestSynchronized") if err := pr.LoadIssue(ctx); err != nil { log.Error("LoadAttributes: %v", err) return } if err := pr.Issue.LoadRepo(db.DefaultContext); err != nil { log.Error("pr.Issue.LoadRepo: %v", err) return } newNotifyInput(pr.Issue.Repo, doer, webhook_module.HookEventPullRequestSync). WithPayload(&api.PullRequestPayload{ Action: api.HookIssueSynchronized, Index: pr.Issue.Index, PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), Repository: convert.ToRepo(ctx, pr.Issue.Repo, perm_model.AccessModeNone), Sender: convert.ToUser(doer, nil), }). WithPullRequest(pr). Notify(ctx) } func (n *actionsNotifier) NotifyPullRequestChangeTargetBranch(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, oldBranch string) { ctx = withMethod(ctx, "NotifyPullRequestChangeTargetBranch") if err := pr.LoadIssue(ctx); err != nil { log.Error("LoadAttributes: %v", err) return } if err := pr.Issue.LoadRepo(db.DefaultContext); err != nil { log.Error("pr.Issue.LoadRepo: %v", err) return } mode, _ := access_model.AccessLevel(ctx, pr.Issue.Poster, pr.Issue.Repo) newNotifyInput(pr.Issue.Repo, doer, webhook_module.HookEventPullRequest). WithPayload(&api.PullRequestPayload{ Action: api.HookIssueEdited, Index: pr.Issue.Index, Changes: &api.ChangesPayload{ Ref: &api.ChangesFromPayload{ From: oldBranch, }, }, PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), Repository: convert.ToRepo(ctx, pr.Issue.Repo, mode), Sender: convert.ToUser(doer, nil), }). WithPullRequest(pr). Notify(ctx) }
-
-
-
@@ -0,0 +1,236 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" "strings" actions_model "code.gitea.io/gitea/models/actions" issues_model "code.gitea.io/gitea/models/issues" packages_model "code.gitea.io/gitea/models/packages" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" actions_module "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/services/convert" "github.com/nektos/act/pkg/jobparser" ) var methodCtxKey struct{} // withMethod sets the notification method that this context currently executes. // Used for debugging/ troubleshooting purposes. func withMethod(ctx context.Context, method string) context.Context { // don't overwrite if v := ctx.Value(methodCtxKey); v != nil { if _, ok := v.(string); ok { return ctx } } return context.WithValue(ctx, methodCtxKey, method) } // getMethod gets the notification method that this context currently executes. // Default: "notify" // Used for debugging/ troubleshooting purposes. func getMethod(ctx context.Context) string { if v := ctx.Value(methodCtxKey); v != nil { if s, ok := v.(string); ok { return s } } return "notify" } type notifyInput struct { // required Repo *repo_model.Repository Doer *user_model.User Event webhook_module.HookEventType // optional Ref string Payload api.Payloader PullRequest *issues_model.PullRequest } func newNotifyInput(repo *repo_model.Repository, doer *user_model.User, event webhook_module.HookEventType) *notifyInput { return ¬ifyInput{ Repo: repo, Doer: doer, Event: event, } } func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput { input.Doer = doer return input } func (input *notifyInput) WithRef(ref string) *notifyInput { input.Ref = ref return input } func (input *notifyInput) WithPayload(payload api.Payloader) *notifyInput { input.Payload = payload return input } func (input *notifyInput) WithPullRequest(pr *issues_model.PullRequest) *notifyInput { input.PullRequest = pr if input.Ref == "" { input.Ref = pr.GetGitRefName() } return input } func (input *notifyInput) Notify(ctx context.Context) { log.Trace("execute %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name) if err := notify(ctx, input); err != nil { log.Error("an error occurred while executing the %s actions method: %v", getMethod(ctx), err) } } func notify(ctx context.Context, input *notifyInput) error { if input.Doer.IsActions() { // avoiding triggering cyclically, for example: // a comment of an issue will trigger the runner to add a new comment as reply, // and the new comment will trigger the runner again. log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name) return nil } if unit_model.TypeActions.UnitGlobalDisabled() { return nil } if err := input.Repo.LoadUnits(ctx); err != nil { return fmt.Errorf("repo.LoadUnits: %w", err) } else if !input.Repo.UnitEnabled(ctx, unit_model.TypeActions) { return nil } gitRepo, err := git.OpenRepository(context.Background(), input.Repo.RepoPath()) if err != nil { return fmt.Errorf("git.OpenRepository: %w", err) } defer gitRepo.Close() ref := input.Ref if ref == "" { ref = input.Repo.DefaultBranch } // Get the commit object for the ref commit, err := gitRepo.GetCommit(ref) if err != nil { return fmt.Errorf("gitRepo.GetCommit: %w", err) } workflows, err := actions_module.DetectWorkflows(commit, input.Event, input.Payload) if err != nil { return fmt.Errorf("DetectWorkflows: %w", err) } if len(workflows) == 0 { log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID) return nil } p, err := json.Marshal(input.Payload) if err != nil { return fmt.Errorf("json.Marshal: %w", err) } for id, content := range workflows { run := actions_model.ActionRun{ Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0], RepoID: input.Repo.ID, OwnerID: input.Repo.OwnerID, WorkflowID: id, TriggerUserID: input.Doer.ID, Ref: ref, CommitSHA: commit.ID.String(), IsForkPullRequest: input.PullRequest != nil && input.PullRequest.IsFromFork(), Event: input.Event, EventPayload: string(p), Status: actions_model.StatusWaiting, } jobs, err := jobparser.Parse(content) if err != nil { log.Error("jobparser.Parse: %v", err) continue } if err := actions_model.InsertRun(ctx, &run, jobs); err != nil { log.Error("InsertRun: %v", err) continue } if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil { log.Error("FindRunJobs: %v", err) } else { for _, job := range jobs { if err := CreateCommitStatus(ctx, job); err != nil { log.Error("CreateCommitStatus: %v", err) } } } } return nil } func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput { return newNotifyInput(issue.Repo, issue.Poster, event) } func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release, ref string, action api.HookReleaseAction) { if err := rel.LoadAttributes(ctx); err != nil { log.Error("LoadAttributes: %v", err) return } mode, _ := access_model.AccessLevel(ctx, doer, rel.Repo) newNotifyInput(rel.Repo, doer, webhook_module.HookEventRelease). WithRef(ref). WithPayload(&api.ReleasePayload{ Action: action, Release: convert.ToRelease(rel), Repository: convert.ToRepo(ctx, rel.Repo, mode), Sender: convert.ToUser(doer, nil), }). Notify(ctx) } func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) { if pd.Repository == nil { // When a package is uploaded to an organization, it could trigger an event to notify. // So the repository could be nil, however, actions can't support that yet. // See https://github.com/go-gitea/gitea/pull/17940 return } apiPackage, err := convert.ToPackage(ctx, pd, sender) if err != nil { log.Error("Error converting package: %v", err) return } newNotifyInput(pd.Repository, sender, webhook_module.HookEventPackage). WithPayload(&api.PackagePayload{ Action: action, Package: apiPackage, Sender: convert.ToUser(sender, nil), }). Notify(ctx) }
-
-
-
@@ -8,6 +8,7 @@ import ("net/http" "strings" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base"
-
@@ -70,6 +71,7 @@ } else {log.Trace("Basic Authorization: Attempting login with username as token") } // check oauth2 token uid := CheckOAuthAccessToken(authToken) if uid != 0 { log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
-
@@ -84,6 +86,7 @@ store.GetData()["IsApiToken"] = truereturn u, nil } // check personal access token token, err := auth_model.GetAccessTokenBySHA(authToken) if err == nil { log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
-
@@ -102,6 +105,17 @@ store.GetData()["IsApiToken"] = truereturn u, nil } else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) { log.Error("GetAccessTokenBySha: %v", err) } // check task token task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken) if err == nil && task != nil { log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID) store.GetData()["IsActionsToken"] = true store.GetData()["ActionsTaskID"] = task.ID return user_model.NewActionsUser(), nil } if !setting.Service.EnableBasicAuth {
-
-
-
@@ -9,6 +9,7 @@ "net/http""strings" "time" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user"
-
@@ -94,7 +95,18 @@ return uid} t, err := auth_model.GetAccessTokenBySHA(tokenSHA) if err != nil { if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) { if auth_model.IsErrAccessTokenNotExist(err) { // check task token task, err := actions_model.GetRunningTaskByToken(db.DefaultContext, tokenSHA) if err == nil && task != nil { log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID) store.GetData()["IsActionsToken"] = true store.GetData()["ActionsTaskID"] = task.ID return user_model.ActionsUserID } } else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) { log.Error("GetAccessTokenBySHA: %v", err) } return 0
-
@@ -118,12 +130,13 @@ return nil, nil} id := o.userIDFromToken(req, store) if id <= 0 { if id <= 0 && id != -2 { // -2 means actions, so we need to allow it. return nil, nil } log.Trace("OAuth2 Authorization: Found token for user[%d]", id) user, err := user_model.GetUserByID(req.Context(), id) user, err := user_model.GetPossibleUserByID(req.Context(), id) if err != nil { if !user_model.IsErrUserNotExist(err) { log.Error("GetUserByName: %v", err)
-
-
-
@@ -196,22 +196,39 @@ return false} // List all group memberships of a user func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string { func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGroupFilter bool) []string { var ldapGroups []string groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid)) var searchFilter string groupFilter, ok := source.sanitizedGroupFilter(source.GroupFilter) if !ok { return ldapGroups } groupDN, ok := source.sanitizedGroupDN(source.GroupDN) if !ok { return ldapGroups } if applyGroupFilter { searchFilter = fmt.Sprintf("(&(%s)(%s=%s))", groupFilter, source.GroupMemberUID, ldap.EscapeFilter(uid)) } else { searchFilter = fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid)) } result, err := l.Search(ldap.NewSearchRequest( source.GroupDN, groupDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, groupFilter, searchFilter, []string{}, nil, )) if err != nil { log.Error("Failed group search using filter[%s]: %v", groupFilter, err) log.Error("Failed group search in LDAP with filter [%s]: %v", searchFilter, err) return ldapGroups }
-
@@ -238,9 +255,7 @@ return ldapGroupsToTeams} // getMappedMemberships : returns the organizations and teams to modify the users membership func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string][]string, map[string][]string) { // get all LDAP group memberships for user usersLdapGroups := source.listLdapGroupMemberships(l, uid) func (source *Source) getMappedMemberships(usersLdapGroups []string, uid string) (map[string][]string, map[string][]string) { // unmarshall LDAP group team map from configs ldapGroupsToTeams := source.mapLdapGroupsToTeams() membershipsToAdd := map[string][]string{}
-
@@ -258,6 +273,14 @@ }} } return membershipsToAdd, membershipsToRemove } func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string { if strings.ToLower(source.UserUID) == "dn" { return entry.DN } return entry.GetAttributeValue(source.UserUID) } // SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
-
@@ -375,58 +398,30 @@ username := sr.Entries[0].GetAttributeValue(source.AttributeUsername)firstname := sr.Entries[0].GetAttributeValue(source.AttributeName) surname := sr.Entries[0].GetAttributeValue(source.AttributeSurname) mail := sr.Entries[0].GetAttributeValue(source.AttributeMail) uid := sr.Entries[0].GetAttributeValue(source.UserUID) if source.UserUID == "dn" || source.UserUID == "DN" { uid = sr.Entries[0].DN } // Check group membership if source.GroupsEnabled && source.GroupFilter != "" { groupFilter, ok := source.sanitizedGroupFilter(source.GroupFilter) if !ok { return nil } groupDN, ok := source.sanitizedGroupDN(source.GroupDN) if !ok { return nil } teamsToAdd := make(map[string][]string) teamsToRemove := make(map[string][]string) log.Trace("Fetching groups '%v' with filter '%s' and base '%s'", source.GroupMemberUID, groupFilter, groupDN) groupSearch := ldap.NewSearchRequest( groupDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, groupFilter, []string{source.GroupMemberUID}, nil) // Check group membership if source.GroupsEnabled { userAttributeListedInGroup := source.getUserAttributeListedInGroup(sr.Entries[0]) usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) srg, err := l.Search(groupSearch) if err != nil { log.Error("LDAP group search failed: %v", err) if source.GroupFilter != "" && len(usersLdapGroups) == 0 { return nil } else if len(srg.Entries) < 1 { log.Error("LDAP group search failed: 0 entries") return nil } isMember := false Entries: for _, group := range srg.Entries { for _, member := range group.GetAttributeValues(source.GroupMemberUID) { if (source.UserUID == "dn" && member == sr.Entries[0].DN) || member == uid { isMember = true break Entries } } } if !isMember { log.Error("LDAP group membership test failed") return nil if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup) } } if isAttributeSSHPublicKeySet { sshPublicKey = sr.Entries[0].GetAttributeValues(source.AttributeSSHPublicKey) } isAdmin := checkAdmin(l, source, userDN) var isRestricted bool if !isAdmin { isRestricted = checkRestricted(l, source, userDN)
-
@@ -434,12 +429,6 @@ }if isAtributeAvatarSet { Avatar = sr.Entries[0].GetRawAttributeValue(source.AttributeAvatar) } teamsToAdd := make(map[string][]string) teamsToRemove := make(map[string][]string) if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { teamsToAdd, teamsToRemove = source.getMappedMemberships(l, uid) } if !directBind && source.AttributesInBind {
-
@@ -520,19 +509,29 @@ log.Error("LDAP Search failed unexpectedly! (%v)", err)return nil, err } result := make([]*SearchResult, len(sr.Entries)) result := make([]*SearchResult, 0, len(sr.Entries)) for i, v := range sr.Entries { for _, v := range sr.Entries { teamsToAdd := make(map[string][]string) teamsToRemove := make(map[string][]string) if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { userAttributeListedInGroup := v.GetAttributeValue(source.UserUID) if source.UserUID == "dn" || source.UserUID == "DN" { userAttributeListedInGroup = v.DN if source.GroupsEnabled { userAttributeListedInGroup := source.getUserAttributeListedInGroup(v) if source.GroupFilter != "" { usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) if len(usersLdapGroups) == 0 { continue } } teamsToAdd, teamsToRemove = source.getMappedMemberships(l, userAttributeListedInGroup) if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, false) teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup) } } result[i] = &SearchResult{ user := &SearchResult{ Username: v.GetAttributeValue(source.AttributeUsername), Name: v.GetAttributeValue(source.AttributeName), Surname: v.GetAttributeValue(source.AttributeSurname),
-
@@ -541,16 +540,22 @@ IsAdmin: checkAdmin(l, source, v.DN),LdapTeamAdd: teamsToAdd, LdapTeamRemove: teamsToRemove, } if !result[i].IsAdmin { result[i].IsRestricted = checkRestricted(l, source, v.DN) if !user.IsAdmin { user.IsRestricted = checkRestricted(l, source, v.DN) } if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(source.AttributeSSHPublicKey) user.SSHPublicKey = v.GetAttributeValues(source.AttributeSSHPublicKey) } if isAtributeAvatarSet { result[i].Avatar = v.GetRawAttributeValue(source.AttributeAvatar) user.Avatar = v.GetRawAttributeValue(source.AttributeAvatar) } result[i].LowerName = strings.ToLower(result[i].Username) user.LowerName = strings.ToLower(user.Username) result = append(result, user) } return result, nil
-
-
-
@@ -164,7 +164,7 @@ }func handlePull(pullID int64, sha string) { ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("Handle AutoMerge of pull[%d] with sha[%s]", pullID, sha)) fmt.Sprintf("Handle AutoMerge of PR[%d] with sha[%s]", pullID, sha)) defer finished() pr, err := issues_model.GetPullRequestByID(ctx, pullID)
-
@@ -176,7 +176,7 @@// Check if there is a scheduled pr in the db exists, scheduledPRM, err := pull_model.GetScheduledMergeByPullID(ctx, pr.ID) if err != nil { log.Error("pull[%d] GetScheduledMergeByPullID: %v", pr.ID, err) log.Error("%-v GetScheduledMergeByPullID: %v", pr, err) return } if !exists {
-
@@ -188,13 +188,13 @@ // We get the latest sha commit hash again to handle the case where the check of a previous push// did not succeed or was not finished yet. if err = pr.LoadHeadRepo(ctx); err != nil { log.Error("pull[%d] LoadHeadRepo: %v", pr.ID, err) log.Error("%-v LoadHeadRepo: %v", pr, err) return } headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath()) if err != nil { log.Error("OpenRepository: %v", err) log.Error("OpenRepository %-v: %v", pr.HeadRepo, err) return } defer headGitRepo.Close()
-
@@ -202,40 +202,40 @@headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch) if pr.HeadRepo == nil || !headBranchExist { log.Warn("Head branch of auto merge pr does not exist [HeadRepoID: %d, Branch: %s, PR ID: %d]", pr.HeadRepoID, pr.HeadBranch, pr.ID) log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch) return } // Check if all checks succeeded pass, err := pull_service.IsPullCommitStatusPass(ctx, pr) if err != nil { log.Error("IsPullCommitStatusPass: %v", err) log.Error("%-v IsPullCommitStatusPass: %v", pr, err) return } if !pass { log.Info("Scheduled auto merge pr has unsuccessful status checks [PullID: %d]", pr.ID) log.Info("Scheduled auto merge %-v has unsuccessful status checks", pr) return } // Merge if all checks succeeded doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID) if err != nil { log.Error("GetUserByIDCtx: %v", err) log.Error("Unable to get scheduled User[%d]: %v", scheduledPRM.DoerID, err) return } perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) if err != nil { log.Error("GetUserRepoPermission: %v", err) log.Error("GetUserRepoPermission %-v: %v", pr.HeadRepo, err) return } if err := pull_service.CheckPullMergable(ctx, doer, &perm, pr, false, false); err != nil { if errors.Is(pull_service.ErrUserNotAllowedToMerge, err) { log.Info("PR %d was scheduled to automerge by an unauthorized user", pr.ID) log.Info("%-v was scheduled to automerge by an unauthorized user", pr) return } log.Error("pull[%d] CheckPullMergable: %v", pr.ID, err) log.Error("%-v CheckPullMergable: %v", pr, err) return }
-
@@ -244,13 +244,13 @@ if pr.BaseRepoID == pr.HeadRepoID {baseGitRepo = headGitRepo } else { if err = pr.LoadBaseRepo(ctx); err != nil { log.Error("LoadBaseRepo: %v", err) log.Error("%-v LoadBaseRepo: %v", pr, err) return } baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) if err != nil { log.Error("OpenRepository: %v", err) log.Error("OpenRepository %-v: %v", pr.BaseRepo, err) return } defer baseGitRepo.Close()
-
-
-
@@ -30,6 +30,7 @@ defer pprof.SetGoroutineLabels(original)_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().ShutdownContext(), "Service: Cron", process.SystemProcessType, true) initBasicTasks() initExtendedTasks() initActionsTasks() lock.Lock() for _, task := range tasks {
-
-
-
@@ -0,0 +1,51 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package cron import ( "context" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" actions_service "code.gitea.io/gitea/services/actions" ) func initActionsTasks() { if !setting.Actions.Enabled { return } registerStopZombieTasks() registerStopEndlessTasks() registerCancelAbandonedJobs() } func registerStopZombieTasks() { RegisterTaskFatal("stop_zombie_tasks", &BaseConfig{ Enabled: true, RunAtStart: true, Schedule: "@every 5m", }, func(ctx context.Context, _ *user_model.User, cfg Config) error { return actions_service.StopZombieTasks(ctx) }) } func registerStopEndlessTasks() { RegisterTaskFatal("stop_endless_tasks", &BaseConfig{ Enabled: true, RunAtStart: true, Schedule: "@every 30m", }, func(ctx context.Context, _ *user_model.User, cfg Config) error { return actions_service.StopEndlessTasks(ctx) }) } func registerCancelAbandonedJobs() { RegisterTaskFatal("cancel_abandoned_jobs", &BaseConfig{ Enabled: true, RunAtStart: true, Schedule: "@every 6h", }, func(ctx context.Context, _ *user_model.User, cfg Config) error { return actions_service.CancelAbandonedJobs(ctx) }) }
-
-
-
@@ -59,11 +59,7 @@ Args: []string{},}, func(ctx context.Context, _ *user_model.User, config Config) error { rhcConfig := config.(*RepoHealthCheckConfig) // the git args are set by config, they can be safe to be trusted args := make([]git.CmdArg, 0, len(rhcConfig.Args)) for _, arg := range rhcConfig.Args { args = append(args, git.CmdArg(arg)) } return repo_service.GitFsckRepos(ctx, rhcConfig.Timeout, args) return repo_service.GitFsckRepos(ctx, rhcConfig.Timeout, git.ToTrustedCmdArgs(rhcConfig.Args)) }) }
-
-
-
@@ -61,11 +61,7 @@ Args: setting.Git.GCArgs,}, func(ctx context.Context, _ *user_model.User, config Config) error { rhcConfig := config.(*RepoHealthCheckConfig) // the git args are set by config, they can be safe to be trusted args := make([]git.CmdArg, 0, len(rhcConfig.Args)) for _, arg := range rhcConfig.Args { args = append(args, git.CmdArg(arg)) } return repo_service.GitGcRepos(ctx, rhcConfig.Timeout, args...) return repo_service.GitGcRepos(ctx, rhcConfig.Timeout, git.ToTrustedCmdArgs(rhcConfig.Args)) }) }
-
-
-
@@ -15,7 +15,7 @@type PackageCleanupRuleForm struct { ID int64 Enabled bool Type string `binding:"Required;In(composer,conan,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,vagrant)"` Type string `binding:"Required;In(composer,conan,conda,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,vagrant)"` KeepCount int `binding:"In(0,1,5,10,25,50,100)"` KeepPattern string `binding:"RegexPattern"` RemoveDays int `binding:"In(0,7,14,30,60,90,180)"`
-
-
-
@@ -146,8 +146,10 @@ TrackerIssueStyle stringExternalTrackerRegexpPattern string EnableCloseIssuesViaCommitInAnyBranch bool EnableProjects bool EnableReleases bool EnablePackages bool EnablePulls bool EnableActions bool PullsIgnoreWhitespace bool PullsAllowMerge bool PullsAllowRebase bool
-
-
services/forms/runner.go (new)
-
@@ -0,0 +1,25 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package forms import ( "net/http" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/binding" ) // EditRunnerForm form for admin to create runner type EditRunnerForm struct { Description string CustomLabels string // comma-separated } // Validate validates form fields func (f *EditRunnerForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) }
-
-
-
@@ -1056,7 +1056,7 @@ SkipTo stringMaxLines int MaxLineCharacters int MaxFiles int WhitespaceBehavior git.CmdArg WhitespaceBehavior git.TrustedCmdArgs DirectComparison bool }
-
@@ -1071,38 +1071,22 @@ if err != nil {return nil, err } argsLength := 6 if len(opts.WhitespaceBehavior) > 0 { argsLength++ } if len(opts.SkipTo) > 0 { argsLength++ } if len(files) > 0 { argsLength += len(files) + 1 } diffArgs := make([]git.CmdArg, 0, argsLength) cmdDiff := git.NewCommand(gitRepo.Ctx) if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA) && commit.ParentCount() == 0 { diffArgs = append(diffArgs, "diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M") if len(opts.WhitespaceBehavior) != 0 { diffArgs = append(diffArgs, opts.WhitespaceBehavior) } // append empty tree ref diffArgs = append(diffArgs, "4b825dc642cb6eb9a060e54bf8d69288fbee4904") diffArgs = append(diffArgs, git.CmdArgCheck(opts.AfterCommitID)) cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). AddArguments(opts.WhitespaceBehavior...). AddArguments("4b825dc642cb6eb9a060e54bf8d69288fbee4904"). // append empty tree ref AddDynamicArguments(opts.AfterCommitID) } else { actualBeforeCommitID := opts.BeforeCommitID if len(actualBeforeCommitID) == 0 { parentCommit, _ := commit.Parent(0) actualBeforeCommitID = parentCommit.ID.String() } diffArgs = append(diffArgs, "diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M") if len(opts.WhitespaceBehavior) != 0 { diffArgs = append(diffArgs, opts.WhitespaceBehavior) } diffArgs = append(diffArgs, git.CmdArgCheck(actualBeforeCommitID)) diffArgs = append(diffArgs, git.CmdArgCheck(opts.AfterCommitID)) cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). AddArguments(opts.WhitespaceBehavior...). AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID) opts.BeforeCommitID = actualBeforeCommitID }
-
@@ -1111,16 +1095,11 @@ // so if we are using at least this version of git we don't have to tell ParsePatch to do// the skipping for us parsePatchSkipToFile := opts.SkipTo if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil { diffArgs = append(diffArgs, git.CmdArg("--skip-to="+opts.SkipTo)) cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo) parsePatchSkipToFile = "" } if len(files) > 0 { diffArgs = append(diffArgs, "--") for _, file := range files { diffArgs = append(diffArgs, git.CmdArg(file)) // it's safe to cast it to CmdArg because there is a "--" before } } cmdDiff.AddDashesAndList(files...) reader, writer := io.Pipe() defer func() {
-
@@ -1128,10 +1107,9 @@ _ = reader.Close()_ = writer.Close() }() go func(ctx context.Context, diffArgs []git.CmdArg, repoPath string, writer *io.PipeWriter) { cmd := git.NewCommand(ctx, diffArgs...) cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) if err := cmd.Run(&git.RunOpts{ go func() { cmdDiff.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) if err := cmdDiff.Run(&git.RunOpts{ Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, Dir: repoPath, Stderr: os.Stderr,
-
@@ -1141,7 +1119,7 @@ log.Error("error during RunWithContext: %w", err)} _ = writer.Close() }(gitRepo.Ctx, diffArgs, repoPath, writer) }() diff, err := ParsePatch(opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile) if err != nil {
-
@@ -1201,16 +1179,16 @@ if opts.DirectComparison {separator = ".." } shortstatArgs := []git.CmdArg{git.CmdArgCheck(opts.BeforeCommitID + separator + opts.AfterCommitID)} diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { shortstatArgs = []git.CmdArg{git.EmptyTreeSHA, git.CmdArgCheck(opts.AfterCommitID)} diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} } diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, shortstatArgs...) diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) if err != nil && strings.Contains(err.Error(), "no merge base") { // git >= 2.28 now returns an error if base and head have become unrelated. // previously it would return the results of git diff --shortstat base head so let's try that... shortstatArgs = []git.CmdArg{git.CmdArgCheck(opts.BeforeCommitID), git.CmdArgCheck(opts.AfterCommitID)} diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, shortstatArgs...) diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID} diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) } if err != nil { return nil, err
-
@@ -1324,17 +1302,17 @@ return diff} // GetWhitespaceFlag returns git diff flag for treating whitespaces func GetWhitespaceFlag(whitespaceBehavior string) git.CmdArg { whitespaceFlags := map[string]string{ "ignore-all": "-w", "ignore-change": "-b", "ignore-eol": "--ignore-space-at-eol", "show-all": "", func GetWhitespaceFlag(whitespaceBehavior string) git.TrustedCmdArgs { whitespaceFlags := map[string]git.TrustedCmdArgs{ "ignore-all": {"-w"}, "ignore-change": {"-b"}, "ignore-eol": {"--ignore-space-at-eol"}, "show-all": nil, } if flag, ok := whitespaceFlags[whitespaceBehavior]; ok { return git.CmdArg(flag) return flag } log.Warn("unknown whitespace behavior: %q, default to 'show-all'", whitespaceBehavior) return "" return nil }
-
-
-
@@ -626,7 +626,7 @@ if !assert.NoError(t, err) {return } defer gitRepo.Close() for _, behavior := range []git.CmdArg{"-w", "--ignore-space-at-eol", "-b", ""} { for _, behavior := range []git.TrustedCmdArgs{{"-w"}, {"--ignore-space-at-eol"}, {"-b"}, nil} { diffs, err := GetDiff(gitRepo, &DiffOptions{ AfterCommitID: "bd7063cc7c04689c4d082183d32a604ed27a24f9",
-
-
-
@@ -71,8 +71,14 @@ if err != nil {return err } if !perm.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsLocked && !doer.IsAdmin { // Locked issues require write permissions if issue.IsLocked && !perm.CanWriteIssuesOrPulls(issue.IsPull) && !doer.IsAdmin { log.Debug("can't write issue or pull") return nil } if !perm.CanReadIssuesOrPulls(issue.IsPull) { log.Debug("can't read issue or pull") return nil }
-
-
-
@@ -203,11 +203,11 @@ timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Secondlog.Trace("SyncMirrors [repo: %-v]: running git remote update...", m.Repo) gitArgs := []git.CmdArg{"remote", "update"} cmd := git.NewCommand(ctx, "remote", "update") if m.EnablePrune { gitArgs = append(gitArgs, "--prune") cmd.AddArguments("--prune") } gitArgs = append(gitArgs, git.CmdArgCheck(m.GetRemoteName())) cmd.AddDynamicArguments(m.GetRemoteName()) remoteURL, remoteErr := git.GetRemoteURL(ctx, repoPath, m.GetRemoteName()) if remoteErr != nil {
-
@@ -217,7 +217,7 @@ }stdoutBuilder := strings.Builder{} stderrBuilder := strings.Builder{} if err := git.NewCommand(ctx, gitArgs...). if err := cmd. SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). Run(&git.RunOpts{ Timeout: timeout,
-
@@ -243,7 +243,7 @@ if pruneErr == nil {// Successful prune - reattempt mirror stderrBuilder.Reset() stdoutBuilder.Reset() if err = git.NewCommand(ctx, gitArgs...). if err = cmd. SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). Run(&git.RunOpts{ Timeout: timeout,
-
-
-
@@ -37,10 +37,10 @@ }if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } if _, _, err := git.NewCommand(ctx, "config", "--add", git.CmdArg("remote."+m.RemoteName+".push"), "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } if _, _, err := git.NewCommand(ctx, "config", "--add", git.CmdArg("remote."+m.RemoteName+".push"), "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } return nil
-
-
-
@@ -173,7 +173,7 @@ }} if versionCreated { if err := checkCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil { if err := CheckCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil { return nil, false, err }
-
@@ -240,7 +240,7 @@func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) { log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename) if err := checkSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil { if err := CheckSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil { return nil, nil, false, err }
-
@@ -302,7 +302,9 @@return pf, pb, !exists, nil } func checkCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error { // CheckCountQuotaExceeded checks if the owner has more than the allowed packages // The check is skipped if the doer is an admin. func CheckCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error { if doer.IsAdmin { return nil }
-
@@ -324,7 +326,9 @@return nil } func checkSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error { // CheckSizeQuotaExceeded checks if the upload size is bigger than the allowed size // The check is skipped if the doer is an admin. func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error { if doer.IsAdmin { return nil }
-
@@ -335,6 +339,8 @@ case packages_model.TypeComposer:typeSpecificSize = setting.Packages.LimitSizeComposer case packages_model.TypeConan: typeSpecificSize = setting.Packages.LimitSizeConan case packages_model.TypeConda: typeSpecificSize = setting.Packages.LimitSizeConda case packages_model.TypeContainer: typeSpecificSize = setting.Packages.LimitSizeContainer case packages_model.TypeGeneric:
-
-
-
@@ -8,7 +8,6 @@ import ("context" "errors" "fmt" "os" "strconv" "strings"
-
@@ -27,7 +26,6 @@ "code.gitea.io/gitea/modules/notification""code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" )
-
@@ -50,14 +48,14 @@ err := prPatchCheckerQueue.PushFunc(strconv.FormatInt(pr.ID, 10), func() error {pr.Status = issues_model.PullRequestStatusChecking err := pr.UpdateColsIfNotMerged(db.DefaultContext, "status") if err != nil { log.Error("AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err) log.Error("AddToTaskQueue(%-v).UpdateCols.(add to queue): %v", pr, err) } else { log.Trace("Adding PR ID: %d to the test pull requests queue", pr.ID) log.Trace("Adding %-v to the test pull requests queue", pr) } return err }) if err != nil && err != queue.ErrAlreadyInQueue { log.Error("Error adding prID %d to the test pull requests queue: %v", pr.ID, err) log.Error("Error adding %-v to the test pull requests queue: %v", pr, err) } }
-
@@ -69,12 +67,14 @@ return ErrHasMerged} if err := pr.LoadIssue(ctx); err != nil { log.Error("Unable to load issue[%d] for %-v: %v", pr.IssueID, pr, err) return err } else if pr.Issue.IsClosed { return ErrIsClosed } if allowedMerge, err := IsUserAllowedToMerge(ctx, pr, *perm, doer); err != nil { log.Error("Error whilst checking if %-v is allowed to merge %-v: %v", doer, pr, err) return err } else if !allowedMerge { return ErrUserNotAllowedToMerge
-
@@ -98,15 +98,19 @@ return ErrIsChecking} if err := CheckPullBranchProtections(ctx, pr, false); err != nil { if models.IsErrDisallowedToMerge(err) { if force { if isRepoAdmin, err2 := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); err2 != nil { return err2 } else if !isRepoAdmin { return err } } } else { if !models.IsErrDisallowedToMerge(err) { log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err) return err } if !force { return err } if isRepoAdmin, err2 := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); err2 != nil { log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, err2) return err2 } else if !isRepoAdmin { return err } }
-
@@ -144,7 +148,7 @@// checkAndUpdateStatus checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) { // Status is not changed to conflict means mergeable. // If status has not been changed to conflict by testPatch then we are mergeable if pr.Status == issues_model.PullRequestStatusChecking { pr.Status = issues_model.PullRequestStatusMergeable }
-
@@ -152,79 +156,69 @@// Make sure there is no waiting test to process before leaving the checking status. has, err := prPatchCheckerQueue.Has(strconv.FormatInt(pr.ID, 10)) if err != nil { log.Error("Unable to check if the queue is waiting to reprocess pr.ID %d. Error: %v", pr.ID, err) log.Error("Unable to check if the queue is waiting to reprocess %-v. Error: %v", pr, err) } if has { log.Trace("Not updating status for %-v as it is due to be rechecked", pr) return } if !has { if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { log.Error("Update[%d]: %v", pr.ID, err) } if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { log.Error("Update[%-v]: %v", pr, err) } } // getMergeCommit checks if a pull request got merged // getMergeCommit checks if a pull request has been merged // Returns the git.Commit of the pull request if merged func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Commit, error) { if pr.BaseRepo == nil { var err error pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID) if err != nil { return nil, fmt.Errorf("GetRepositoryByID: %w", err) } if err := pr.LoadBaseRepo(ctx); err != nil { return nil, fmt.Errorf("unable to load base repo for %s: %w", pr, err) } indexTmpPath, err := os.MkdirTemp(os.TempDir(), "gitea-"+pr.BaseRepo.Name) if err != nil { return nil, fmt.Errorf("Failed to create temp dir for repository %s: %w", pr.BaseRepo.RepoPath(), err) } defer func() { if err := util.RemoveAll(indexTmpPath); err != nil { log.Warn("Unable to remove temporary index path: %s: Error: %v", indexTmpPath, err) } }() prHeadRef := pr.GetGitRefName() headFile := pr.GetGitRefName() // Check if a pull request is merged into BaseBranch _, _, err = git.NewCommand(ctx, "merge-base", "--is-ancestor").AddDynamicArguments(headFile, pr.BaseBranch). RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath(), Env: []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}}) if err != nil { // Errors are signaled by a non-zero status that is not 1 // Check if the pull request is merged into BaseBranch if _, _, err := git.NewCommand(ctx, "merge-base", "--is-ancestor"). AddDynamicArguments(prHeadRef, pr.BaseBranch). RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}); err != nil { if strings.Contains(err.Error(), "exit status 1") { // prHeadRef is not an ancestor of the base branch return nil, nil } return nil, fmt.Errorf("git merge-base --is-ancestor: %w", err) // Errors are signaled by a non-zero status that is not 1 return nil, fmt.Errorf("%-v git merge-base --is-ancestor: %w", pr, err) } commitIDBytes, err := os.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile) // If merge-base successfully exits then prHeadRef is an ancestor of pr.BaseBranch // Find the head commit id prHeadCommitID, err := git.GetFullCommitID(ctx, pr.BaseRepo.RepoPath(), prHeadRef) if err != nil { return nil, fmt.Errorf("ReadFile(%s): %w", headFile, err) } commitID := string(commitIDBytes) if len(commitID) < git.SHAFullLength { return nil, fmt.Errorf(`ReadFile(%s): invalid commit-ID "%s"`, headFile, commitID) return nil, fmt.Errorf("GetFullCommitID(%s) in %s: %w", prHeadRef, pr.BaseRepo.FullName(), err) } cmd := commitID[:git.SHAFullLength] + ".." + pr.BaseBranch // Get the commit from BaseBranch where the pull request got merged mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse").AddDynamicArguments(cmd). RunStdString(&git.RunOpts{Dir: "", Env: []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}}) mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse"). AddDynamicArguments(prHeadCommitID + ".." + pr.BaseBranch). RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) } else if len(mergeCommit) < git.SHAFullLength { // PR was maybe fast-forwarded, so just use last commit of PR mergeCommit = commitID[:git.SHAFullLength] mergeCommit = prHeadCommitID } mergeCommit = strings.TrimSpace(mergeCommit) gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) if err != nil { return nil, fmt.Errorf("OpenRepository: %w", err) return nil, fmt.Errorf("%-v OpenRepository: %w", pr.BaseRepo, err) } defer gitRepo.Close() commit, err := gitRepo.GetCommit(mergeCommit[:git.SHAFullLength]) commit, err := gitRepo.GetCommit(mergeCommit) if err != nil { return nil, fmt.Errorf("GetMergeCommit[%v]: %w", mergeCommit[:git.SHAFullLength], err) return nil, fmt.Errorf("GetMergeCommit[%s]: %w", mergeCommit, err) } return commit, nil
-
@@ -234,7 +228,7 @@ // manuallyMerged checks if a pull request got manually merged// When a pull request got manually merged mark the pull request as merged func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { if err := pr.LoadBaseRepo(ctx); err != nil { log.Error("PullRequest[%d].LoadBaseRepo: %v", pr.ID, err) log.Error("%-v LoadBaseRepo: %v", pr, err) return false }
-
@@ -244,47 +238,50 @@ if !config.AutodetectManualMerge {return false } } else { log.Error("PullRequest[%d].BaseRepo.GetUnit(unit.TypePullRequests): %v", pr.ID, err) log.Error("%-v BaseRepo.GetUnit(unit.TypePullRequests): %v", pr, err) return false } commit, err := getMergeCommit(ctx, pr) if err != nil { log.Error("PullRequest[%d].getMergeCommit: %v", pr.ID, err) log.Error("%-v getMergeCommit: %v", pr, err) return false } if commit == nil { // no merge commit found return false } if commit != nil { pr.MergedCommitID = commit.ID.String() pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix()) pr.Status = issues_model.PullRequestStatusManuallyMerged merger, _ := user_model.GetUserByEmail(commit.Author.Email) pr.MergedCommitID = commit.ID.String() pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix()) pr.Status = issues_model.PullRequestStatusManuallyMerged merger, _ := user_model.GetUserByEmail(commit.Author.Email) // When the commit author is unknown set the BaseRepo owner as merger if merger == nil { if pr.BaseRepo.Owner == nil { if err = pr.BaseRepo.GetOwner(ctx); err != nil { log.Error("BaseRepo.GetOwner[%d]: %v", pr.ID, err) return false } // When the commit author is unknown set the BaseRepo owner as merger if merger == nil { if pr.BaseRepo.Owner == nil { if err = pr.BaseRepo.GetOwner(ctx); err != nil { log.Error("%-v BaseRepo.GetOwner: %v", pr, err) return false } merger = pr.BaseRepo.Owner } pr.Merger = merger pr.MergerID = merger.ID merger = pr.BaseRepo.Owner } pr.Merger = merger pr.MergerID = merger.ID if merged, err := pr.SetMerged(ctx); err != nil { log.Error("PullRequest[%d].setMerged : %v", pr.ID, err) return false } else if !merged { return false } if merged, err := pr.SetMerged(ctx); err != nil { log.Error("%-v setMerged : %v", pr, err) return false } else if !merged { return false } notification.NotifyMergePullRequest(ctx, merger, pr) notification.NotifyMergePullRequest(ctx, merger, pr) log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String()) return true } return false log.Info("manuallyMerged[%-v]: Marked as manually merged into %s/%s by commit id: %s", pr, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String()) return true } // InitializePullRequests checks and tests untested patches of pull requests.
-
@@ -300,10 +297,10 @@ case <-ctx.Done():return default: if err := prPatchCheckerQueue.PushFunc(strconv.FormatInt(prID, 10), func() error { log.Trace("Adding PR ID: %d to the pull requests patch checking queue", prID) log.Trace("Adding PR[%d] to the pull requests patch checking queue", prID) return nil }); err != nil { log.Error("Error adding prID: %s to the pull requests patch checking queue %v", prID, err) log.Error("Error adding PR[%d] to the pull requests patch checking queue %v", prID, err) } } }
-
@@ -327,23 +324,30 @@ defer finished()pr, err := issues_model.GetPullRequestByID(ctx, id) if err != nil { log.Error("GetPullRequestByID[%d]: %v", id, err) log.Error("Unable to GetPullRequestByID[%d] for testPR: %v", id, err) return } log.Trace("Testing %-v", pr) defer func() { log.Trace("Done testing %-v (status: %s)", pr, pr.Status) }() if pr.HasMerged { log.Trace("%-v is already merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID) return } if manuallyMerged(ctx, pr) { log.Trace("%-v is manually merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID) return } if err := TestPatch(pr); err != nil { log.Error("testPatch[%d]: %v", pr.ID, err) log.Error("testPatch[%-v]: %v", pr, err) pr.Status = issues_model.PullRequestStatusError if err := pr.UpdateCols("status"); err != nil { log.Error("update pr [%d] status to PullRequestStatusError failed: %v", pr.ID, err) log.Error("update pr [%-v] status to PullRequestStatusError failed: %v", pr, err) } return }
-
-
-
@@ -370,16 +370,16 @@sig := doer.NewGitSig() committer := sig // Determine if we should sign var signArg git.CmdArg sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) // Determine if we should sign. If no signKeyID, use --no-gpg-sign to countermand the sign config (from gitconfig) var signArgs git.TrustedCmdArgs sign, signKeyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) if sign { signArg = git.CmdArg("-S" + keyID) if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { committer = signer } signArgs = git.ToTrustedCmdArgs([]string{"-S" + signKeyID}) } else { signArg = git.CmdArg("--no-gpg-sign") signArgs = append(signArgs, "--no-gpg-sign") } commitTimeStr := time.Now().Format(time.RFC3339)
-
@@ -403,7 +403,7 @@ log.Error("Unable to merge tracking into base: %v", err)return "", err } if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil { if err := commitAndSignNoAuthor(ctx, pr, message, signArgs, tmpBasePath, env); err != nil { log.Error("Unable to make final commit: %v", err) return "", err }
-
@@ -505,7 +505,7 @@ log.Error("Unable to merge staging into base: %v", err)return "", err } if mergeStyle == repo_model.MergeStyleRebaseMerge { if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil { if err := commitAndSignNoAuthor(ctx, pr, message, signArgs, tmpBasePath, env); err != nil { log.Error("Unable to make final commit: %v", err) return "", err }
-
@@ -523,35 +523,22 @@ log.Error("LoadPoster: %v", err)return "", fmt.Errorf("LoadPoster: %w", err) } sig := pr.Issue.Poster.NewGitSig() if signArg == "" { if err := git.NewCommand(ctx, "commit", git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email)), "-m").AddDynamicArguments(message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } } else { if setting.Repository.PullRequest.AddCoCommitterTrailers && committer.String() != sig.String() { // add trailer message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) } if err := git.NewCommand(ctx, "commit"). AddArguments(signArg). AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email))). AddArguments("-m").AddDynamicArguments(message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } if setting.Repository.PullRequest.AddCoCommitterTrailers && committer.String() != sig.String() { // add trailer message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) } if err := git.NewCommand(ctx, "commit"). AddArguments(signArgs...). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). AddOptionValues("-m", message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } outbuf.Reset() errbuf.Reset()
-
@@ -649,30 +636,17 @@return mergeCommitID, nil } func commitAndSignNoAuthor(ctx context.Context, pr *issues_model.PullRequest, message string, signArg git.CmdArg, tmpBasePath string, env []string) error { func commitAndSignNoAuthor(ctx context.Context, pr *issues_model.PullRequest, message string, signArgs git.TrustedCmdArgs, tmpBasePath string, env []string) error { var outbuf, errbuf strings.Builder if signArg == "" { if err := git.NewCommand(ctx, "commit", "-m").AddDynamicArguments(message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } } else { if err := git.NewCommand(ctx, "commit").AddArguments(signArg).AddArguments("-m").AddDynamicArguments(message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } if err := git.NewCommand(ctx, "commit").AddArguments(signArgs...).AddOptionValues("-m", message). Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, Stdout: &outbuf, Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } return nil }
-
-
-
@@ -60,7 +60,7 @@ }// TestPatch will test whether a simple patch will apply func TestPatch(pr *issues_model.PullRequest) error { ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: Repo[%d]#%d", pr.BaseRepoID, pr.Index)) ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr)) defer finished() // Clone base repo.
-
@@ -376,16 +376,16 @@ }prConfig := prUnit.PullRequestsConfig() // 6. Prepare the arguments to apply the patch against the index args := []git.CmdArg{"apply", "--check", "--cached"} cmdApply := git.NewCommand(gitRepo.Ctx, "apply", "--check", "--cached") if prConfig.IgnoreWhitespaceConflicts { args = append(args, "--ignore-whitespace") cmdApply.AddArguments("--ignore-whitespace") } is3way := false if git.CheckGitVersionAtLeast("2.32.0") == nil { args = append(args, "--3way") cmdApply.AddArguments("--3way") is3way = true } args = append(args, git.CmdArgCheck(patchPath)) cmdApply.AddDynamicArguments(patchPath) // 7. Prep the pipe: // - Here we could do the equivalent of:
-
@@ -407,71 +407,70 @@ }()// 8. Run the check command conflict = false err = git.NewCommand(gitRepo.Ctx, args...). Run(&git.RunOpts{ Dir: tmpBasePath, Stderr: stderrWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { // Close the writer end of the pipe to begin processing _ = stderrWriter.Close() defer func() { // Close the reader on return to terminate the git command if necessary _ = stderrReader.Close() }() err = cmdApply.Run(&git.RunOpts{ Dir: tmpBasePath, Stderr: stderrWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { // Close the writer end of the pipe to begin processing _ = stderrWriter.Close() defer func() { // Close the reader on return to terminate the git command if necessary _ = stderrReader.Close() }() const prefix = "error: patch failed:" const errorPrefix = "error: " const threewayFailed = "Failed to perform three-way merge..." const appliedPatchPrefix = "Applied patch to '" const withConflicts = "' with conflicts." const prefix = "error: patch failed:" const errorPrefix = "error: " const threewayFailed = "Failed to perform three-way merge..." const appliedPatchPrefix = "Applied patch to '" const withConflicts = "' with conflicts." conflicts := make(container.Set[string]) conflicts := make(container.Set[string]) // Now scan the output from the command scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { line := scanner.Text() log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line) if strings.HasPrefix(line, prefix) { conflict = true filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) conflicts.Add(filepath) } else if is3way && line == threewayFailed { conflict = true } else if strings.HasPrefix(line, errorPrefix) { conflict = true for _, suffix := range patchErrorSuffices { if strings.HasSuffix(line, suffix) { filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) if filepath != "" { conflicts.Add(filepath) } break // Now scan the output from the command scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { line := scanner.Text() log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line) if strings.HasPrefix(line, prefix) { conflict = true filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) conflicts.Add(filepath) } else if is3way && line == threewayFailed { conflict = true } else if strings.HasPrefix(line, errorPrefix) { conflict = true for _, suffix := range patchErrorSuffices { if strings.HasSuffix(line, suffix) { filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) if filepath != "" { conflicts.Add(filepath) } } } else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) { conflict = true filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) if filepath != "" { conflicts.Add(filepath) break } } // only list 10 conflicted files if len(conflicts) >= 10 { break } else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) { conflict = true filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) if filepath != "" { conflicts.Add(filepath) } } // only list 10 conflicted files if len(conflicts) >= 10 { break } } if len(conflicts) > 0 { pr.ConflictedFiles = make([]string, 0, len(conflicts)) for key := range conflicts { pr.ConflictedFiles = append(pr.ConflictedFiles, key) } if len(conflicts) > 0 { pr.ConflictedFiles = make([]string, 0, len(conflicts)) for key := range conflicts { pr.ConflictedFiles = append(pr.ConflictedFiles, key) } } return nil }, }) return nil }, }) // 9. Check if the found conflictedfiles is non-zero, "err" could be non-nil, so we should ignore it if we found conflicts. // Note: `"err" could be non-nil` is due that if enable 3-way merge, it doesn't return any error on found conflicts.
-
-
-
@@ -4,14 +4,12 @@package pull import ( "bufio" "bytes" "context" "fmt" "io" "os" "regexp" "strings" "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db"
-
@@ -29,6 +27,7 @@ "code.gitea.io/gitea/modules/process"repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" )
-
@@ -299,7 +298,6 @@ }} } pr.Issue.PullRequest = pr notification.NotifyPullRequestSynchronized(ctx, doer, pr) } }
-
@@ -351,69 +349,56 @@// checkIfPRContentChanged checks if diff to target branch has changed by push // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) { if err = pr.LoadHeadRepo(ctx); err != nil { return false, fmt.Errorf("LoadHeadRepo: %w", err) } else if pr.HeadRepo == nil { // corrupt data assumed changed return true, nil tmpBasePath, err := createTemporaryRepo(ctx, pr) if err != nil { log.Error("CreateTemporaryRepo: %v", err) return false, err } if err = pr.LoadBaseRepo(ctx); err != nil { return false, fmt.Errorf("LoadBaseRepo: %w", err) } defer func() { if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { log.Error("checkIfPRContentChanged: RemoveTemporaryPath: %s", err) } }() headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath()) tmpRepo, err := git.OpenRepository(ctx, tmpBasePath) if err != nil { return false, fmt.Errorf("OpenRepository: %w", err) } defer headGitRepo.Close() defer tmpRepo.Close() // Add a temporary remote. tmpRemote := "checkIfPRContentChanged-" + fmt.Sprint(time.Now().UnixNano()) if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil { return false, fmt.Errorf("AddRemote: %s/%s-%s: %w", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) } defer func() { if err := headGitRepo.RemoveRemote(tmpRemote); err != nil { log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) } }() // To synchronize repo and get a base ref _, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch) // Find the merge-base _, base, err := tmpRepo.GetMergeBase("", "base", "tracking") if err != nil { return false, fmt.Errorf("GetMergeBase: %w", err) } diffBefore := &bytes.Buffer{} diffAfter := &bytes.Buffer{} if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil { // If old commit not found, assume changed. log.Debug("GetDiffFromMergeBase: %v", err) return true, nil } if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil { // New commit should be found return false, fmt.Errorf("GetDiffFromMergeBase: %w", err) cmd := git.NewCommand(ctx, "diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return false, fmt.Errorf("unable to open pipe for to run diff: %w", err) } diffBeforeLines := bufio.NewScanner(diffBefore) diffAfterLines := bufio.NewScanner(diffAfter) for diffBeforeLines.Scan() && diffAfterLines.Scan() { if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") { // file hashes can change without the diff changing continue } else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") { // the location of the difference may change continue } else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) { if err := cmd.Run(&git.RunOpts{ Dir: tmpBasePath, Stdout: stdoutWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer func() { _ = stdoutReader.Close() }() return util.IsEmptyReader(stdoutReader) }, }); err != nil { if err == util.ErrNotEmpty { return true, nil } } if diffBeforeLines.Scan() || diffAfterLines.Scan() { // Diffs not of equal length return true, nil log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: Error: %v", newCommitID, oldCommitID, base, pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch, err) return false, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, base, err) } return false, nil
-
-
-
@@ -67,7 +67,7 @@ Name: repo.Name,} } if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, true); err != nil { if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil { return err } if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil {
-
-
-
@@ -23,7 +23,7 @@ "xorm.io/builder") // GitFsckRepos calls 'git fsck' to check repository health. func GitFsckRepos(ctx context.Context, timeout time.Duration, args []git.CmdArg) error { func GitFsckRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Doing: GitFsck") if err := db.Iterate(
-
@@ -47,10 +47,10 @@ return nil} // GitFsckRepo calls 'git fsck' to check an individual repository's health. func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args []git.CmdArg) error { func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Running health check on repository %-v", repo) repoPath := repo.RepoPath() if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil { if err := git.Fsck(ctx, repoPath, timeout, args); err != nil { log.Warn("Failed to health check repository (%-v): %v", repo, err) if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil { log.Error("CreateRepositoryNotice: %v", err)
-
@@ -60,9 +60,8 @@ return nil} // GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository func GitGcRepos(ctx context.Context, timeout time.Duration, args ...git.CmdArg) error { func GitGcRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Doing: GitGcRepos") args = append([]git.CmdArg{"gc"}, args...) if err := db.Iterate( ctx,
-
@@ -86,9 +85,9 @@ return nil} // GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args []git.CmdArg) error { func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Running git gc on %-v", repo) command := git.NewCommand(ctx, args...). command := git.NewCommand(ctx, "gc").AddArguments(args...). SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())) var stdout string var err error
-
-
-
@@ -141,14 +141,12 @@stdout := &strings.Builder{} stderr := &strings.Builder{} args := []git.CmdArg{"apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary"} cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") if git.CheckGitVersionAtLeast("2.32") == nil { args = append(args, "-3") cmdApply.AddArguments("-3") } cmd := git.NewCommand(ctx, args...) if err := cmd.Run(&git.RunOpts{ if err := cmdApply.Run(&git.RunOpts{ Dir: t.basePath, Stdout: stdout, Stderr: stderr,
-
-
-
@@ -233,11 +233,9 @@ messageBytes := new(bytes.Buffer)_, _ = messageBytes.WriteString(message) _, _ = messageBytes.WriteString("\n") var args []git.CmdArg cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(treeHash) if parent != "" { args = []git.CmdArg{"commit-tree", git.CmdArgCheck(treeHash), "-p", git.CmdArgCheck(parent)} } else { args = []git.CmdArg{"commit-tree", git.CmdArgCheck(treeHash)} cmdCommitTree.AddOptionValues("-p", parent) } var sign bool
-
@@ -249,7 +247,7 @@ } else {sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author) } if sign { args = append(args, git.CmdArg("-S"+keyID)) cmdCommitTree.AddOptionFormat("-S%s", keyID) if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { // Add trailers
-
@@ -264,7 +262,7 @@ }committerSig = signer } } else { args = append(args, "--no-gpg-sign") cmdCommitTree.AddArguments("--no-gpg-sign") } if signoff {
-
@@ -281,7 +279,7 @@ )stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) if err := git.NewCommand(t.ctx, args...). if err := cmdCommitTree. Run(&git.RunOpts{ Env: env, Dir: t.basePath,
-
@@ -364,7 +362,7 @@ return nil, fmt.Errorf("Unable to run diff-index pipeline in temporary repo %s. Error: %w\nStderr: %s",t.repo.FullName(), err, stderr) } diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, "--cached", "HEAD") diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, git.TrustedCmdArgs{"--cached"}, "HEAD") if err != nil { return nil, err }
-
-
-
@@ -370,7 +370,7 @@if setting.LFS.StartServer && hasOldBranch { // Check there is no way this can return multiple infos filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ Attributes: []git.CmdArg{"filter"}, Attributes: []string{"filter"}, Filenames: []string{treePath}, CachedOnly: true, })
-
-
-
@@ -96,7 +96,7 @@var filename2attribute2info map[string]map[string]string if setting.LFS.StartServer { filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ Attributes: []git.CmdArg{"filter"}, Attributes: []string{"filter"}, Filenames: names, CachedOnly: true, })
-
-
-
@@ -119,7 +119,7 @@ panic(panicErr)}() err = db.WithTx(ctx, func(txCtx context.Context) error { if err = repo_module.CreateRepositoryByExample(txCtx, doer, owner, repo, false); err != nil { if err = repo_module.CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil { return err }
-
-
-
@@ -110,9 +110,13 @@ return fmt.Errorf("old and new revisions are both %s", git.EmptySHA)} if opts.IsTag() { // If is tag reference if pusher == nil || pusher.ID != opts.PusherID { var err error if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil { return err if opts.PusherID == user_model.ActionsUserID { pusher = user_model.NewActionsUser() } else { var err error if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil { return err } } } tagName := opts.TagName()
-
@@ -150,9 +154,13 @@ notification.NotifyCreateRef(db.DefaultContext, pusher, repo, "tag", opts.RefFullName, opts.NewCommitID)} } else if opts.IsBranch() { // If is branch reference if pusher == nil || pusher.ID != opts.PusherID { var err error if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil { return err if opts.PusherID == user_model.ActionsUserID { pusher = user_model.NewActionsUser() } else { var err error if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil { return err } } }
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin config"> <div role="main" aria-label="{{.Title}}" class="page-content admin config"> {{template "admin/navbar" .}} <div class="ui container"> <div class="twelve wide column content">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin config"> <div role="main" aria-label="{{.Title}}" class="page-content admin config"> {{template "admin/navbar" .}} {{template "user/settings/applications_oauth2_edit_form" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin edit authentication"> <div role="main" aria-label="{{.Title}}" class="page-content admin edit authentication"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin authentication"> <div role="main" aria-label="{{.Title}}" class="page-content admin authentication"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin new authentication"> <div role="main" aria-label="{{.Title}}" class="page-content admin new authentication"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin config"> <div role="main" aria-label="{{.Title}}" class="page-content admin config"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
@@ -303,14 +303,14 @@ <dl class="dl-horizontal admin-dl-horizontal"><dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt> <dd> <div class="ui toggle checkbox"> <input type="checkbox" name="picture.disable_gravatar" version="{{.SystemSettings.GetVersion "picture.disable_gravatar"}}"{{if .SystemSettings.GetBool "picture.disable_gravatar"}} checked{{end}}> <input type="checkbox" name="picture.disable_gravatar" version="{{.SystemSettings.GetVersion "picture.disable_gravatar"}}"{{if .SystemSettings.GetBool "picture.disable_gravatar"}} checked{{end}} title="{{.locale.Tr "admin.config.disable_gravatar"}}"> </div> </dd> <div class="ui divider"></div> <dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt> <dd> <div class="ui toggle checkbox"> <input type="checkbox" name="picture.enable_federated_avatar" version="{{.SystemSettings.GetVersion "picture.enable_federated_avatar"}}"{{if .SystemSettings.GetBool "picture.enable_federated_avatar"}} checked{{end}}> <input type="checkbox" name="picture.enable_federated_avatar" version="{{.SystemSettings.GetVersion "picture.enable_federated_avatar"}}"{{if .SystemSettings.GetBool "picture.enable_federated_avatar"}} checked{{end}} title="{{.locale.Tr "admin.config.enable_federated_avatar"}}"> </div> </dd> </dl>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin dashboard"> <div role="main" aria-label="{{.Title}}" class="page-content admin dashboard"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin settings new webhook"> <div role="main" aria-label="{{.Title}}" class="page-content admin settings new webhook"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin hooks"> <div role="main" aria-label="{{.Title}}" class="page-content admin hooks"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin monitor"> <div role="main" aria-label="{{.Title}}" class="page-content admin monitor"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -33,6 +33,11 @@ <a class="{{if .PageIsAdminApplications}}active {{end}}item" href="{{AppSubUrl}}/admin/applications">{{.locale.Tr "settings.applications"}} </a> {{end}} {{if .EnableActions}} <a class="{{if .PageIsAdminRunners}}active {{end}}item" href="{{AppSubUrl}}/admin/runners"> {{.locale.Tr "actions.runners"}} </a> {{end}} <a class="{{if .PageIsAdminConfig}}active {{end}}item" href="{{AppSubUrl}}/admin/config"> {{.locale.Tr "admin.config"}} </a>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin notice"> <div role="main" aria-label="{{.Title}}" class="page-content admin notice"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin monitor"> <div role="main" aria-label="{{.Title}}" class="page-content admin monitor"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -0,0 +1,8 @@{{template "base/head" .}} <div class="page-content admin runners"> {{template "admin/navbar" .}} <div class="ui container"> {{template "shared/actions/runner_list" .}} </div> </div> {{template "base/footer" .}}
-
-
-
@@ -0,0 +1,8 @@{{template "base/head" .}} <div class="page-content admin runners"> {{template "admin/navbar" .}} <div class="ui container"> {{template "shared/actions/runner_edit" .}} </div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin monitor"> <div role="main" aria-label="{{.Title}}" class="page-content admin monitor"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin edit user"> <div role="main" aria-label="{{.Title}}" class="page-content admin edit user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin user"> <div role="main" aria-label="{{.Title}}" class="page-content admin user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content admin new user"> <div role="main" aria-label="{{.Title}}" class="page-content admin new user"> {{template "admin/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,6 +1,6 @@<footer> <footer role="group" aria-label="{{.locale.Tr "aria.footer"}}"> <div class="ui container"> <div class="ui left"> <div class="ui left" role="contentinfo" aria-label="{{.locale.Tr "aria.footer.software"}}"> <a target="_blank" rel="noopener noreferrer" href="https://gitea.io">{{.locale.Tr "powered_by" "Gitea"}}</a> {{if (or .ShowFooterVersion .PageIsAdmin)}} {{.locale.Tr "version"}}:
-
@@ -15,7 +15,7 @@ {{.locale.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong>{{.locale.Tr "template"}}{{if .TemplateName}} {{.TemplateName}}{{end}}: <strong>{{call .TemplateLoadTimes}}</strong> {{end}} </div> <div class="ui right links"> <div class="ui right links" role="group" aria-label="{{.locale.Tr "aria.footer.links"}}"> {{if .ShowFooterBranding}} <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea">{{svg "octicon-mark-github"}}<span class="sr-only">GitHub</span></a> {{end}}
-
-
-
@@ -1,4 +1,4 @@<div class="ui container" id="navbar"> <div class="ui container" id="navbar" role="navigation" aria-label="{{.locale.Tr "aria.navbar"}}"> {{$notificationUnreadCount := 0}} {{if .IsSigned}} {{if .NotificationUnreadCount}}{{$notificationUnreadCount = call .NotificationUnreadCount}}{{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content explore users"> <div role="main" aria-label="{{.Title}}" class="page-content explore users"> {{template "explore/navbar" .}} <div class="ui container"> {{template "code/searchform" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content explore users"> <div role="main" aria-label="{{.Title}}" class="page-content explore users"> {{template "explore/navbar" .}} <div class="ui container"> {{template "explore/search" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content explore repositories"> <div role="main" aria-label="{{.Title}}" class="page-content explore repositories"> {{template "explore/navbar" .}} <div class="ui container"> {{template "explore/repo_search" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content explore users"> <div role="main" aria-label="{{.Title}}" class="page-content explore users"> {{template "explore/navbar" .}} <div class="ui container"> {{template "explore/search" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content home"> <div role="main" aria-label="{{if .IsSigned}}{{.locale.Tr "dashboard"}}{{else}}{{.locale.Tr "home"}}{{end}}" class="page-content home"> <div class="ui stackable middle very relaxed page grid"> <div class="sixteen wide center aligned centered column"> <div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content install"> <div role="main" aria-label="{{.Title}}" class="page-content install"> <div class="ui middle very relaxed page grid"> <div class="sixteen wide center aligned centered column"> <h3 class="ui top attached header">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization new org"> <div role="main" aria-label="{{.Title}}" class="page-content organization new org"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization profile"> <div role="main" aria-label="{{.Title}}" class="page-content organization profile"> <div class="ui container df"> {{avatar .Org 140 "org-avatar"}} <div id="org-info">
-
@@ -13,7 +13,7 @@ {{if .Org.Visibility.IsLimited}}<div class="ui large basic horizontal label">{{.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}{{if .Org.Visibility.IsPrivate}}<div class="ui large basic horizontal label">{{.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} </span> </div> {{if $.RenderedDescription}}<p class="render-content markup">{{$.RenderedDescription|Str2html}}</p>{{end}} {{if $.RenderedDescription}}<div class="render-content markup">{{$.RenderedDescription|Str2html}}</div>{{end}} <div class="text grey meta"> {{if .Org.Location}}<div class="item">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}} {{if .Org.Website}}<div class="item">{{svg "octicon-link"}} <a target="_blank" rel="noopener noreferrer me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization members"> <div role="main" aria-label="{{.Title}}" class="page-content organization members"> {{template "org/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "user/overview/header" .}} {{template "projects/list" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "user/overview/header" .}} {{template "projects/new" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "user/overview/header" .}} {{template "projects/view" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings options"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings options"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings options"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings options"> {{template "org/header" .}} {{template "user/settings/applications_oauth2_edit_form" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings delete"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings delete"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings new webhook"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings new webhook"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings webhooks"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings webhooks"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings labels"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings labels"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -25,6 +25,11 @@ <a class="{{if .PageIsSettingsPackages}}active {{end}}item" href="{{.OrgLink}}/settings/packages">{{.locale.Tr "packages.title"}} </a> {{end}} {{if .EnableActions}} <a class="{{if .PageIsOrgSettingsRunners}}active {{end}}item" href="{{.OrgLink}}/settings/runners"> {{.locale.Tr "actions.runners"}} </a> {{end}} <a class="{{if .PageIsSettingsDelete}}active {{end}}item" href="{{.OrgLink}}/settings/delete"> {{.locale.Tr "org.settings.delete"}} </a>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings options"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings options"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings packages"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings packages"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings packages"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings packages"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization settings packages admin"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings packages admin"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -0,0 +1,13 @@{{template "base/head" .}} <div class="page-content organization settings runners"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid"> {{template "org/settings/navbar" .}} <div class="twelve wide column content"> {{template "shared/actions/runner_list" .}} </div> </div> </div> </div> {{template "base/footer" .}}
-
-
-
@@ -0,0 +1,13 @@{{template "base/head" .}} <div class="page-content organization settings runners"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid"> {{template "org/settings/navbar" .}} <div class="twelve wide column content"> {{template "shared/actions/runner_edit" .}} </div> </div> </div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,83 +1,15 @@{{template "base/head" .}} <div class="page-content organization settings webhooks"> <div role="main" aria-label="{{.Title}}" class="page-content organization settings webhooks"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid"> {{template "org/settings/navbar" .}} <div class="ui twelve wide column content"> {{template "base/alert" .}} <h4 class="ui top attached header"> {{.locale.Tr "secrets.secrets"}} <div class="ui right"> <div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div> </div> </h4> <div class="ui attached segment"> <div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel"> <form class="ui form" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} <div class="field"> {{.locale.Tr "secrets.description"}} </div> <div class="field{{if .Err_Title}} error{{end}}"> <label for="secret-title">{{.locale.Tr "secrets.name"}}</label> <input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}"> </div> <div class="field{{if .Err_Content}} error{{end}}"> <label for="secret-content">{{.locale.Tr "secrets.value"}}</label> <textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea> </div> <button class="ui green button"> {{.locale.Tr "secrets.creation"}} </button> <button class="ui hide-panel button" data-panel="#add-secret-panel"> {{.locale.Tr "cancel"}} </button> </form> </div> {{if .Secrets}} <div class="ui key list"> {{range .Secrets}} <div class="item"> <div class="right floated content"> <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> {{$.locale.Tr "settings.delete_key"}} </button> </div> <div class="left floated content"> <i>{{svg "octicon-key" 32}}</i> </div> <div class="content"> <strong>{{.Name}}</strong> <div class="print meta">******</div> <div class="activity meta"> <i> {{$.locale.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> </i> </div> </div> </div> {{end}} </div> {{else}} {{.locale.Tr "secrets.none"}} {{end}} </div> {{template "shared/secrets/add_list" .}} </div> </div> </div> </div> <div class="ui small basic delete modal"> <div class="ui header"> {{svg "octicon-trash" 16 "mr-2"}} {{.locale.Tr "secrets.deletion"}} </div> <div class="content"> <p>{{.locale.Tr "secrets.deletion.description"}}</p> </div> {{template "base/delete_modal_actions" .}} </div> {{template "base/footer" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization invite"> <div role="main" aria-label="{{.Title}}" class="page-content organization invite"> <div class="ui container"> {{template "base/alert" .}} <div class="ui centered card">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization teams"> <div role="main" aria-label="{{.Title}}" class="page-content organization teams"> {{template "org/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization new team"> <div role="main" aria-label="{{.Title}}" class="page-content organization new team"> {{template "org/header" .}} <div class="ui container"> <div class="ui grid">
-
@@ -78,7 +78,7 @@ <thead><tr> <th>{{.locale.Tr "units.unit"}}</th> <th class="center aligned">{{.locale.Tr "org.teams.none_access"}} <span class="tooltip vm" data-content="{{.locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "ml-2"}}</th> <span class="tooltip vm" data-content="{{.locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "ml-2"}}</span></th> <th class="center aligned">{{.locale.Tr "org.teams.read_access"}} <span class="tooltip vm" data-content="{{.locale.Tr "org.teams.read_access_helper"}}">{{svg "octicon-question" 16 "ml-2"}}</span></th> <th class="center aligned">{{.locale.Tr "org.teams.write_access"}}
-
@@ -99,17 +99,17 @@ </div></td> <td class="center aligned"> <div class="ui radio checkbox"> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}}> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}} title="{{$.locale.Tr "org.teams.none_access"}}"> </div> </td> <td class="center aligned"> <div class="ui radio checkbox"> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{$.locale.Tr "org.teams.read_access"}}"> </div> </td> <td class="center aligned"> <div class="ui radio checkbox"> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="2"{{if (eq ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}> <input type="radio" class="hidden" name="unit_{{$unit.Type.Value}}" value="2"{{if (eq ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{$.locale.Tr "org.teams.write_access"}}"> </div> </td> </tr>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization teams"> <div role="main" aria-label="{{.Title}}" class="page-content organization teams"> {{template "org/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content organization teams"> <div role="main" aria-label="{{.Title}}" class="page-content organization teams"> {{template "org/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -0,0 +1,30 @@{{if eq .PackageDescriptor.Package.Type "conda"}} <h4 class="ui top attached header">{{.locale.Tr "packages.installation"}}</h4> <div class="ui attached segment"> <div class="ui form"> <div class="field"> <label>{{svg "octicon-code"}} {{.locale.Tr "packages.conda.registry" | Safe}}</label> <div class="markup"><pre class="code-block"><code>channel_alias: {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda channels:   - {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda default_channels:   - {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda</code></pre></div> </div> <div class="field"> <label>{{svg "octicon-terminal"}} {{.locale.Tr "packages.conda.install"}}</label> {{$channel := .PackageDescriptor.PackageProperties.GetByName "conda.channel"}} <div class="markup"><pre class="code-block"><code>conda install{{if $channel}} -c {{$channel}}{{end}} {{.PackageDescriptor.PackageProperties.GetByName "conda.name"}}={{.PackageDescriptor.Version.Version}}</code></pre></div> </div> <div class="field"> <label>{{.locale.Tr "packages.conda.documentation" | Safe}}</label> </div> </div> </div> {{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Summary}} <h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4> <div class="ui attached segment"> {{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{else}}{{.PackageDescriptor.Metadata.Summary}}{{end}} </div> {{end}} {{end}}
-
-
-
@@ -0,0 +1,6 @@{{if eq .PackageDescriptor.Package.Type "conda"}} {{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law" 16 "mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}} {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.details.project_site"}}</a></div>{{end}} {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.conda.details.repository_site"}}</a></div>{{end}} {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.conda.details.documentation_site"}}</a></div>{{end}} {{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings options"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings options"> {{template "user/overview/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository view issue packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository view issue packages"> {{template "user/overview/header" .}} <div class="ui container"> <div>
-
@@ -21,6 +21,7 @@ </div><div class="twelve wide column"> {{template "package/content/composer" .}} {{template "package/content/conan" .}} {{template "package/content/conda" .}} {{template "package/content/container" .}} {{template "package/content/generic" .}} {{template "package/content/helm" .}}
-
@@ -44,6 +45,7 @@ <div class="item">{{svg "octicon-calendar" 16 "mr-3"}} {{TimeSinceUnix .PackageDescriptor.Version.CreatedUnix $.locale}}</div><div class="item">{{svg "octicon-download" 16 "mr-3"}} {{.PackageDescriptor.Version.DownloadCount}}</div> {{template "package/metadata/composer" .}} {{template "package/metadata/conan" .}} {{template "package/metadata/conda" .}} {{template "package/metadata/container" .}} {{template "package/metadata/generic" .}} {{template "package/metadata/helm" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content install"> <div role="main" aria-label="{{.Title}}" class="page-content install"> <div class="ui container"> <div class="ui grid"> <div class="sixteen wide column content">
-
-
-
@@ -1,4 +1,4 @@<div class="page-content repository projects"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects"> <div class="ui container"> {{if .CanWriteProjects}} <div class="navbar">
-
-
-
@@ -1,4 +1,4 @@<div class="page-content repository projects edit-project new milestone"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects edit-project new milestone"> <div class="ui container"> <div class="navbar"> {{if and .CanWriteProjects .PageIsEditProject}}
-
-
-
@@ -1,4 +1,4 @@<div class="page-content repository projects view-project"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project"> <div class="ui container"> <div class="ui two column stackable grid"> <div class="column">
-
-
-
@@ -0,0 +1,27 @@{{template "base/head" .}} <div class="page-content repository"> {{template "repo/header" .}} <div class="ui container"> <div class="ui grid"> <div class="four wide column"> <div class="ui fluid vertical menu"> <a class="item{{if not $.CurWorkflow}} active{{end}}" href="{{$.Link}}">{{.locale.Tr "actions.runs.all_workflows"}}</a> <div class="divider"></div> {{range .workflows}} <a class="item{{if eq .Name $.CurWorkflow}} active{{end}}" href="{{$.Link}}?workflow={{.Name}}">{{.Name}}</a> {{end}} </div> </div> <div class="twelve wide column content"> <div id="issue-filters" class="ui stackable grid"> <div class="six wide column"> {{template "repo/actions/openclose" .}} </div> </div> {{template "repo/actions/runs_list" .}} </div> </div> </div> </div> {{template "base/footer" .}}
-
-
-
@@ -0,0 +1,10 @@<div class="ui compact tiny menu"> <a class="{{if not .IsShowClosed}}active {{end}}item" href="{{$.Link}}?workflow={{.CurWorkflow}}&state=open"> {{svg "octicon-issue-opened" 16 "mr-3"}} {{.locale.Tr "actions.runs.open_tab" $.NumOpenActionRuns}} </a> <a class="{{if .IsShowClosed}}active {{end}}item" href="{{$.Link}}?workflow={{.CurWorkflow}}&state=closed"> {{svg "octicon-issue-closed" 16 "mr-3"}} {{.locale.Tr "actions.runs.closed_tab" $.NumClosedActionRuns}} </a> </div>
-
-
-
@@ -0,0 +1,32 @@<div class="issue list"> {{range .Runs}} <li class="item df py-3"> <div class="issue-item-left df"> {{template "repo/actions/status" .Status}} </div> <div class="issue-item-main f1 fc df"> <div class="issue-item-top-row"> <a class="index ml-0 mr-2" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}"> {{.Title}} </a> <span class="ui label"> {{if .RefLink}} <a href="{{.RefLink}}">{{.PrettyRef}}</a> {{else}} {{.PrettyRef}} {{end}} </span> </div> <div class="desc issue-item-bottom-row df ac fw my-1"> <b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>: {{$.locale.Tr "actions.runs.commit"}} <a href="{{$.RepoLink}}/commit/{{.CommitSHA}}">{{ShortSha .CommitSHA}}</a> {{$.locale.Tr "actions.runs.pushed_by"}} {{.TriggerUser.GetDisplayName | Escape}} </div> </div> <div class="issue-item-right"> <div>{{TimeSinceUnix .Updated $.locale}}</div> <div>{{.Duration}}</div> </div> </li> {{end}} </div> {{template "base/paginate" .}}
-
-
-
@@ -0,0 +1,12 @@{{if .IsWaiting}} <i class="commit-status circle icon gray"></i> {{end}} {{if .IsRunning}} <i class="commit-status circle icon yellow"></i> {{end}} {{if .IsSuccess}} <i class="commit-status check icon green"></i> {{end}} {{if .IsFailure}} <i class="commit-status warning icon red"></i> {{end}}
-
-
-
@@ -0,0 +1,8 @@{{template "base/head" .}} <div class="page-content repository"> {{template "repo/header" .}} <div id="repo-action-view" data-run-index="{{.RunIndex}}" data-job-index="{{.JobIndex}}" data-actions-url="{{.ActionsURL}}"></div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository commits"> {{template "repo/header" .}} <div class="ui container"> <h2 class="ui header"><time data-format="date" datetime="{{.DateFrom}}">{{.DateFrom}}</time> - <time data-format="date" datetime="{{.DateUntil}}">{{.DateUntil}}</time>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content ui repository branches"> <div role="main" aria-label="{{.Title}}" class="page-content ui repository branches"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository diff"> <div role="main" aria-label="{{.Title}}" class="page-content repository diff"> {{template "repo/header" .}} <div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}"> {{$class := ""}}
-
-
-
@@ -1,4 +1,4 @@<a class="ui link commit-statuses-trigger vm"{{if eq (len .Statuses) 1}}{{$status := index .Statuses 0}}{{if $status.TargetURL}} href="{{$status.TargetURL}}"{{end}}{{end}}>{{template "repo/commit_status" .Status}}</a> {{if eq (len .Statuses) 1}}{{$status := index .Statuses 0}}{{if $status.TargetURL}}<a class="ui link commit-statuses-trigger vm" href="{{$status.TargetURL}}">{{template "repo/commit_status" .Status}}</a>{{end}}{{end}} <div class="ui commit-statuses-popup commit-statuses tippy-target"> <div class="ui relaxed list divided"> {{range .Statuses}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository commits"> {{template "repo/header" .}} <div class="ui container"> {{template "repo/sub_menu" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new repo"> <div role="main" aria-label="{{.Title}}" class="page-content repository new repo"> <div class="ui middle very relaxed page one column grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -76,17 +76,18 @@ {{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}}{{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}} {{$isCsv := (call $.IsCsvFile $file)}} {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} mt-3" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if $file.ShouldBeHidden}}data-folded="true"{{end}}> {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} mt-3" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}> <h4 class="diff-file-header sticky-2nd-row ui top attached normal header df ac sb"> <div class="df ac"> <a role="button" class="fold-file muted mr-2"> <a role="button" class="fold-file muted mr-2" {{if not $isExpandable}}style="visibility: hidden"{{end}}> {{if $file.ShouldBeHidden}} {{svg "octicon-chevron-right" 18}} {{else}} {{svg "octicon-chevron-down" 18}} {{end}} </a> <div class="bold df ac"> <div class="bold df ac mono"> {{if $file.IsBin}} <span class="ml-1 mr-3"> {{$.locale.Tr "repo.diff.bin"}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository diff {{if .PageIsComparePull}}compare pull{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content repository diff {{if .PageIsComparePull}}compare pull{{end}}"> {{template "repo/header" .}} <div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file editor edit"> <div role="main" aria-label="{{.Title}}" class="page-content repository file editor edit"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -61,7 +61,7 @@ </div><div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}hide{{end}}"> <div class="new-branch-name-input field {{if .Err_NewBranchName}}error{{end}}"> {{svg "octicon-git-branch"}} <input type="text" name="new_branch_name" value="{{.new_branch_name}}" class="input-contrast mr-2 js-quick-pull-new-branch-name" placeholder="{{.locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}}> <input type="text" name="new_branch_name" value="{{.new_branch_name}}" class="input-contrast mr-2 js-quick-pull-new-branch-name" placeholder="{{.locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{.locale.Tr "repo.editor.new_branch_name"}}"> <span class="text-muted js-quick-pull-normalization-info"></span> </div> </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file editor delete"> <div role="main" aria-label="{{.Title}}" class="page-content repository file editor delete"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file editor edit"> <div role="main" aria-label="{{.Title}}" class="page-content repository file editor edit"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file editor edit"> <div role="main" aria-label="{{.Title}}" class="page-content repository file editor edit"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file editor upload"> <div role="main" aria-label="{{.Title}}" class="page-content repository file editor upload"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository quickstart"> <div role="main" aria-label="{{.Title}}" class="page-content repository quickstart"> {{template "repo/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository"> <div role="main" aria-label="{{.Title}}" class="page-content repository"> {{template "repo/header" .}} <div class="ui container"> <div class="df ac">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository forks"> <div role="main" aria-label="{{.Title}}" class="page-content repository forks"> {{template "repo/header" .}} <div class="ui container"> <h2 class="ui dividing header">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository commits"> {{template "repo/header" .}} <div class="ui container"> <div id="git-graph-container" class="ui segment{{if eq .Mode "monochrome"}} monochrome{{end}}">
-
-
-
@@ -68,7 +68,7 @@ </form>{{end}} <form method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch?redirect_to={{$.Link}}"> {{$.CsrfTokenHtml}} <div class="ui labeled button{{if not $.IsSigned}} tooltip{{end}}" tabindex="0"{{if not $.IsSigned}} data-content="{{$.locale.Tr "repo.watch_guest_user"}}" data-position="top center"{{end}}> <div class="ui labeled button{{if not $.IsSigned}} tooltip{{end}}"{{if not $.IsSigned}} data-content="{{$.locale.Tr "repo.watch_guest_user"}}" data-position="top center"{{end}}> <button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}> {{if $.IsWatchingRepo}}{{svg "octicon-eye-closed" 16}}{{$.locale.Tr "repo.unwatch"}}{{else}}{{svg "octicon-eye"}}{{$.locale.Tr "repo.watch"}}{{end}} </button>
-
@@ -80,7 +80,7 @@ </form>{{if not $.DisableStars}} <form method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}"> {{$.CsrfTokenHtml}} <div class="ui labeled button{{if not $.IsSigned}} tooltip{{end}}" tabindex="0"{{if not $.IsSigned}} data-content="{{$.locale.Tr "repo.star_guest_user"}}" data-position="top center"{{end}}> <div class="ui labeled button{{if not $.IsSigned}} tooltip{{end}}"{{if not $.IsSigned}} data-content="{{$.locale.Tr "repo.star_guest_user"}}" data-position="top center"{{end}}> <button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}> {{if $.IsStaringRepo}}{{svg "octicon-star-fill"}}{{$.locale.Tr "repo.unstar"}}{{else}}{{svg "octicon-star"}}{{$.locale.Tr "repo.star"}}{{end}} </button>
-
@@ -100,7 +100,7 @@ data-content="{{$.locale.Tr "repo.fork_guest_user"}}"{{else if and (not $.CanSignedUserFork) (eq (len $.UserAndOrgForks) 0)}} data-content="{{$.locale.Tr "repo.fork_from_self"}}" {{end}} data-position="top center" tabindex="0"> data-position="top center"> <a class="ui compact{{if $.ShowForkModal}} show-modal{{end}} small basic button" {{if not $.CanSignedUserFork}} {{if gt (len $.UserAndOrgForks) 1}}
-
@@ -163,7 +163,7 @@ {{if .Permission.CanRead $.UnitTypeIssues}}<a class="{{if .PageIsIssueList}}active {{end}}item" href="{{.RepoLink}}/issues"> {{svg "octicon-issue-opened"}} {{.locale.Tr "repo.issues"}} {{if .Repository.NumOpenIssues}} <span class="ui primary small label">{{CountFmt .Repository.NumOpenIssues}}</span> <span class="ui small label">{{CountFmt .Repository.NumOpenIssues}}</span> {{end}} </a> {{end}}
-
@@ -178,7 +178,16 @@ {{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}<a class="{{if .PageIsPullList}}active {{end}}item" href="{{.RepoLink}}/pulls"> {{svg "octicon-git-pull-request"}} {{.locale.Tr "repo.pulls"}} {{if .Repository.NumOpenPulls}} <span class="ui primary small label">{{CountFmt .Repository.NumOpenPulls}}</span> <span class="ui small label">{{CountFmt .Repository.NumOpenPulls}}</span> {{end}} </a> {{end}} {{if and .EnableActions (not .UnitActionsGlobalDisabled) (.Permission.CanRead $.UnitTypeActions)}} <a class="{{if .PageIsActions}}active {{end}}item" href="{{.RepoLink}}/actions"> {{svg "octicon-play"}} {{.locale.Tr "actions.actions"}} {{if .Repository.NumOpenActionRuns}} <span class="ui small label">{{CountFmt .Repository.NumOpenActionRuns}}</span> {{end}} </a> {{end}}
-
@@ -193,7 +202,7 @@ {{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects)}}<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item"> {{svg "octicon-project"}} {{.locale.Tr "repo.project_board"}} {{if .Repository.NumOpenProjects}} <span class="ui primary small label">{{CountFmt .Repository.NumOpenProjects}}</span> <span class="ui small label">{{CountFmt .Repository.NumOpenProjects}}</span> {{end}} </a> {{end}}
-
@@ -202,7 +211,7 @@ {{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo)}}<a class="{{if .PageIsReleaseList}}active {{end}}item" href="{{.RepoLink}}/releases"> {{svg "octicon-tag"}} {{.locale.Tr "repo.releases"}} {{if .NumReleases}} <span class="ui primary small label">{{CountFmt .NumReleases}}</span> <span class="ui small label">{{CountFmt .NumReleases}}</span> {{end}} </a> {{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file list {{if .IsBlame}}blame{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content repository file list {{if .IsBlame}}blame{{end}}"> {{template "repo/header" .}} <div class="ui container {{if .IsBlame}}fluid padded{{end}}"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new issue"> <div role="main" aria-label="{{.Title}}" class="page-content repository new issue"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository labels"> <div role="main" aria-label="{{.Title}}" class="page-content repository labels"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository"> <div role="main" aria-label="{{.Title}}" class="page-content repository"> {{template "repo/header" .}} <div class="ui container"> <div class="ui three column grid issue-list-headers">
-
@@ -30,8 +30,7 @@ <div id="issue-filters" class="ui stackable grid"><div class="six wide column"> {{if $.CanWriteIssuesOrPulls}} <div class="ui checkbox issue-checkbox-all vm"> <input type="checkbox"></input> <label></label> <input type="checkbox" title="{{.locale.Tr "repo.issues.action_check_all"}}"> </div> {{end}} {{template "repo/issue/openclose" .}}
-
@@ -50,9 +49,9 @@ <i class="icon df ac jc">{{svg "octicon-search" 16}}</i><input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}"> </div> <span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a> {{range .Labels}} <a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a> <a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a> {{end}} </div> </div>
-
@@ -70,7 +69,47 @@ <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestone"}}"></div> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_no_select"}}</a> {{range .Milestones}} <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.Name}}</a> <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.Name}}</a> {{end}} </div> </div> <!-- Project --> <div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item"> <span class="text"> {{.locale.Tr "repo.issues.filter_projects"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <div class="ui icon search input"> <i class="icon df ac jc">{{svg "octicon-search" 16}}</i> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_projects"}}"> </div> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_all"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_none"}}</a> {{if .OpenProjects}} <div class="divider"></div> <div class="header"> {{.locale.Tr "repo.issues.new.open_projects"}} </div> {{range .OpenProjects}} <a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} {{.Title}} </a> {{end}} {{end}} {{if .ClosedProjects}} <div class="divider"></div> <div class="header"> {{.locale.Tr "repo.issues.new.closed_projects"}} </div> {{range .ClosedProjects}} <a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} {{.Title}} </a> {{end}} {{end}} </div> </div>
-
@@ -86,9 +125,9 @@ <div class="ui icon search input"><i class="icon df ac jc">{{svg "octicon-search" 16}}</i> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_poster"}}"> </div> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a> {{range .Posters}} <a class="{{if eq $.PosterID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{.ID}}"> <a class="{{if eq $.PosterID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{.ID}}"> {{avatar .}} {{.GetDisplayName}} </a> {{end}}
-
@@ -106,9 +145,9 @@ <div class="ui icon search input"><i class="icon df ac jc">{{svg "octicon-search" 16}}</i> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_assignee"}}"> </div> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a> {{range .Assignees}} <a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{.ID}}&poster={{$.PosterID}}"> <a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}"> {{avatar .}} {{.GetDisplayName}} </a> {{end}}
-
@@ -123,12 +162,12 @@ {{.locale.Tr "repo.issues.filter_type"}}{{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.all_issues"}}</a> <a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.created_by_you"}}</a> <a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a> <a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.all_issues"}}</a> <a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.created_by_you"}}</a> <a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a> {{if .PageIsPullList}} <a class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.review_requested"}}</a> <a class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.review_requested"}}</a> {{end}} </div> </div>
-
@@ -141,14 +180,14 @@ {{.locale.Tr "repo.issues.filter_sort"}}{{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a> <a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> <a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> <a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.mostcomment"}}</a> <a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastcomment"}}</a> <a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.nearduedate"}}</a> <a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.farduedate"}}</a> <a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a> <a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> <a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> <a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.mostcomment"}}</a> <a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastcomment"}}</a> <a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.nearduedate"}}</a> <a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.farduedate"}}</a> </div> </div> </div>
-
@@ -205,18 +244,38 @@ </div></div> <!-- Projects --> <div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item"> <div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item"> <span class="text"> {{.locale.Tr "repo.project_board"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/projects"> {{.locale.Tr "repo.issues.new.no_projects"}} {{.locale.Tr "repo.issues.new.clear_projects"}} </div> {{range .Projects}} <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> {{.Title}} {{if .OpenProjects}} <div class="divider"></div> <div class="header"> {{.locale.Tr "repo.issues.new.open_projects"}} </div> {{range .OpenProjects}} <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} {{.Title}} </div> {{end}} {{end}} {{if .ClosedProjects}} <div class="divider"></div> <div class="header"> {{.locale.Tr "repo.issues.new.closed_projects"}} </div> {{range .ClosedProjects}} <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} {{.Title}} </div> {{end}} </div> {{end}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository"> <div role="main" aria-label="{{.Title}}" class="page-content repository"> {{template "repo/header" .}} <div class="ui container"> <div class="ui two column stackable grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new milestone"> <div role="main" aria-label="{{.Title}}" class="page-content repository new milestone"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository milestones"> <div role="main" aria-label="{{.Title}}" class="page-content repository milestones"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new issue"> <div role="main" aria-label="{{.Title}}" class="page-content repository new issue"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@<div class="ui compact tiny menu"> <a class="{{if not .IsShowClosed}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}&poster={{.PosterID}}"> <a class="{{if not .IsShowClosed}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&project={{.ProjectID}}&assignee={{.AssigneeID}}&poster={{.PosterID}}"> {{if .PageIsPullList}} {{svg "octicon-git-pull-request" 16 "mr-3"}} {{else}}
-
@@ -7,7 +7,7 @@ {{svg "octicon-issue-opened" 16 "mr-3"}}{{end}} {{JsPrettyNumber .IssueStats.OpenCount}} {{.locale.Tr "repo.issues.open_title"}} </a> <a class="{{if .IsShowClosed}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}&poster={{.PosterID}}"> <a class="{{if .IsShowClosed}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&project={{.ProjectID}}&assignee={{.AssigneeID}}&poster={{.PosterID}}"> {{svg "octicon-check" 16 "mr-3"}} {{JsPrettyNumber .IssueStats.ClosedCount}} {{.locale.Tr "repo.issues.closed_title"}} </a>
-
-
-
@@ -4,6 +4,7 @@ <input type="hidden" name="type" value="{{$.ViewType}}"/><input type="hidden" name="state" value="{{$.State}}"/> <input type="hidden" name="labels" value="{{.SelectLabels}}"/> <input type="hidden" name="milestone" value="{{$.MilestoneID}}"/> <input type="hidden" name="project" value="{{$.ProjectID}}"/> <input type="hidden" name="assignee" value="{{$.AssigneeID}}"/> <input type="hidden" name="poster" value="{{$.PosterID}}"/> <input name="q" value="{{.Keyword}}" placeholder="{{.locale.Tr "explore.search"}}...">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository view issue pull"> <div role="main" aria-label="{{.Title}}" class="page-content repository view issue pull"> {{template "repo/header" .}} <div class="ui container"> <div class="ui two column grid">
-
-
-
@@ -190,6 +190,7 @@ </button>{{end}} </div> </div> {{template "repo/issue/view_content/update_branch_by_merge" (dict "locale" .locale "Issue" .Issue "UpdateAllowed" .UpdateAllowed "UpdateByRebaseAllowed" .UpdateByRebaseAllowed "Link" .Link)}} {{else if .Issue.PullRequest.IsChecking}} <div class="item"> <i class="icon icon-octicon">{{svg "octicon-sync"}}</i>
-
@@ -282,44 +283,7 @@ {{$.locale.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason)}}</div> {{end}} {{end}} {{if and (gt .Issue.PullRequest.CommitsBehind 0) (not .Issue.IsClosed) (not .Issue.PullRequest.IsChecking) (not .IsPullFilesConflicted) (not .IsPullRequestBroken) (not $canAutoMerge)}} <div class="ui divider"></div> <div class="item item-section"> <div class="item-section-left"> <i class="icon icon-octicon">{{svg "octicon-alert"}}</i> {{$.locale.Tr "repo.pulls.outdated_with_base_branch"}} </div> <div class="item-section-right"> {{if and .UpdateAllowed .UpdateByRebaseAllowed}} <div class="dib"> <div class="ui buttons update-button"> <button class="ui button" data-do="{{.Link}}/update" data-redirect="{{.Link}}"> <span class="button-text"> {{$.locale.Tr "repo.pulls.update_branch"}} </span> </button> <div class="ui dropdown icon button no-text"> {{svg "octicon-triangle-down" 14 "dropdown icon"}} <div class="menu"> <div class="item active selected" data-do="{{.Link}}/update">{{$.locale.Tr "repo.pulls.update_branch"}}</div> <div class="item" data-do="{{.Link}}/update?style=rebase">{{$.locale.Tr "repo.pulls.update_branch_rebase"}}</div> </div> </div> </div> </div> {{end}} {{if and .UpdateAllowed (not .UpdateByRebaseAllowed)}} <form action="{{.Link}}/update" method="post" class="ui update-branch-form"> {{.CsrfTokenHtml}} <button class="ui compact button" data-do="update"> <span class="ui text">{{$.locale.Tr "repo.pulls.update_branch"}}</span> </button> </form> {{end}} </div> </div> {{end}} {{template "repo/issue/view_content/update_branch_by_merge" (dict "locale" .locale "Issue" .Issue "UpdateAllowed" .UpdateAllowed "UpdateByRebaseAllowed" .UpdateByRebaseAllowed "Link" .Link)}} {{if .Issue.PullRequest.IsEmpty}} <div class="ui divider"></div>
-
-
-
@@ -212,6 +212,13 @@ {{svg "octicon-gear" 16 "ml-2"}}{{end}} </a> <div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects"> <div class="header" style="text-transform: none;font-size:16px;">{{.locale.Tr "repo.issues.new.add_project_title"}}</div> {{if or .OpenProjects .ClosedProjects}} <div class="ui icon search input"> <i class="icon df ac jc">{{svg "octicon-search" 16}}</i> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_projects"}}"> </div> {{end}} <div class="no-select item">{{.locale.Tr "repo.issues.new.clear_projects"}}</div> {{if .OpenProjects}} <div class="divider"></div>
-
-
-
@@ -0,0 +1,39 @@{{$canAutoMerge := false}} {{if and (gt .Issue.PullRequest.CommitsBehind 0) (not .Issue.IsClosed) (not .Issue.PullRequest.IsChecking) (not .IsPullFilesConflicted) (not .IsPullRequestBroken) (not $canAutoMerge)}} <div class="ui divider"></div> <div class="item item-section"> <div class="item-section-left"> <i class="icon icon-octicon">{{svg "octicon-alert"}}</i> {{$.locale.Tr "repo.pulls.outdated_with_base_branch"}} </div> <div class="item-section-right"> {{if and .UpdateAllowed .UpdateByRebaseAllowed}} <div class="dib"> <div class="ui buttons update-button"> <button class="ui button" data-do="{{.Link}}/update" data-redirect="{{.Link}}"> <span class="button-text"> {{$.locale.Tr "repo.pulls.update_branch"}} </span> </button> <div class="ui dropdown icon button no-text"> {{svg "octicon-triangle-down" 14 "dropdown icon"}} <div class="menu"> <div class="item active selected" data-do="{{.Link}}/update">{{$.locale.Tr "repo.pulls.update_branch"}}</div> <div class="item" data-do="{{.Link}}/update?style=rebase">{{$.locale.Tr "repo.pulls.update_branch_rebase"}}</div> </div> </div> </div> </div> {{end}} {{if and .UpdateAllowed (not .UpdateByRebaseAllowed)}} <form action="{{.Link}}/update" method="post" class="ui update-branch-form"> {{.CsrfTokenHtml}} <button class="ui compact button" data-do="update"> <span class="ui text">{{$.locale.Tr "repo.pulls.update_branch"}}</span> </button> </form> {{end}} </div> </div> {{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> {{template "repo/migrate/helper" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository"> <div role="main" aria-label="{{.Title}}" class="page-content repository"> {{template "repo/header" .}} <div class="ui container"> <div class="ui grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new migrate"> <div role="main" aria-label="{{.Title}}" class="page-content repository new migrate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "repo/header" .}} {{template "package/shared/list" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository projects milestones"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects milestones"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository projects edit-project new milestone"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects edit-project new milestone"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository projects view-project"> <div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project"> {{template "repo/header" .}} <div class="ui container"> <div class="ui two column stackable grid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository view issue pull commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository view issue pull commits"> {{template "repo/header" .}} <div class="ui container"> <div class="navbar">
-
-
-
@@ -3,7 +3,7 @@<input type="hidden" id="repolink" value="{{$.RepoRelPath}}"> <input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/> <div class="page-content repository view issue pull files diff"> <div role="main" aria-label="{{.Title}}" class="page-content repository view issue pull files diff"> {{template "repo/header" .}} <div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}"> <div class="navbar">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new fork"> <div role="main" aria-label="{{.Title}}" class="page-content repository new fork"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository release"> <div role="main" aria-label="{{.Title}}" class="page-content repository release"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new release"> <div role="main" aria-label="{{.Title}}" class="page-content repository new release"> {{template "repo/header" .}} <div class="ui container"> <h2 class="ui dividing header">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository file list"> <div role="main" aria-label="{{.Title}}" class="page-content repository file list"> {{template "repo/header" .}} <div class="ui container"> <div class="ui repo-search">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings edit"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings edit"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings collaboration"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings collaboration"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
@@ -51,7 +51,7 @@ <div class="ui key list">{{range .Deploykeys}} <div class="item"> <div class="right floated content"> <button class="ui red tiny button delete-button" data-modal-id="delete-deploy_keys-modal" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> {{$.locale.Tr "settings.delete_key"}} </button> </div>
-
@@ -75,11 +75,9 @@ {{.locale.Tr "repo.settings.no_deploy_keys"}}{{end}} </div> </div> <br/> {{template "repo/settings/secrets" .}} </div> <div class="ui small basic delete modal" id="delete-deploy_keys-modal"> <div class="ui small basic delete modal"> <div class="ui icon header"> {{svg "octicon-trash"}} {{.locale.Tr "repo.settings.deploy_key_deletion"}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings edit githook"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings edit githook"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings githooks"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings githooks"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings lfs"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings lfs"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings lfs"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings lfs"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container repository file list">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings lfs"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings lfs"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container repository file list">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings lfs"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings lfs"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container repository file list">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings lfs"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings lfs"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -12,7 +12,8 @@ {{end}}{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}} <li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.locale.Tr "repo.settings.githooks"}}</a></li> {{end}} <li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.locale.Tr "secrets.secrets"}}</a></li> <li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.locale.Tr "repo.settings.deploy_keys"}}</a></li> <li {{if .PageIsSettingsSecrets}}class="current"{{end}}><a href="{{.RepoLink}}/settings/secrets">{{.locale.Tr "secrets.secrets"}}</a></li> </ul> </div> </div>
-
-
-
@@ -25,11 +25,19 @@ {{.locale.Tr "repo.settings.githooks"}}</a> {{end}} <a class="{{if .PageIsSettingsKeys}}active {{end}}item" href="{{.RepoLink}}/settings/keys"> {{.locale.Tr "repo.settings.deploy_keys"}} </a> <a class="{{if .PageIsSettingsSecrets}}active {{end}}item" href="{{.RepoLink}}/settings/secrets"> {{.locale.Tr "secrets.secrets"}} </a> {{if .LFSStartServer}} <a class="{{if .PageIsSettingsLFS}}active {{end}}item" href="{{.RepoLink}}/settings/lfs"> {{.locale.Tr "repo.settings.lfs"}} </a> {{end}} {{if and .EnableActions (not .UnitActionsGlobalDisabled) (.Permission.CanRead $.UnitTypeActions)}} <a class="{{if .PageIsSettingsRunners}}active {{end}}item" href="{{.RepoLink}}/settings/runners"> {{.locale.Tr "actions.runners"}} </a> {{end}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings options"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings options"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
@@ -420,6 +420,19 @@ <label>{{.locale.Tr "repo.settings.projects_desc"}}</label></div> </div> {{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}} <div class="inline field"> <label>{{.locale.Tr "repo.releases"}}</label> {{if .UnitTypeReleases.UnitGlobalDisabled}} <div class="ui checkbox tooltip disabled" data-content="{{.locale.Tr "repo.unit_disabled"}}"> {{else}} <div class="ui checkbox"> {{end}} <input class="enable-system" name="enable_releases" type="checkbox" {{if $isReleasesEnabled}}checked{{end}}> <label>{{.locale.Tr "repo.settings.releases_desc"}}</label> </div> </div> {{$isPackagesEnabled := .Repository.UnitEnabled $.Context $.UnitTypePackages}} <div class="inline field"> <label>{{.locale.Tr "repo.packages"}}</label>
-
@@ -432,6 +445,21 @@ <input class="enable-system" name="enable_packages" type="checkbox" {{if $isPackagesEnabled}}checked{{end}}><label>{{.locale.Tr "repo.settings.packages_desc"}}</label> </div> </div> {{if .EnableActions}} {{$isActionsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeActions}} <div class="inline field"> <label>{{.locale.Tr "actions.actions"}}</label> {{if .UnitTypeActions.UnitGlobalDisabled}} <div class="ui checkbox tooltip disabled" data-content="{{.locale.Tr "repo.unit_disabled"}}"> {{else}} <div class="ui checkbox"> {{end}} <input class="enable-system" name="enable_actions" type="checkbox" {{if $isActionsEnabled}}checked{{end}}> <label>{{.locale.Tr "repo.settings.actions_desc"}}</label> </div> </div> {{end}} {{if not .IsMirror}} <div class="ui divider"></div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings branches"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings branches"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -0,0 +1,9 @@{{template "base/head" .}} <div class="page-content repository settings runners"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container"> {{template "shared/actions/runner_edit" .}} </div> </div> {{template "base/footer" .}}
-
-
-
@@ -0,0 +1,9 @@{{template "base/head" .}} <div class="page-content repository settings runners"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container"> {{template "shared/actions/runner_list" .}} </div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,80 +1,10 @@<div class="ui container"> <h4 class="ui top attached header"> {{.locale.Tr "secrets.secrets"}} <div class="ui right"> <div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div> </div> </h4> <div class="ui attached segment"> <div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel"> <form class="ui form" action="{{.Link}}/secrets" method="post"> {{.CsrfTokenHtml}} <div class="field"> {{.locale.Tr "secrets.description"}} </div> <div class="field{{if .Err_Title}} error{{end}}"> <label for="secret-title">{{.locale.Tr "secrets.name"}}</label> <input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}"> </div> <div class="field{{if .Err_Content}} error{{end}}"> <label for="secret-content">{{.locale.Tr "secrets.value"}}</label> <textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea> </div> <button class="ui green button"> {{.locale.Tr "secrets.creation"}} </button> <button class="ui hide-panel button" data-panel="#add-secret-panel"> {{.locale.Tr "cancel"}} </button> </form> </div> {{if .Secrets}} <div class="ui key list"> {{range .Secrets}} <div class="item"> <div class="right floated content"> <button class="ui red tiny button delete-button" data-modal-id="delete-secret-modal" data-url="{{$.Link}}/secrets/delete" data-id="{{.ID}}"> {{$.locale.Tr "settings.delete_key"}} </button> </div> <div class="left floated content"> <i>{{svg "octicon-key" 32}}</i> </div> <div class="content"> <strong>{{.Name}}</strong> <div class="print meta">******</div> <div class="activity meta"> <i> {{$.locale.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> </i> </div> </div> </div> {{end}} </div> {{else}} {{.locale.Tr "secrets.none"}} {{end}} {{template "base/head" .}} <div class="page-content repository settings"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}} {{template "shared/secrets/add_list" .}} </div> </div> <div class="ui small basic delete modal" id="delete-secret-modal"> <div class="ui icon header"> {{svg "octicon-trash"}} {{.locale.Tr "secrets.deletion"}} </div> <div class="content"> <p>{{.locale.Tr "secrets.deletion.description"}}</p> </div> <div class="actions"> <div class="ui red basic inverted cancel button"> <i class="remove icon"></i> {{.locale.Tr "modal.no"}} </div> <div class="ui green basic inverted ok button"> <i class="checkmark icon"></i> {{.locale.Tr "modal.yes"}} </div> </div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings edit"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings edit"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings webhooks"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings webhooks"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository settings new webhook"> <div role="main" aria-label="{{.Title}}" class="page-content repository settings new webhook"> {{template "repo/header" .}} {{template "repo/settings/navbar" .}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository watchers"> <div role="main" aria-label="{{.Title}}" class="page-content repository watchers"> {{template "repo/header" .}} {{template "repo/user_cards" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository wiki new"> <div role="main" aria-label="{{.Title}}" class="page-content repository wiki new"> {{template "repo/header" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository wiki pages"> <div role="main" aria-label="{{.Title}}" class="page-content repository wiki pages"> {{template "repo/header" .}} <div class="ui container"> <h2 class="ui header df ac sb">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository wiki revisions"> <div role="main" aria-label="{{.Title}}" class="page-content repository wiki revisions"> {{template "repo/header" .}} {{$title := .title}} <div class="ui container">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository wiki start"> <div role="main" aria-label="{{.Title}}" class="page-content repository wiki start"> {{template "repo/header" .}} <div class="ui container"> <div class="ui center segment">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository wiki view"> <div role="main" aria-label="{{.Title}}" class="page-content repository wiki view"> {{template "repo/header" .}} {{$title := .title}} <div class="ui container">
-
-
-
@@ -0,0 +1,102 @@<div class="runner-container"> {{template "base/alert" .}} <h4 class="ui top attached header"> {{.locale.Tr "actions.runners.runner_title"}} {{.Runner.ID}} {{.Runner.Name}} </h4> <div class="ui attached segment"> <form class="ui form" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} <div class="runner-basic-info"> <div class="field dib disabled"> <label>{{.locale.Tr "actions.runners.status"}}</label> <span class="runner-status-{{if .Runner.IsOnline}}online{{else}}offline{{end}}">{{.Runner.StatusLocaleName $.locale}}</span> </div> <div class="field dib disabled"> <label>{{.locale.Tr "actions.runners.last_online"}}</label> <span>{{TimeSinceUnix .Runner.LastOnline $.locale}}</span> </div> <div class="field dib disabled"> <label>{{.locale.Tr "actions.runners.agent_labels"}}</label> <span> {{range .Runner.AgentLabels}} <span>{{.}}</span> {{end}} </span> </div> <div class="field dib disabled"> <label>{{.locale.Tr "actions.runners.owner_type"}}</label> <span>{{.Runner.OwnType}}</span> </div> </div> <div class="ui divider"></div> <div class="field"> <label for="description">{{.locale.Tr "actions.runners.description"}}</label> <input id="description" name="description" value="{{.Runner.Description}}"> </div> <div class="field tooltip" data-content="Labels are comma-separated. Whitespace at the beginning, end, and around the commas are ignored."> <label for="custom_labels">{{.locale.Tr "actions.runners.custom_labels"}}</label> <input id="custom_labels" name="custom_labels" value="{{Join .Runner.CustomLabels `,`}}"> <p class="help">{{.locale.Tr "actions.runners.custom_labels_helper"}}</p> </div> <div class="ui divider"></div> <div class="field"> <button class="ui green button" data-url="{{.Link}}">{{.locale.Tr "actions.runners.update_runner"}}</button> <button class="ui red button delete-button show-modal" data-url="{{.Link}}/delete" data-modal="#runner-delete-modal"> {{.locale.Tr "actions.runners.delete_runner"}}</button> </div> </form> </div> <h4 class="ui top attached header"> {{.locale.Tr "actions.runners.task_list"}} </h4> <div class="ui attached segment"> <table class="ui very basic striped table unstackable"> <thead> <tr> <th>{{.locale.Tr "actions.runners.task_list.run"}}</th> <th>{{.locale.Tr "actions.runners.task_list.status"}}</th> <th>{{.locale.Tr "actions.runners.task_list.repository"}}</th> <th>{{.locale.Tr "actions.runners.task_list.commit"}}</th> <th>{{.locale.Tr "actions.runners.task_list.done_at"}}</th> </tr> </thead> <tbody> {{range .Tasks}} <tr> <td><a href="{{.GetRunLink}}" target="_blank">{{.ID}}</a></td> <td><span class="ui label task-status-{{.Status.String}}">{{.Status.LocaleString $.locale}}</span></td> <td><a href="{{.GetRepoLink}}" target="_blank">{{.GetRepoName}}</a></td> <td> <strong><a href="{{.GetCommitLink}}" target="_blank">{{ShortSha .CommitSHA}}</a></strong> </td> <td>{{if .IsStopped}} <span>{{TimeSinceUnix .Stopped $.locale}}</span> {{else}}-{{end}}</td> </tr> {{end}} {{if not .Tasks}} <tr> <td colspan="5">{{.locale.Tr "runners.task_list.no_tasks"}}</td> </tr> {{end}} </tbody> </table> {{template "base/paginate" .}} </div> <div class="ui small basic delete modal" id="runner-delete-modal"> <div class="ui icon header"> {{svg "octicon-trash"}} {{.locale.Tr "actions.runners.delete_runner_header"}} </div> <div class="content"> <p>{{.locale.Tr "actions.runners.delete_runner_notice" | Safe}}</p> </div> {{template "base/delete_modal_actions" .}} </div> </div>
-
-
-
@@ -0,0 +1,90 @@<div class="runner-container"> {{template "base/alert" .}} <h4 class="ui top attached header"> {{.locale.Tr "actions.runners.runner_manage_panel"}} ({{.locale.Tr "admin.total" .Total}}) <div class="ui right"> <div class="ui top right pointing dropdown"> <button class="ui button primary"> {{.locale.Tr "actions.runners.new"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </button> <div class="menu"> <div class="item"> {{/* TODO: replece the document link when there's a better one than the README of act_runner */}} <a href="https://gitea.com/gitea/act_runner/src/branch/main/README.md">{{.locale.Tr "actions.runners.new_notice"}}</a> </div> <div class="divider"></div> <div class="header"> Registration Token </div> <div class="ui input"> <input type="text" value="{{.RegistrationToken}}"> <div class="ui basic label button" data-clipboard-text="{{.RegistrationToken}}"> {{svg "octicon-copy" 14}} </div> </div> <div class="divider"></div> <div class="item"> <a href="{{$.Link}}/reset_registration_token">Reset registration token</a> </div> </div> </div> </div> </h4> <div class="ui attached segment"> <form class="ui form ignore-dirty" id="user-list-search-form" action="{{$.Link}}"> <!-- Search Text --> <div class="ui fluid action input" style="max-width: 70%;"> <input name="q" value="{{.Keyword}}" placeholder="{{.locale.Tr "explore.search"}}..." autofocus> <button class="ui primary button">{{.locale.Tr "explore.search"}}</button> </div> </form> </div> <div class="ui attached table segment"> <table class="ui very basic striped table unstackable"> <thead> <tr> <th data-sortt-asc="online" data-sortt-desc="offline">{{.locale.Tr "actions.runners.status"}}</th> <th data-sortt-asc="alphabetically">{{.locale.Tr "actions.runners.id"}}</th> <th>{{.locale.Tr "actions.runners.name"}}</th> <th>{{.locale.Tr "actions.runners.owner_type"}}</th> <th>{{.locale.Tr "actions.runners.labels"}}</th> <th>{{.locale.Tr "actions.runners.last_online"}}</th> <th></th> </tr> </thead> <tbody> {{if .Runners}} {{range .Runners}} <tr> <td> <span class="runner-status-{{if .IsOnline}}online{{else}}offline{{end}}">{{.StatusLocaleName $.locale}}</span> </td> <td>{{.ID}}</td> <td><p class="tooltip" data-content="{{.Description}}">{{.Name}}</p></td> <td>{{.OwnType}}</td> <td class="runner-tags"> {{range .AllLabels}}<span class="ui label">{{.}}</span>{{end}} </td> <td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</td> <td class="runner-ops"> {{if .Editable $.RunnerOnwerID $.RunnerRepoID}} <a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a> {{end}} </td> </tr> {{end}} {{else}} <tr> <td class="center aligned" colspan="6">{{.locale.Tr "actions.runners.none"}}</td> </tr> {{end}} </tbody> </table> </div> {{template "base/paginate" .}} </div>
-
-
-
@@ -5,8 +5,7 @@ <li class="item df py-3"><div class="issue-item-left df"> {{if $.CanWriteIssuesOrPulls}} <div class="ui checkbox issue-checkbox"> <input type="checkbox" data-issue-id={{.ID}}></input> <label></label> <input type="checkbox" data-issue-id={{.ID}} title="{{$.locale.Tr "repo.issues.action_check"}} «{{.Title}}»"> </div> {{end}} <div class="issue-item-icon">
-
-
-
@@ -0,0 +1,68 @@<h4 class="ui top attached header"> {{.locale.Tr "secrets.secrets"}} <div class="ui right"> <div class="ui primary tiny show-panel button" data-panel="#add-secret-panel">{{.locale.Tr "secrets.creation"}}</div> </div> </h4> <div class="ui attached segment"> <div class="{{if not .HasError}}hide {{end}}mb-4" id="add-secret-panel"> <form class="ui form" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} <div class="field"> {{.locale.Tr "secrets.description"}} </div> <div class="field{{if .Err_Title}} error{{end}}"> <label for="secret-title">{{.locale.Tr "secrets.name"}}</label> <input id="secret-title" name="title" value="{{.title}}" autofocus required pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}"> </div> <div class="field{{if .Err_Content}} error{{end}}"> <label for="secret-content">{{.locale.Tr "secrets.value"}}</label> <textarea id="secret-content" name="content" required placeholder="{{.locale.Tr "secrets.creation.value_placeholder"}}">{{.content}}</textarea> </div> <button class="ui green button"> {{.locale.Tr "secrets.creation"}} </button> <button class="ui hide-panel button" data-panel="#add-secret-panel"> {{.locale.Tr "cancel"}} </button> </form> </div> {{if .Secrets}} <div class="ui key list"> {{range .Secrets}} <div class="item"> <div class="right floated content"> <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> {{$.locale.Tr "settings.delete_key"}} </button> </div> <div class="left floated content"> <i>{{svg "octicon-key" 32}}</i> </div> <div class="content"> <strong>{{.Name}}</strong> <div class="print meta">******</div> <div class="activity meta"> <i> {{$.locale.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> </i> </div> </div> </div> {{end}} </div> {{else}} {{.locale.Tr "secrets.none"}} {{end}} </div> <div class="ui small basic delete modal"> <div class="ui header"> {{svg "octicon-trash" 16 "mr-2"}} {{.locale.Tr "secrets.deletion"}} </div> <div class="content"> <p>{{.locale.Tr "secrets.deletion.description"}}</p> </div> {{template "base/delete_modal_actions" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content ui container center full-screen-width {{if .IsRepo}}repository{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content ui container center full-screen-width {{if .IsRepo}}repository{{end}}"> {{if .IsRepo}}{{template "repo/header" .}}{{end}} <div class="ui container center"> <p style="margin-top: 100px"><img src="{{AssetUrlPrefix}}/img/404.png" alt="404"/></p>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content ui container full-screen-width center"> <div role="main" aria-label="{{.Title}}" class="page-content ui container full-screen-width center"> <p style="margin-top: 100px"><img src="{{AssetUrlPrefix}}/img/500.png" alt="500"/></p> <div class="ui divider"></div> <br>
-
-
-
@@ -437,6 +437,127 @@ }} } }, "/admin/hooks": { "get": { "produces": [ "application/json" ], "tags": [ "admin" ], "summary": "List system's webhooks", "operationId": "adminListHooks", "parameters": [ { "type": "integer", "description": "page number of results to return (1-based)", "name": "page", "in": "query" }, { "type": "integer", "description": "page size of results", "name": "limit", "in": "query" } ], "responses": { "200": { "$ref": "#/responses/HookList" } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "admin" ], "summary": "Create a hook", "operationId": "adminCreateHook", "parameters": [ { "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/CreateHookOption" } } ], "responses": { "201": { "$ref": "#/responses/Hook" } } } }, "/admin/hooks/{id}": { "get": { "produces": [ "application/json" ], "tags": [ "admin" ], "summary": "Get a hook", "operationId": "adminGetHook", "parameters": [ { "type": "integer", "format": "int64", "description": "id of the hook to get", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "$ref": "#/responses/Hook" } } }, "patch": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "admin" ], "summary": "Update a hook", "operationId": "adminEditHook", "parameters": [ { "type": "integer", "format": "int64", "description": "id of the hook to update", "name": "id", "in": "path", "required": true }, { "name": "body", "in": "body", "schema": { "$ref": "#/definitions/EditHookOption" } } ], "responses": { "200": { "$ref": "#/responses/Hook" } } } }, "/admin/orgs": { "get": { "produces": [
-
@@ -896,6 +1017,33 @@ "$ref": "#/responses/error"}, "422": { "$ref": "#/responses/validationError" } } } }, "/amdin/hooks/{id}": { "delete": { "produces": [ "application/json" ], "tags": [ "admin" ], "summary": "Delete a hook", "operationId": "adminDeleteHook", "parameters": [ { "type": "integer", "format": "int64", "description": "id of the hook to delete", "name": "id", "in": "path", "required": true } ], "responses": { "204": { "$ref": "#/responses/empty" } } }
-
@@ -2253,6 +2401,7 @@ {"enum": [ "composer", "conan", "conda", "container", "generic", "helm",
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user activate"> <div role="main" aria-label="{{.Title}}" class="page-content user activate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/activate" method="post">
-
@@ -30,8 +30,10 @@ </div><input id="code" name="code" type="hidden" value="{{.Code}}"> {{else if .IsSendRegisterMail}} <p>{{.locale.Tr "auth.confirmation_mail_sent_prompt" (.Email|Escape) .ActiveCodeLives | Str2html}}</p> {{else if .IsActivateFailed}} {{else if .IsCodeInvalid}} <p>{{.locale.Tr "auth.invalid_code"}}</p> {{else if .IsPasswordInvalid}} <p>{{.locale.Tr "auth.invalid_password"}}</p> {{else if .ManualActivationOnly}} <p class="center">{{.locale.Tr "auth.manual_activation_only"}}</p> {{else}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> <div class="ui container"> {{template "user/auth/change_passwd_inner" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin"> <div role="main" aria-label="{{.Title}}" class="page-content user signin"> <div class="ui container"> <div class="ui grid"> {{template "user/auth/finalize_openid_navbar" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user forgot password"> <div role="main" aria-label="{{.Title}}" class="page-content user forgot password"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form ignore-dirty" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content ui one column stackable center aligned page grid oauth2-authorize-application-box"> <div role="main" aria-label="{{.Title}}" class="page-content ui one column stackable center aligned page grid oauth2-authorize-application-box"> <div class="column seven wide"> <div class="ui middle centered raised segments"> <h3 class="ui top attached header">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content ui one column stackable center aligned page grid oauth2-authorize-application-box {{if .IsRepo}}repository{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content ui one column stackable center aligned page grid oauth2-authorize-application-box {{if .IsRepo}}repository{{end}}"> {{if .IsRepo}}{{template "repo/header" .}}{{end}} <div class="column seven wide"> <div class="ui middle centered raised segments">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user link-account"> <div role="main" aria-label="{{.Title}}" class="page-content user link-account"> <div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> <div class="new-menu-inner"> <!-- TODO handle .ShowRegistrationButton once other login bugs are fixed -->
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user activate"> <div role="main" aria-label="{{.Title}}" class="page-content user activate"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user reset password"> <div role="main" aria-label="{{.Title}}" class="page-content user reset password"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form ignore-dirty" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> {{template "user/auth/signin_navbar" .}} <div class="ui middle very relaxed page grid"> <div class="ui container column fluid">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin openid"> <div role="main" aria-label="{{.Title}}" class="page-content user signin openid"> {{template "user/auth/signin_navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> <div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}"> <div class="ui middle very relaxed page grid"> {{template "user/auth/signup_inner" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signup"> <div role="main" aria-label="{{.Title}}" class="page-content user signup"> {{template "user/auth/signup_openid_navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signup"> <div role="main" aria-label="{{.Title}}" class="page-content user signup"> {{template "user/auth/signup_openid_navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin"> <div role="main" aria-label="{{.Title}}" class="page-content user signin"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user signin"> <div role="main" aria-label="{{.Title}}" class="page-content user signin"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository code-search"> <div role="main" aria-label="{{.Title}}" class="page-content repository code-search"> {{template "user/overview/header" .}} <div class="ui container"> {{template "code/searchform" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content dashboard feeds"> <div role="main" aria-label="{{.Title}}" class="page-content dashboard feeds"> {{template "user/dashboard/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content dashboard issues"> <div role="main" aria-label="{{.Title}}" class="page-content dashboard issues"> {{template "user/dashboard/navbar" .}} <div class="ui container"> <div class="ui stackable grid">
-
@@ -30,7 +30,7 @@ {{end}}<div class="ui divider"></div> <a class="{{if not $.RepoIDs}}ui basic primary button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}"> <span class="text truncate">All</span> <div class="ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{CountFmt .TotalIssueCount}}</div> <span class="ui">{{CountFmt .TotalIssueCount}}</span> </a> {{range .Repos}} {{with $Repo := .}}
-
@@ -49,7 +49,7 @@ {{- end -}}{{- end -}} ]&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}" title="{{.FullName}}"> <span class="text truncate">{{$Repo.FullName}}</span> <div class="ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{CountFmt (index $.Counts $Repo.ID)}}</div> <span class="ui">{{CountFmt (index $.Counts $Repo.ID)}}</span> </a> {{end}} {{end}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content dashboard issues repository milestones"> <div role="main" aria-label="{{.Title}}" class="page-content dashboard issues repository milestones"> {{template "user/dashboard/navbar" .}} <div class="ui container"> <div class="ui stackable grid">
-
-
-
@@ -7,8 +7,8 @@ {{avatar .ContextUser}}<span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span> {{if .ContextUser.IsOrganization}} <span class="org-visibility"> {{if .ContextUser.Visibility.IsLimited}}<div class="ui orange tiny horizontal label">{{.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .ContextUser.Visibility.IsPrivate}}<div class="ui red tiny horizontal label">{{.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} {{if .ContextUser.Visibility.IsLimited}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .ContextUser.Visibility.IsPrivate}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} </span> {{end}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
-
@@ -27,8 +27,8 @@ <a class="{{if eq $.ContextUser.ID .ID}}active selected{{end}} item truncated-item-container" title="{{.Name}}" href="{{.OrganisationLink}}/{{if $.PageIsIssues}}issues{{else if $.PageIsPulls}}pulls{{else if $.PageIsMilestonesDashboard}}milestones{{else}}dashboard{{end}}">{{avatar .}} <span class="truncated-item-name">{{.ShortName 40}}</span> <span class="org-visibility"> {{if .Visibility.IsLimited}}<div class="ui orange tiny horizontal label">{{$.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .Visibility.IsPrivate}}<div class="ui red tiny horizontal label">{{$.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} {{if .Visibility.IsLimited}}<div class="ui basic tiny horizontal label">{{$.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .Visibility.IsPrivate}}<div class="ui basic tiny horizontal label">{{$.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} </span> </a> {{end}}
-
-
-
@@ -1,4 +1,4 @@<div class="page-content user notification" id="notification_div" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div class="ui container"> <h1 class="ui dividing header">{{.locale.Tr "notification.notifications"}}</h1> <div class="ui top attached tabular menu">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user notification" id="notification_subscriptions" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_subscriptions" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div class="ui container"> <div class="ui top attached tabular menu"> <a href="{{AppSubUrl}}/notifications/subscriptions" class="{{if eq .Status 1}}active {{end}}item">
-
-
-
@@ -8,8 +8,8 @@ <div class="ui header">{{avatar . 100}} <span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span> <span class="org-visibility"> {{if .Visibility.IsLimited}}<div class="ui medium orange horizontal label">{{$.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .Visibility.IsPrivate}}<div class="ui medium red horizontal label">{{$.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} {{if .Visibility.IsLimited}}<div class="ui medium basic horizontal label">{{$.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}} {{if .Visibility.IsPrivate}}<div class="ui medium basic horizontal label">{{$.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}} </span> </div> </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "user/overview/header" .}} {{template "package/shared/versionlist" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository packages"> <div role="main" aria-label="{{.Title}}" class="page-content repository packages"> {{template "user/overview/header" .}} {{template "package/shared/list" .}} </div>
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user profile"> <div role="main" aria-label="{{.Title}}" class="page-content user profile"> <div class="ui container"> <div class="ui stackable grid"> <div class="ui five wide column">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content repository new repo"> <div role="main" aria-label="{{.Title}}" class="page-content repository new repo"> <div class="ui middle very relaxed page grid"> <div class="column"> <form class="ui form" action="{{.Link}}" method="post">
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings account"> <div role="main" aria-label="{{.Title}}" class="page-content user settings account"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings sshkeys"> <div role="main" aria-label="{{.Title}}" class="page-content user settings sshkeys"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings applications"> <div role="main" aria-label="{{.Title}}" class="page-content user settings applications"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings applications"> <div role="main" aria-label="{{.Title}}" class="page-content user settings applications"> {{template "user/settings/navbar" .}} {{template "user/settings/applications_oauth2_edit_form" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings sshkeys"> <div role="main" aria-label="{{.Title}}" class="page-content user settings sshkeys"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -18,6 +18,9 @@ </a><a class="{{if .PageIsSettingsKeys}}active {{end}}item" href="{{AppSubUrl}}/user/settings/keys"> {{.locale.Tr "settings.ssh_gpg_keys"}} </a> <a class="{{if .PageIsSettingsSecrets}}active {{end}}item" href="{{AppSubUrl}}/user/settings/secrets"> {{.locale.Tr "secrets.secrets"}} </a> {{if .EnablePackages}} <a class="{{if .PageIsSettingsPackages}}active {{end}}item" href="{{AppSubUrl}}/user/settings/packages"> {{.locale.Tr "packages.title"}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings organization"> <div role="main" aria-label="{{.Title}}" class="page-content user settings organization"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings packages"> <div role="main" aria-label="{{.Title}}" class="page-content user settings packages"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings packages"> <div role="main" aria-label="{{.Title}}" class="page-content user settings packages"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings packages admin"> <div role="main" aria-label="{{.Title}}" class="page-content user settings packages admin"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "package/shared/cleanup_rules/preview" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings profile"> <div role="main" aria-label="{{.Title}}" class="page-content user settings profile"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings repos"> <div role="main" aria-label="{{.Title}}" class="page-content user settings repos"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -0,0 +1,10 @@{{template "base/head" .}} <div class="page-content user settings secrets"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}} {{template "shared/secrets/add_list" .}} </div> </div> {{template "base/footer" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings security"> <div role="main" aria-label="{{.Title}}" class="page-content user settings security"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -1,5 +1,5 @@{{template "base/head" .}} <div class="page-content user settings twofa"> <div role="main" aria-label="{{.Title}}" class="page-content user settings twofa"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}}
-
-
-
@@ -0,0 +1,274 @@// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package integration import ( "archive/tar" "archive/zip" "bytes" "fmt" "io" "net/http" "testing" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" conda_module "code.gitea.io/gitea/modules/packages/conda" "code.gitea.io/gitea/tests" "github.com/dsnet/compress/bzip2" "github.com/klauspost/compress/zstd" "github.com/stretchr/testify/assert" ) func TestPackageConda(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) packageName := "test_package" packageVersion := "1.0.1" channel := "test-channel" root := fmt.Sprintf("/api/packages/%s/conda", user.Name) t.Run("Upload", func(t *testing.T) { tarContent := func() []byte { var buf bytes.Buffer tw := tar.NewWriter(&buf) content := []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `","subdir":"noarch","build":"xxx"}`) hdr := &tar.Header{ Name: "info/index.json", Mode: 0o600, Size: int64(len(content)), } tw.WriteHeader(hdr) tw.Write(content) tw.Close() return buf.Bytes() }() t.Run(".tar.bz2", func(t *testing.T) { defer tests.PrintCurrentTest(t)() var buf bytes.Buffer bw, _ := bzip2.NewWriter(&buf, nil) io.Copy(bw, bytes.NewReader(tarContent)) bw.Close() filename := fmt.Sprintf("%s-%s.tar.bz2", packageName, packageVersion) req := NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) MakeRequest(t, req, http.StatusUnauthorized) req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusCreated) req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusConflict) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConda) assert.NoError(t, err) assert.Len(t, pvs, 1) pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) assert.NoError(t, err) assert.Nil(t, pd.SemVer) assert.IsType(t, &conda_module.VersionMetadata{}, pd.Metadata) assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageVersion, pd.Version.Version) assert.Empty(t, pd.PackageProperties.GetByName(conda_module.PropertyChannel)) }) t.Run(".conda", func(t *testing.T) { defer tests.PrintCurrentTest(t)() var infoBuf bytes.Buffer zsw, _ := zstd.NewWriter(&infoBuf) io.Copy(zsw, bytes.NewReader(tarContent)) zsw.Close() var buf bytes.Buffer zpw := zip.NewWriter(&buf) w, _ := zpw.Create("info-x.tar.zst") w.Write(infoBuf.Bytes()) zpw.Close() fullName := channel + "/" + packageName filename := fmt.Sprintf("%s-%s.conda", packageName, packageVersion) req := NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) MakeRequest(t, req, http.StatusUnauthorized) req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusCreated) req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusConflict) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConda) assert.NoError(t, err) assert.Len(t, pvs, 2) pds, err := packages.GetPackageDescriptors(db.DefaultContext, pvs) assert.NoError(t, err) assert.Condition(t, func() bool { for _, pd := range pds { if pd.Package.Name == fullName { return true } } return false }) for _, pd := range pds { if pd.Package.Name == fullName { assert.Nil(t, pd.SemVer) assert.IsType(t, &conda_module.VersionMetadata{}, pd.Metadata) assert.Equal(t, fullName, pd.Package.Name) assert.Equal(t, packageVersion, pd.Version.Version) assert.Equal(t, channel, pd.PackageProperties.GetByName(conda_module.PropertyChannel)) } } }) }) t.Run("Download", func(t *testing.T) { t.Run(".tar.bz2", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/%s-%s-xxx.tar.bz2", root, packageName, packageVersion)) MakeRequest(t, req, http.StatusOK) req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/noarch/%s-%s-xxx.tar.bz2", root, channel, packageName, packageVersion)) MakeRequest(t, req, http.StatusNotFound) }) t.Run(".conda", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/%s-%s-xxx.conda", root, packageName, packageVersion)) MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/noarch/%s-%s-xxx.conda", root, channel, packageName, packageVersion)) MakeRequest(t, req, http.StatusOK) }) }) t.Run("EnumeratePackages", func(t *testing.T) { type Info struct { Subdir string `json:"subdir"` } type PackageInfo struct { Name string `json:"name"` Version string `json:"version"` NoArch string `json:"noarch"` Subdir string `json:"subdir"` Timestamp int64 `json:"timestamp"` Build string `json:"build"` BuildNumber int64 `json:"build_number"` Dependencies []string `json:"depends"` License string `json:"license"` LicenseFamily string `json:"license_family"` HashMD5 string `json:"md5"` HashSHA256 string `json:"sha256"` Size int64 `json:"size"` } type RepoData struct { Info Info `json:"info"` Packages map[string]*PackageInfo `json:"packages"` PackagesConda map[string]*PackageInfo `json:"packages.conda"` Removed map[string]*PackageInfo `json:"removed"` } req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json.bz2", root)) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json", root)) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json.bz2", root)) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) t.Run(".tar.bz2", func(t *testing.T) { defer tests.PrintCurrentTest(t)() pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeConda, packageName, packageVersion) assert.NoError(t, err) pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) assert.NoError(t, err) req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) resp := MakeRequest(t, req, http.StatusOK) var result RepoData DecodeJSON(t, resp, &result) assert.Equal(t, "noarch", result.Info.Subdir) assert.Empty(t, result.PackagesConda) assert.Empty(t, result.Removed) filename := fmt.Sprintf("%s-%s-xxx.tar.bz2", packageName, packageVersion) assert.Contains(t, result.Packages, filename) packageInfo := result.Packages[filename] assert.Equal(t, packageName, packageInfo.Name) assert.Equal(t, packageVersion, packageInfo.Version) assert.Equal(t, "noarch", packageInfo.Subdir) assert.Equal(t, "xxx", packageInfo.Build) assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) }) t.Run(".conda", func(t *testing.T) { defer tests.PrintCurrentTest(t)() pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeConda, channel+"/"+packageName, packageVersion) assert.NoError(t, err) pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) assert.NoError(t, err) req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/noarch/repodata.json", root, channel)) resp := MakeRequest(t, req, http.StatusOK) var result RepoData DecodeJSON(t, resp, &result) assert.Equal(t, "noarch", result.Info.Subdir) assert.Empty(t, result.Packages) assert.Empty(t, result.Removed) filename := fmt.Sprintf("%s-%s-xxx.conda", packageName, packageVersion) assert.Contains(t, result.PackagesConda, filename) packageInfo := result.PackagesConda[filename] assert.Equal(t, packageName, packageInfo.Name) assert.Equal(t, packageVersion, packageInfo.Version) assert.Equal(t, "noarch", packageInfo.Subdir) assert.Equal(t, "xxx", packageInfo.Build) assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) }) }) }
-
-
-
@@ -5,8 +5,10 @@ package integrationimport ( "bytes" "crypto/sha256" "fmt" "net/http" "strings" "testing" "time"
-
@@ -171,34 +173,62 @@func TestPackageQuota(t *testing.T) { defer tests.PrepareTestEnv(t)() limitTotalOwnerCount, limitTotalOwnerSize, limitSizeGeneric := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize, setting.Packages.LimitSizeGeneric limitTotalOwnerCount, limitTotalOwnerSize := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize // Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload. admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) uploadPackage := func(doer *user_model.User, version string, expectedStatus int) { url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version) req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})) AddBasicAuthHeader(req, doer.Name) MakeRequest(t, req, expectedStatus) } t.Run("Common", func(t *testing.T) { defer tests.PrintCurrentTest(t)() // Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload. limitSizeGeneric := setting.Packages.LimitSizeGeneric setting.Packages.LimitTotalOwnerCount = 0 uploadPackage(user, "1.0", http.StatusForbidden) uploadPackage(admin, "1.0", http.StatusCreated) setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount uploadPackage := func(doer *user_model.User, version string, expectedStatus int) { url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version) req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})) AddBasicAuthHeader(req, doer.Name) MakeRequest(t, req, expectedStatus) } setting.Packages.LimitTotalOwnerSize = 0 uploadPackage(user, "1.1", http.StatusForbidden) uploadPackage(admin, "1.1", http.StatusCreated) setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize setting.Packages.LimitTotalOwnerCount = 0 uploadPackage(user, "1.0", http.StatusForbidden) uploadPackage(admin, "1.0", http.StatusCreated) setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount setting.Packages.LimitSizeGeneric = 0 uploadPackage(user, "1.2", http.StatusForbidden) uploadPackage(admin, "1.2", http.StatusCreated) setting.Packages.LimitSizeGeneric = limitSizeGeneric setting.Packages.LimitTotalOwnerSize = 0 uploadPackage(user, "1.1", http.StatusForbidden) uploadPackage(admin, "1.1", http.StatusCreated) setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize setting.Packages.LimitSizeGeneric = 0 uploadPackage(user, "1.2", http.StatusForbidden) uploadPackage(admin, "1.2", http.StatusCreated) setting.Packages.LimitSizeGeneric = limitSizeGeneric }) t.Run("Container", func(t *testing.T) { defer tests.PrintCurrentTest(t)() limitSizeContainer := setting.Packages.LimitSizeContainer uploadBlob := func(doer *user_model.User, data string, expectedStatus int) { url := fmt.Sprintf("/v2/%s/quota-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256([]byte(data))) req := NewRequestWithBody(t, "POST", url, strings.NewReader(data)) AddBasicAuthHeader(req, doer.Name) MakeRequest(t, req, expectedStatus) } setting.Packages.LimitTotalOwnerSize = 0 uploadBlob(user, "2", http.StatusForbidden) uploadBlob(admin, "2", http.StatusCreated) setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize setting.Packages.LimitSizeContainer = 0 uploadBlob(user, "3", http.StatusForbidden) uploadBlob(admin, "3", http.StatusCreated) setting.Packages.LimitSizeContainer = limitSizeContainer }) } func TestPackageCleanup(t *testing.T) {
-
-
-
@@ -11,12 +11,14 @@ "strings""testing" "code.gitea.io/gitea/models" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert"
-
@@ -102,13 +104,28 @@ }return host } func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string, groupMapParams ...string) { func getLDAPServerPort() string { port := os.Getenv("TEST_LDAP_PORT") if len(port) == 0 { port = "389" } return port } func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, groupFilter string, groupMapParams ...string) { groupTeamMapRemoval := "off" groupTeamMap := "" if len(groupMapParams) == 2 { groupTeamMapRemoval = groupMapParams[0] groupTeamMap = groupMapParams[1] } // Modify user filter to test group filter explicitly userFilter := "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))" if groupFilter != "" { userFilter = "(&(objectClass=inetOrgPerson)(uid=%s))" } session := loginUser(t, "user1") csrf := GetCSRF(t, session, "/admin/auths/new") req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{
-
@@ -116,11 +133,11 @@ "_csrf": csrf,"type": "2", "name": "ldap", "host": getLDAPServerHost(), "port": "389", "port": getLDAPServerPort(), "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", "bind_password": "password", "user_base": "ou=people,dc=planetexpress,dc=com", "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", "filter": userFilter, "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", "restricted_filter": "(uid=leela)", "attribute_username": "uid",
-
@@ -133,6 +150,7 @@ "is_active": "on","groups_enabled": "on", "group_dn": "ou=people,dc=planetexpress,dc=com", "group_member_uid": "member", "group_filter": groupFilter, "group_team_map": groupTeamMap, "group_team_map_removal": groupTeamMapRemoval, "user_uid": "DN",
-
@@ -146,7 +164,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "") addAuthSourceLDAP(t, "", "") u := gitLDAPUsers[0]
-
@@ -163,7 +181,7 @@ }func TestLDAPAuthChange(t *testing.T) { defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "") addAuthSourceLDAP(t, "", "") session := loginUser(t, "user1") req := NewRequest(t, "GET", "/admin/auths")
-
@@ -221,7 +239,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "") addAuthSourceLDAP(t, "", "") auth.SyncExternalUsers(context.Background(), true) session := loginUser(t, "user1")
-
@@ -266,13 +284,72 @@ assert.True(t, tr.Length() == 0)} } func TestLDAPUserSyncWithGroupFilter(t *testing.T) { if skipLDAPTests() { t.Skip() return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "", "(cn=git)") // Assert a user not a member of the LDAP group "cn=git" cannot login // This test may look like TestLDAPUserSigninFailed but it is not. // The later test uses user filter containing group membership filter (memberOf) // This test is for the case when LDAP user records may not be linked with // all groups the user is a member of, the user filter is modified accordingly inside // the addAuthSourceLDAP based on the value of the groupFilter u := otherLDAPUsers[0] testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect")) auth.SyncExternalUsers(context.Background(), true) // Assert members of LDAP group "cn=git" are added for _, gitLDAPUser := range gitLDAPUsers { unittest.BeanExists(t, &user_model.User{ Name: gitLDAPUser.UserName, }) } // Assert everyone else is not added for _, gitLDAPUser := range otherLDAPUsers { unittest.AssertNotExistsBean(t, &user_model.User{ Name: gitLDAPUser.UserName, }) } ldapSource := unittest.AssertExistsAndLoadBean(t, &auth_model.Source{ Name: "ldap", }) ldapConfig := ldapSource.Cfg.(*ldap.Source) ldapConfig.GroupFilter = "(cn=ship_crew)" auth_model.UpdateSource(ldapSource) auth.SyncExternalUsers(context.Background(), true) for _, gitLDAPUser := range gitLDAPUsers { if gitLDAPUser.UserName == "fry" || gitLDAPUser.UserName == "leela" || gitLDAPUser.UserName == "bender" { // Assert members of the LDAP group "cn-ship_crew" are still active user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ Name: gitLDAPUser.UserName, }) assert.True(t, user.IsActive, "User %s should be active", gitLDAPUser.UserName) } else { // Assert everyone else is inactive user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ Name: gitLDAPUser.UserName, }) assert.False(t, user.IsActive, "User %s should be inactive", gitLDAPUser.UserName) } } } func TestLDAPUserSigninFailed(t *testing.T) { if skipLDAPTests() { t.Skip() return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "") addAuthSourceLDAP(t, "", "") u := otherLDAPUsers[0] testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect"))
-
@@ -284,7 +361,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "sshPublicKey") addAuthSourceLDAP(t, "sshPublicKey", "") auth.SyncExternalUsers(context.Background(), true)
-
@@ -317,7 +394,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) addAuthSourceLDAP(t, "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) org, err := organization.GetOrgByName("org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
-
@@ -362,7 +439,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) addAuthSourceLDAP(t, "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) org, err := organization.GetOrgByName("org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
-
@@ -398,7 +475,7 @@ t.Skip()return } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`) addAuthSourceLDAP(t, "", "", "on", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`) u := gitLDAPUsers[0]
-
-
-
@@ -154,16 +154,16 @@ assert.NoError(t, err)} } func doGitPushTestRepository(dstPath string, args ...git.CmdArg) func(*testing.T) { func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { _, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"push", "-u"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) _, _, err := git.NewCommand(git.DefaultContext, "push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepositoryFail(dstPath string, args ...git.CmdArg) func(*testing.T) { func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { _, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"push"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) _, _, err := git.NewCommand(git.DefaultContext, "push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.Error(t, err) } }
-
@@ -175,23 +175,23 @@ assert.NoError(t, err)} } func doGitCheckoutBranch(dstPath string, args ...git.CmdArg) func(*testing.T) { func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "checkout"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitMerge(dstPath string, args ...git.CmdArg) func(*testing.T) { func doGitMerge(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { _, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"merge"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) _, _, err := git.NewCommand(git.DefaultContext, "merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPull(dstPath string, args ...git.CmdArg) func(*testing.T) { func doGitPull(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "pull"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } }
-
-
-
@@ -509,7 +509,7 @@ AutodetectManualMerge: &falseBool,})) t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", git.CmdArgCheck(headBranch))) t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) t.Run("CreateEmptyPullRequest", func(t *testing.T) { pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) assert.NoError(t, err)
-
-
-
@@ -187,7 +187,7 @@htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg) assert.EqualValues(t, "Branch 'user1/repo1:feature/test' has been deleted.", resultMsg) }) }
-
@@ -287,7 +287,7 @@assert.NoError(t, err) sha := strings.TrimSpace(stdout.String()) _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo", "100644", git.CmdArgCheck(sha), "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) assert.NoError(t, err) treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path})
-
-
-
@@ -126,7 +126,8 @@ [packages]ENABLED = true [email.incoming] ENABLED = true ; temporarily disabled because the incoming mail tests are flaky due to the IMAP server (during integration tests) couldn't be not ready in time sometimes. ENABLED = false HOST = smtpimap PORT = 993 USERNAME = debug@localdomain.test
-
-
-
@@ -0,0 +1,398 @@<template> <div class="action-view-container"> <div class="action-view-header"> <div class="action-info-summary"> {{ run.title }} <button class="run_cancel" @click="cancelRun()" v-if="run.canCancel"> <i class="stop circle outline icon"/> </button> </div> </div> <div class="action-view-body"> <div class="action-view-left"> <div class="job-group-section"> <div class="job-brief-list"> <a class="job-brief-item" v-for="(job, index) in run.jobs" :key="job.id" :href="run.link+'/jobs/'+index"> <SvgIcon name="octicon-check-circle-fill" class="green" v-if="job.status === 'success'"/> <SvgIcon name="octicon-skip" class="ui text grey" v-else-if="job.status === 'skipped'"/> <SvgIcon name="octicon-clock" class="ui text yellow" v-else-if="job.status === 'waiting'"/> <SvgIcon name="octicon-blocked" class="ui text yellow" v-else-if="job.status === 'blocked'"/> <SvgIcon name="octicon-meter" class="ui text yellow" class-name="job-status-rotate" v-else-if="job.status === 'running'"/> <SvgIcon name="octicon-x-circle-fill" class="red" v-else/> {{ job.name }} <button class="job-brief-rerun" @click="rerunJob(index)" v-if="job.canRerun"> <SvgIcon name="octicon-sync" class="ui text black"/> </button> </a> </div> </div> </div> <div class="action-view-right"> <div class="job-info-header"> <div class="job-info-header-title"> {{ currentJob.title }} </div> <div class="job-info-header-detail"> {{ currentJob.detail }} </div> </div> <div class="job-step-container"> <div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i"> <div class="job-step-summary" @click.stop="toggleStepLogs(i)"> <SvgIcon name="octicon-chevron-down" class="mr-3" v-show="currentJobStepsStates[i].expanded"/> <SvgIcon name="octicon-chevron-right" class="mr-3" v-show="!currentJobStepsStates[i].expanded"/> <SvgIcon name="octicon-check-circle-fill" class="green mr-3" v-if="jobStep.status === 'success'"/> <SvgIcon name="octicon-skip" class="ui text grey mr-3" v-else-if="jobStep.status === 'skipped'"/> <SvgIcon name="octicon-clock" class="ui text yellow mr-3" v-else-if="jobStep.status === 'waiting'"/> <SvgIcon name="octicon-blocked" class="ui text yellow mr-3" v-else-if="jobStep.status === 'blocked'"/> <SvgIcon name="octicon-meter" class="ui text yellow mr-3" class-name="job-status-rotate" v-else-if="jobStep.status === 'running'"/> <SvgIcon name="octicon-x-circle-fill" class="red mr-3 " v-else/> <span class="step-summary-msg">{{ jobStep.summary }}</span> <span class="step-summary-dur">{{ jobStep.duration }}</span> </div> <!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM --> <div class="job-step-logs" ref="logs" v-show="currentJobStepsStates[i].expanded"/> </div> </div> </div> </div> </div> </template> <script> import {SvgIcon} from '../svg.js'; import {createApp} from 'vue'; import AnsiToHTML from 'ansi-to-html'; const {csrfToken} = window.config; const sfc = { name: 'RepoActionView', components: { SvgIcon, }, props: { runIndex: String, jobIndex: String, actionsURL: String, }, data() { return { ansiToHTML: new AnsiToHTML({escapeXML: true}), // internal state loading: false, intervalID: null, currentJobStepsStates: [], // provided by backend run: { link: '', title: '', canCancel: false, done: false, jobs: [ // { // id: 0, // name: '', // status: '', // canRerun: false, // }, ], }, currentJob: { title: '', detail: '', steps: [ // { // summary: '', // duration: '', // status: '', // } ], }, }; }, mounted() { // load job data and then auto-reload periodically this.loadJob(); this.intervalID = setInterval(this.loadJob, 1000); }, methods: { // get the active container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group` getLogsContainer(idx) { const el = this.$refs.logs[idx]; return el._stepLogsActiveContainer ?? el; }, // begin a log group beginLogGroup(idx) { const el = this.$refs.logs[idx]; const elJobLogGroup = document.createElement('div'); elJobLogGroup.classList.add('job-log-group'); const elJobLogGroupSummary = document.createElement('div'); elJobLogGroupSummary.classList.add('job-log-group-summary'); const elJobLogList = document.createElement('div'); elJobLogList.classList.add('job-log-list'); elJobLogGroup.appendChild(elJobLogGroupSummary); elJobLogGroup.appendChild(elJobLogList); el._stepLogsActiveContainer = elJobLogList; }, // end a log group endLogGroup(idx) { const el = this.$refs.logs[idx]; el._stepLogsActiveContainer = null; }, // show/hide the step logs for a step toggleStepLogs(idx) { this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded; if (this.currentJobStepsStates[idx].expanded) { this.loadJob(); // try to load the data immediately instead of waiting for next timer interval } }, // rerun a job rerunJob(idx) { this.fetch(`${this.run.link}/jobs/${idx}/rerun`); }, // cancel a run cancelRun() { this.fetch(`${this.run.link}/cancel`); }, createLogLine(line) { const div = document.createElement('div'); div.classList.add('job-log-line'); div._jobLogTime = line.timestamp; const lineNumber = document.createElement('div'); lineNumber.className = 'line-num'; lineNumber.innerText = line.index; div.appendChild(lineNumber); // TODO: Support displaying time optionally const logMessage = document.createElement('div'); logMessage.className = 'log-msg'; logMessage.innerHTML = this.ansiToHTML.toHtml(line.message); div.appendChild(logMessage); return div; }, appendLogs(stepIndex, logLines) { for (const line of logLines) { // TODO: group support: ##[group]GroupTitle , ##[endgroup] const el = this.getLogsContainer(stepIndex); el.append(this.createLogLine(line)); } }, async fetchJob() { const logCursors = this.currentJobStepsStates.map((it, idx) => { // cursor is used to indicate the last position of the logs // it's only used by backend, frontend just reads it and passes it back, it and can be any type. // for example: make cursor=null means the first time to fetch logs, cursor=eof means no more logs, etc return {step: idx, cursor: it.cursor, expanded: it.expanded}; }); const resp = await this.fetch( `${this.actionsURL}/runs/${this.runIndex}/jobs/${this.jobIndex}`, JSON.stringify({logCursors}), ); return await resp.json(); }, async loadJob() { if (this.loading) return; try { this.loading = true; const response = await this.fetchJob(); // save the state to Vue data, then the UI will be updated this.run = response.state.run; this.currentJob = response.state.currentJob; // sync the currentJobStepsStates to store the job step states for (let i = 0; i < this.currentJob.steps.length; i++) { if (!this.currentJobStepsStates[i]) { this.currentJobStepsStates[i] = {cursor: null, expanded: false}; } } // append logs to the UI for (const logs of response.logs.stepsLog) { // save the cursor, it will be passed to backend next time this.currentJobStepsStates[logs.step].cursor = logs.cursor; this.appendLogs(logs.step, logs.lines); } if (this.run.done && this.intervalID) { clearInterval(this.intervalID); this.intervalID = null; } } finally { this.loading = false; } }, fetch(url, body) { return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Csrf-Token': csrfToken, }, body, }); }, }, }; export default sfc; export function initRepositoryActionView() { const el = document.getElementById('repo-action-view'); if (!el) return; const view = createApp(sfc, { runIndex: el.getAttribute('data-run-index'), jobIndex: el.getAttribute('data-job-index'), actionsURL: el.getAttribute('data-actions-url'), }); view.mount(el); } </script> <style scoped lang="less"> // some elements are not managed by vue, so we need to use _actions.less in addition. .action-view-body { display: flex; height: calc(100vh - 266px); // fine tune this value to make the main view has full height } // ================ // action view header .action-view-header { margin: 0 20px 20px 20px; button.run_cancel { border: none; color: var(--color-red); background-color: transparent; outline: none; cursor: pointer; transition:transform 0.2s; }; button.run_cancel:hover{ transform:scale(130%); }; } .action-info-summary { font-size: 150%; height: 20px; padding: 0 10px; } // ================ // action view left .action-view-left { width: 30%; max-width: 400px; overflow-y: scroll; margin-left: 10px; } .job-group-section { .job-group-summary { margin: 5px 0; padding: 10px; } .job-brief-list { a.job-brief-item { display: block; margin: 5px 0; padding: 10px; background: var(--color-info-bg); border-radius: 5px; text-decoration: none; button.job-brief-rerun { float: right; border: none; background-color: transparent; outline: none; cursor: pointer; transition:transform 0.2s; }; button.job-brief-rerun:hover{ transform:scale(130%); }; } a.job-brief-item:hover { background-color: var(--color-secondary); } } } // ================ // action view right .action-view-right { flex: 1; background-color: var(--color-console-bg); color: var(--color-console-fg); max-height: 100%; margin-right: 10px; display: flex; flex-direction: column; } .job-info-header { .job-info-header-title { font-size: 150%; padding: 10px; } .job-info-header-detail { padding: 0 10px 10px; border-bottom: 1px solid var(--color-grey); } } .job-step-container { max-height: 100%; overflow: auto; .job-step-summary { cursor: pointer; padding: 5px 10px; display: flex; .step-summary-msg { flex: 1; } .step-summary-dur { margin-left: 16px; } } .job-step-summary:hover { background-color: var(--color-black-light); } } </style>
-
-
-
@@ -263,7 +263,7 @@ width: sizes.max.width * factor + 2,height: sizes.max.height * factor + 4 }); const $range = $container.find("input[type='range'"); const $range = $container.find("input[type='range']"); const onInput = () => sizes.image1.parent().css({ opacity: $range.val() / 100 });
-
-
-
@@ -72,6 +72,10 @@ }return filterResult; } export function escapePath(s) { return s.split('/').map(encodeURIComponent).join('/'); } function filterRepoFiles(filter) { const treeLink = $repoFindFileInput.attr('data-url-tree-link'); $repoFindFileTableBody.empty();
-
@@ -83,7 +87,7 @@ $repoFindFileNoResult.toggle(filterResult.length === 0);for (const r of filterResult) { const $row = $(tmplRow); const $a = $row.find('a'); $a.attr('href', `${treeLink}/${r.matchResult.join('')}`); $a.attr('href', `${treeLink}/${escapePath(r.matchResult.join(''))}`); const $octiconFile = $(svg('octicon-file')).addClass('mr-3'); $a.append($octiconFile); // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz']
-
-
-
@@ -1,5 +1,5 @@import {describe, expect, test} from 'vitest'; import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted} from './repo-findfile.js'; import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted, escapePath} from './repo-findfile.js'; describe('Repo Find Files', () => { test('strSubMatch', () => {
-
@@ -31,5 +31,10 @@res = filterRepoFilesWeighted(['we-got-result.dat', 'word.txt'], 'word'); expect(res).toHaveLength(2); expect(res[0].matchResult).toEqual(['', 'word', '.txt']); }); test('escapePath', () => { expect(escapePath('a/b/c')).toEqual('a/b/c'); expect(escapePath('a/b/ c')).toEqual('a/b/%20c'); }); });
-
-
-
@@ -17,12 +17,12 @@export function initRepoReleaseEditor() { const $editor = $('.repository.new.release .content-editor'); if ($editor.length === 0) { return false; return; } (async () => { const $textarea = $editor.find('textarea'); await attachTribute($textarea.get(), {mentions: false, emoji: true}); await attachTribute($textarea.get(), {mentions: true, emoji: true}); const easyMDE = await createCommentEasyMDE($textarea); initCompMarkupContentPreviewTab($editor); const $dropzone = $editor.parent().find('.dropzone');
-
-
-
@@ -14,9 +14,9 @@ }$.getJSON(`${appSubUrl}/user/webauthn/assertion`, {}) .done((makeAssertionOptions) => { makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge); makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge); for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) { makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id); makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id); } navigator.credentials.get({ publicKey: makeAssertionOptions.publicKey
-
@@ -56,14 +56,14 @@ url: `${appSubUrl}/user/webauthn/assertion`,type: 'POST', data: JSON.stringify({ id: assertedCredential.id, rawId: bufferEncode(rawId), rawId: encodeURLEncodedBase64(rawId), type: assertedCredential.type, clientExtensionResults: assertedCredential.getClientExtensionResults(), response: { authenticatorData: bufferEncode(authData), clientDataJSON: bufferEncode(clientDataJSON), signature: bufferEncode(sig), userHandle: bufferEncode(userHandle), authenticatorData: encodeURLEncodedBase64(authData), clientDataJSON: encodeURLEncodedBase64(clientDataJSON), signature: encodeURLEncodedBase64(sig), userHandle: encodeURLEncodedBase64(userHandle), }, }), contentType: 'application/json; charset=utf-8',
-
@@ -85,14 +85,21 @@ }}); } // Encode an ArrayBuffer into a base64 string. function bufferEncode(value) { // Encode an ArrayBuffer into a URLEncoded base64 string. function encodeURLEncodedBase64(value) { return encode(value) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } // Dccode a URLEncoded base64 to an ArrayBuffer string. function decodeURLEncodedBase64(value) { return decode(value .replace(/_/g, '/') .replace(/-/g, '+')); } function webauthnRegistered(newCredential) { const attestationObject = new Uint8Array(newCredential.response.attestationObject); const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
-
@@ -104,11 +111,11 @@ type: 'POST',headers: {'X-Csrf-Token': csrfToken}, data: JSON.stringify({ id: newCredential.id, rawId: bufferEncode(rawId), rawId: encodeURLEncodedBase64(rawId), type: newCredential.type, response: { attestationObject: bufferEncode(attestationObject), clientDataJSON: bufferEncode(clientDataJSON), attestationObject: encodeURLEncodedBase64(attestationObject), clientDataJSON: encodeURLEncodedBase64(clientDataJSON), }, }), dataType: 'json',
-
@@ -184,11 +191,11 @@ name: $('#nickname').val(),}).done((makeCredentialOptions) => { $('#nickname').closest('div.field').removeClass('error'); makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge); makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id); makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge); makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id); if (makeCredentialOptions.publicKey.excludeCredentials) { for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) { makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id); makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id); } }
-
-
-
@@ -90,6 +90,7 @@ import {initRepoCommentForm, initRepository} from './features/repo-legacy.js';import {initFormattingReplacements} from './features/formatting.js'; import {initMcaptcha} from './features/mcaptcha.js'; import {initCopyContent} from './features/copycontent.js'; import {initRepositoryActionView} from './components/RepoActionView.vue'; // Run time-critical code as soon as possible. This is safe to do because this // script appears at the end of <body> and rendered HTML is accessible at that point.
-
@@ -187,6 +188,7 @@ initRepoTemplateSearch();initRepoTopicBar(); initRepoWikiForm(); initRepository(); initRepositoryActionView(); initCommitStatuses(); initMcaptcha();
-
-
-
@@ -25,8 +25,16 @@ import octiconSidebarCollapse from '../../public/img/svg/octicon-sidebar-collapse.svg';import octiconSidebarExpand from '../../public/img/svg/octicon-sidebar-expand.svg'; import octiconTriangleDown from '../../public/img/svg/octicon-triangle-down.svg'; import octiconX from '../../public/img/svg/octicon-x.svg'; import octiconCheckCircleFill from '../../public/img/svg/octicon-check-circle-fill.svg'; import octiconXCircleFill from '../../public/img/svg/octicon-x-circle-fill.svg'; import octiconSkip from '../../public/img/svg/octicon-skip.svg'; import octiconMeter from '../../public/img/svg/octicon-meter.svg'; import octiconBlocked from '../../public/img/svg/octicon-blocked.svg'; import octiconSync from '../../public/img/svg/octicon-sync.svg'; export const svgs = { 'octicon-blocked': octiconBlocked, 'octicon-check-circle-fill': octiconCheckCircleFill, 'octicon-chevron-down': octiconChevronDown, 'octicon-chevron-right': octiconChevronRight, 'octicon-clock': octiconClock,
-
@@ -44,6 +52,7 @@ 'octicon-issue-opened': octiconIssueOpened,'octicon-kebab-horizontal': octiconKebabHorizontal, 'octicon-link': octiconLink, 'octicon-lock': octiconLock, 'octicon-meter': octiconMeter, 'octicon-milestone': octiconMilestone, 'octicon-mirror': octiconMirror, 'octicon-project': octiconProject,
-
@@ -52,8 +61,11 @@ 'octicon-repo-forked': octiconRepoForked,'octicon-repo-template': octiconRepoTemplate, 'octicon-sidebar-collapse': octiconSidebarCollapse, 'octicon-sidebar-expand': octiconSidebarExpand, 'octicon-skip': octiconSkip, 'octicon-sync': octiconSync, 'octicon-triangle-down': octiconTriangleDown, 'octicon-x': octiconX, 'octicon-x-circle-fill': octiconXCircleFill, }; const parser = new DOMParser();
-
-
-
@@ -0,0 +1,43 @@@import "variables.less"; // TODO: the parent element's full height doesn't work well now body > div.full.height { padding-bottom: 0; } .job-status-rotate { animation: job-status-rotate-keyframes 1s linear infinite; } @keyframes job-status-rotate-keyframes { 100% { transform: rotate(360deg); } } .job-step-section { margin: 10px; .job-step-logs { font-family: monospace; .job-log-line { display: flex; .line-num { width: 48px; color: var(--color-grey-light); text-align: right; } .log-time { color: var(--color-grey-light); margin-left: 10px; white-space: nowrap; } .log-msg { flex: 1; word-break: break-all; white-space: break-spaces; margin-left: 10px; } } // TODO: group support } }
-
-
-
@@ -1797,7 +1797,7 @@.ui { &.left, &.right { line-height: 40px; line-height: 39px; // there is a border-top on the footer, so make the line-height 1px less } } }
-
@@ -2200,9 +2200,7 @@ background: var(--color-button);border: 1px solid var(--color-light-border); color: var(--color-text); } .ui.tertiary.button { border: none; } .page-content .ui.button { box-shadow: none !important; }
-
@@ -2317,6 +2315,15 @@ .ui.basic.secondary.button:hover,.ui.basic.secondary.buttons .button:active, .ui.basic.secondary.button:active { color: var(--color-secondary-dark-8) !important; } .ui.tertiary.button { color: var(--color-text-light); border: none; } .ui.tertiary.button:hover { color: var(--color-text); } .ui.primary.label,
-
-
-
@@ -2619,7 +2619,8 @@ }} &.webhooks .list > .item:not(:first-child), &.githooks .list > .item:not(:first-child) { &.githooks .list > .item:not(:first-child), &.runners .list > .item:not(:first-child) { padding: .25rem 1rem; margin: 12px -1rem -1rem; }
-
-
-
@@ -0,0 +1,45 @@@import "variables.less"; .runner-container { padding-bottom: 30px; .runner-ops > a { margin-left: .5em; } .runner-ops-delete { color: var(--color-red-light); } .runner-basic-info .dib { margin-right: 1em; } .runner-status-online { .ui.label; background-color: var(--color-green); color: var(--color-white); } .runner-new-text { color: var(--color-white); } #runner-new:hover .runner-new-text { color: var(--color-white) !important; } .runner-new-menu { width: 300px; } .task-status-success { background-color: var(--color-green); color: var(--color-white); } .task-status-failure { background-color: var(--color-red-light); color: var(--color-white); } .task-status-running { background-color: var(--color-blue); color: var(--color-white); } .task-status-cancelled, .task-status-blocked { background-color: var(--color-yellow); color: var(--color-white); } }
-
-
-
@@ -37,5 +37,7 @@ @import "_admin";@import "_explore"; @import "_review"; @import "_package"; @import "_runner"; @import "_actions"; @import "./helpers.less";
-
-
-
@@ -0,0 +1,20 @@<?xml version="1.0" encoding="UTF-8"?> <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> <path d="M16.559,8.137a7.2,7.2,0,0,0-1.234-1.708,7.586,7.586,0,0,0-.19,2.183,5.161,5.161,0,0,1,1.424-.475Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M13.617,9.466a7.992,7.992,0,0,0-1.993-1.2,8.123,8.123,0,0,0,.885,2.183c0,.063.443-.475,1.108-.981Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M17.445,7.188a9.143,9.143,0,0,1,1.3-2.246A7.585,7.585,0,0,0,17,2.854a8.35,8.35,0,0,0-1.3,2.278,8.451,8.451,0,0,1,1.74,2.056Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="m11.592 11.744a10.276 10.276 0 0 0-2.692-0.158 7.478 7.478 0 0 0 1.93 1.9 6.858 6.858 0 0 1 0.759-1.74z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="m6.878 15.161a7.44 7.44 0 0 1 2.942-1.139 10.019 10.019 0 0 1-2.056-2.278 7.639 7.639 0 0 0-2.847 1.2 7.11 7.11 0 0 0 1.961 2.215z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="m10.516 14.876a6.16 6.16 0 0 0-2.815 0.886 9.936 9.936 0 0 0 2.815 1.2 7.683 7.683 0 0 1 0-2.088z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M14.281,5.543A7.839,7.839,0,0,0,11.592,4.4,8.361,8.361,0,0,0,11.4,7,8.875,8.875,0,0,1,13.87,8.264a10.292,10.292,0,0,1,.411-2.721Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M24.025,3.234a20.488,20.488,0,0,1,.917,4.112,6.823,6.823,0,0,0-3.068,1.519,7.443,7.443,0,0,1,1.55,1.044,1.351,1.351,0,0,0,1.645.316,36.938,36.938,0,0,0,2.721-2.72,1.273,1.273,0,0,0-.159-1.835,20.521,20.521,0,0,0-3.606-2.436Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M4.379,12.06A8.67,8.67,0,0,1,7.226,10.8a7.763,7.763,0,0,1-.759-2.974A14.687,14.687,0,0,0,4.379,12.06Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M11.339,10.668a9.991,9.991,0,0,1-.949-2.784,7.928,7.928,0,0,0-2.911-.126,7.312,7.312,0,0,0,.791,2.879,9.664,9.664,0,0,1,3.069.031Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M6.119,15.73a8.894,8.894,0,0,1-2.025-2.373,14.208,14.208,0,0,0-.063,4.9A8.522,8.522,0,0,1,6.119,15.73Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M22.538,3.487A7.581,7.581,0,0,0,20.323,5.1a11.789,11.789,0,0,1,.823,2.5,9.775,9.775,0,0,1,2.309-1.329,6.593,6.593,0,0,0-.917-2.784Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M19.374,6.3a8.608,8.608,0,0,0-.822,1.676h0a9.645,9.645,0,0,1,1.329.19A7.568,7.568,0,0,0,19.374,6.3Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M19.659,3.9a9.577,9.577,0,0,1,2.056-1.487A15.38,15.38,0,0,0,18.046,2a9.709,9.709,0,0,1,1.613,1.9Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068489px"/> <path d="M27.378,23.892c-1.993-1.9-2.4-3.132-4.081-1.835a7.837,7.837,0,0,1-12.591-4.144A10.179,10.179,0,0,1,6.878,16.3a9.427,9.427,0,0,0-2.562,3.321H4.284C7.163,30.5,21.178,33.035,27.663,26.233,28.739,25.094,27.758,24.3,27.378,23.892ZM6.309,20.855a7.559,7.559,0,0,1,.917-2.025,6.872,6.872,0,0,0,2.151.538c1.013,2.689,4.556,6.264,8.922,6.264a9.632,9.632,0,0,0,6.3-2.309,12.841,12.841,0,0,1,1.772,1.771c.095.127.095.159.095.159C20.7,30.283,10.928,29.555,6.309,20.855Z" fill="#43b02a"/> <path d="M10.67,4.11a19.934,19.934,0,0,0-.214,2.509,10.512,10.512,0,0,0-2.689-.093A18,18,0,0,1,10.67,4.11Z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".066605px"/> <path d="m12.26 3.274a9.107 9.107 0 0 1 2.445 1.053 14.083 14.083 0 0 1 1.253-2.137 12.106 12.106 0 0 0-3.698 1.084z" fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".066605px"/> </svg>
-