Compare commits

..
Author SHA1 Message Date
silverwind ed69a6dc2c Merge remote-tracking branch 'origin/renovate/dependencies' into renovate/dependencies
* origin/renovate/dependencies:
  chore(deps): update dependencies

# Conflicts:
#	go.mod
#	go.sum
#	routers/api/actions/ping/ping.go
#	routers/api/actions/ping/ping_test.go
#	routers/api/actions/runner/runner.go
#	tests/integration/actions_job_test.go
#	tests/integration/actions_log_finalize_test.go
#	tests/integration/actions_runner_test.go
2026-10-19 03:51:51 +02:00
silverwind 68c5cb4a0c refactor(actions): migrate the runner API to connect-go v2
Pin https://gitea.com/gitea/actionslib/pulls/28, which generates
connect-go v2 code. v2 hides plain error messages from clients and caps
messages at 4 MiB. Keep v1's behaviour for runners: they show the messages,
and send all pending log rows in one UpdateLog.

Assisted-by: Claude Code:claude-opus-5-5
2026-10-19 03:49:15 +02:00
silverwind 5ff7fac827 chore: adapt to eslint-plugin-unicorn 78 and kin-openapi 0.150
unicorn 78 now reports code containing comments, which flags the release
tag dropdown loop. Its two new rules find nothing worth enabling. kin-openapi
0.150 keeps the base path as server URL itself and emits explicit style and
explode for array query parameters.

Assisted-by: Claude Code:claude-opus-5-5
2026-10-19 03:49:09 +02:00
Giteabot 12389281f7 chore(deps): update dependencies 2026-10-19 01:46:10 +00:00
Giteabot 8aedd6e634 chore(deps): update dependencies 2026-10-19 01:08:51 +00:00
230 changed files with 3253 additions and 2474 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ runs:
using: composite
steps:
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: 26
- if: ${{ inputs.cache == 'true' }}
+1 -1
View File
@@ -4,7 +4,7 @@ description: Set up uv and python and restore the uv cache
runs:
using: composite
steps:
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
python-version: 3.14
save-cache: ${{ github.workflow == 'cache-seeder' }}
+2 -2
View File
@@ -9,7 +9,7 @@ concurrency:
group: cron-renovate
env:
RENOVATE_VERSION: 44.143.0 # renovate: datasource=npm depName=renovate
RENOVATE_VERSION: 44.155.8 # renovate: datasource=npm depName=renovate
permissions:
contents: read
@@ -21,7 +21,7 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: renovatebot/github-action@230ce922b08968d0a4f6f70f295601daff09ef1d # v46.3.7
- uses: renovatebot/github-action@f0f41e5f2e706165f6814b1d0f0d0e319de7d1d9 # v46.3.8
with:
renovate-version: ${{ env.RENOVATE_VERSION }}
configurationFile: renovate.json5
+1 -1
View File
@@ -223,7 +223,7 @@ jobs:
runs-on: ubuntu-latest
services:
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:ef0b8db33970ecd01bed49c3a84a1d083c435a9891718df619298b67b352e74a
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:07d7dedecdbfcf6776035dc5eefb5bfd9aa1804b2f6b99f7dedb397d8a599069
env:
ACCEPT_EULA: Y
MSSQL_PID: Standard
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.pull_request.base.sha }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: 26
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
+1 -1
View File
@@ -9,7 +9,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.8.0 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.9.0 # renovate: datasource=go
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
+2 -7
View File
@@ -5,8 +5,8 @@
"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 tLine truncated
},
{
"name": "connectrpc.com/connect",
"path": "connectrpc.com/connect/LICENSE",
"name": "connectrpc.com/connect/v2",
"path": "connectrpc.com/connect/v2/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 thiLine truncated
},
{
@@ -889,11 +889,6 @@
"path": "github.com/shopspring/decimal/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Spring, Inc.\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\n- Based on https://github.com/oguzbilgic/fpd, which has the following license:\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2013 Oguz Bilgic\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\"\"\"\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/sorairolake/lzip-go",
"path": "github.com/sorairolake/lzip-go/LICENSE-APACHE",
+2 -3
View File
@@ -23,8 +23,8 @@ import (
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
// Gitea-specific post-processing: server URL, file-schema fixups, URI
// formats, deprecated flags, and shared-enum extraction.
// Gitea-specific post-processing: file-schema fixups, URI formats,
// deprecated flags, and shared-enum extraction.
//
// astEnumMap is a value-set-key → Go-type-name(s) map (built by
// ScanSwaggerEnumTypes). When a value set is shared by multiple Go types,
@@ -42,7 +42,6 @@ func Convert(swaggerJSON []byte, astEnumMap map[string][]string) (*openapi3.T, e
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
}
oas3.Servers = openapi3.Servers{{URL: swagger2.BasePath}}
fixFileSchemas(oas3)
addURIFormats(oas3)
addDeprecatedFlags(oas3)
+3 -7
View File
@@ -1218,9 +1218,8 @@ LEVEL = Info
;; In default merge messages only include approvers who are official
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
;;
;; Set default squash merge commit message body, valid options: PR_BODY (pull request description), COMMIT_MESSAGES (commit messages), BLANK (none)
;; Repositories store the current value when created or when their settings are next saved, and can change it there.
;DEFAULT_SQUASH_MERGE_MESSAGE = PR_BODY
;; In default squash-merge messages include the commit message of all commits comprising the pull request.
;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
;;
;; Add co-authored-by and co-committed-by trailers if committer does not match author
;ADD_CO_COMMITTER_TRAILERS = true
@@ -1434,14 +1433,11 @@ LEVEL = Info
;; The icon theme for folders (basic/material)
;FOLDER_ICON_THEME = basic
;;
;; All available reactions users can choose on issues, pull requests and comments.
;; All available reactions users can choose on issues/prs and comments.
;; Values can be emoji alias (:smile:) or a unicode emoji.
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
;;
;; Reactions available for releases
;;RELEASE_REACTIONS = +1, laugh, hooray, heart, rocket, eyes
;;
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
;REACTION_MAX_USER_NUM = 10
;;
+2
View File
@@ -851,6 +851,7 @@ export default defineConfig([
'unicorn/no-prevent-default-in-passive-listener': [2],
'unicorn/no-process-exit': [0],
'unicorn/no-redundant-comparison': [2],
'unicorn/no-repeated-call-wrappers': [0],
'unicorn/no-return-array-push': [2],
'unicorn/no-selector-as-dom-name': [2],
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
@@ -871,6 +872,7 @@ export default defineConfig([
'unicorn/no-unnecessary-array-splice-count': [2],
'unicorn/no-unnecessary-await': [2],
'unicorn/no-unnecessary-boolean-comparison': [2],
'unicorn/no-unnecessary-default-arguments': [0],
'unicorn/no-unnecessary-fetch-options': [0],
'unicorn/no-unnecessary-global-this': [0],
'unicorn/no-unnecessary-nested-ternary': [2],
+19 -20
View File
@@ -2,13 +2,13 @@ module gitea.dev
go 1.27
toolchain go1.27.3
toolchain go1.27.2
require (
connectrpc.com/connect v1.21.0
connectrpc.com/connect/v2 v2.0.1
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
gitea.dev/actionslib v1.4.0
gitea.dev/actionslib v1.4.2-0.20261019014634-1fd849368a92
gitea.dev/sdk v1.3.0
github.com/42wim/httpsig v1.3.0
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
@@ -27,19 +27,19 @@ require (
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
github.com/chi-middleware/proxy v1.1.1
github.com/coder/websocket v1.8.15
github.com/dlclark/regexp2/v2 v2.8.3
github.com/dlclark/regexp2/v2 v2.8.4
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.1.0
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
github.com/emersion/go-imap v1.2.1
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.10.1
github.com/getkin/kin-openapi v0.149.0
github.com/go-chi/chi/v5 v5.3.2
github.com/getkin/kin-openapi v0.150.0
github.com/go-chi/chi/v5 v5.4.0
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron/v2 v2.22.0
github.com/go-enry/go-enry/v2 v2.9.6
github.com/go-ldap/ldap/v3 v3.4.14
github.com/go-ldap/ldap/v3 v3.4.15
github.com/go-redsync/redsync/v4 v4.18.0
github.com/go-sql-driver/mysql v1.10.1
github.com/go-webauthn/webauthn v0.18.2
@@ -56,7 +56,7 @@ require (
github.com/huandu/xstrings v1.6.2
github.com/jhillyerd/enmime/v2 v2.5.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.20.1
github.com/klauspost/compress v1.20.3
github.com/lib/pq v1.12.3
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.24
@@ -65,13 +65,13 @@ require (
github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.11.2
github.com/minio/minio-go/v7 v7.3.0
github.com/minio/minio-go/v7 v7.4.1
github.com/msteinert/pam/v2 v2.1.0
github.com/niklasfasching/go-org v1.9.1
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.24.1
github.com/prometheus/client_golang v1.25.0
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.23.0
github.com/robfig/cron/v3 v3.0.1
@@ -86,16 +86,16 @@ require (
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.8.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go/v3 v3.16.0
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/mod v0.41.0
golang.org/x/net v0.60.0
golang.org/x/crypto v0.58.0
golang.org/x/image v0.47.0
golang.org/x/mod v0.42.0
golang.org/x/net v0.61.0
golang.org/x/oauth2 v0.37.0
golang.org/x/sync v0.24.0
golang.org/x/sys v0.49.0
golang.org/x/text v0.42.0
golang.org/x/text v0.43.0
google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12
gopkg.in/ini.v1 v1.67.3
@@ -204,14 +204,13 @@ require (
github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/prometheus/client_model v0.6.3 // indirect
github.com/prometheus/common v0.72.0 // indirect
github.com/prometheus/procfs v0.22.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.10.2 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+44 -46
View File
@@ -2,8 +2,8 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
connectrpc.com/connect/v2 v2.0.1 h1:WTAycxe87UqxSM5oxS30d1dalZ+C5xz8Ec8O6zVYkHQ=
connectrpc.com/connect/v2 v2.0.1/go.mod h1:N32OLrstcPUzkykFynnefFro3+xzSdOrZlK2jHmS/xo=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
@@ -12,8 +12,8 @@ gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHq
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.dev/actionslib v1.4.0 h1:k31smj22tnUZrrM5kkntXN8lKAe7dkoTEoiP1Fk5KJc=
gitea.dev/actionslib v1.4.0/go.mod h1:svCefEsavx4jmn8vJ4wTG7Q0KBcGfrFmGUjrhxuNUAQ=
gitea.dev/actionslib v1.4.2-0.20261019014634-1fd849368a92 h1:qjj0mqRCwZ269pJo4LH8e4ePMVK/iWMA0tO6Dxiey+M=
gitea.dev/actionslib v1.4.2-0.20261019014634-1fd849368a92/go.mod h1:NWzFSnEG5EEXaq66Ql3moZ31g4H7+sAmSCyBirjj2R4=
gitea.dev/sdk v1.3.0 h1:4U95+Ou6WHCQiRPDEyAzvl1Q7cxg5RUTWkqmOXr5w4I=
gitea.dev/sdk v1.3.0/go.mod h1:85EBqSAqUn1KlFCk40y3sTjWjBsB/KouL3slGXMA/2o=
github.com/42wim/httpsig v1.3.0 h1:HK2bieeZ99X9IvOoc1ST6yrhLek0f4MqlEGES2JCP3g=
@@ -166,10 +166,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.8.3 h1:4iBHVtmyAsQOOthVo+0v0RxktjLPDOTKx9Nuhf+PpD0=
github.com/dlclark/regexp2/v2 v2.8.3/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.8.4 h1:yfDDXTn4oxyLDC10/ieOBNDCCdIVWS9WbT6ddVXsPKI=
github.com/dlclark/regexp2/v2 v2.8.4/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -194,15 +194,15 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA=
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
github.com/getkin/kin-openapi v0.150.0 h1:7+O1HMsrAB4BevQDElbLAeCAWwyJC/RjQJYw4oosUUU=
github.com/getkin/kin-openapi v0.150.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY=
github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/chi/v5 v5.4.0 h1:+ufzvFTGKyin26Or5DLrlIDq4ygIDBVcj7oVNeeaYFE=
github.com/go-chi/chi/v5 v5.4.0/go.mod h1:d1IG7GtKPSjPyBP365PH8sAGVHk3qfB/IhjFNRH1wWM=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I=
@@ -213,8 +213,8 @@ github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCc
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
github.com/go-ldap/ldap/v3 v3.4.15 h1:eRB4f4Ecn1POL1mO86+uUDlecmyWsdiJwh60bAUS4UY=
github.com/go-ldap/ldap/v3 v3.4.15/go.mod h1:jeD0c98EdZQCKKKKe4kTWs5aCf6y+j8KuYsYLznZSQk=
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
@@ -354,8 +354,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4=
github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
@@ -413,8 +413,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.3.0 h1:HM4pFCSQq/TK+j0/zmorSh5ddh81iDgRgU0BG0Vz/YU=
github.com/minio/minio-go/v7 v7.3.0/go.mod h1:KUPWdecEO1LWyUz+sTGXAuf2jZHrPh5fCsRH86QbPfk=
github.com/minio/minio-go/v7 v7.4.1 h1:PtU/zIOfQRWZ+QydXAZysc+xZYHdG+BBR4Z6TUvy73w=
github.com/minio/minio-go/v7 v7.4.1/go.mod h1:nQnQi7zTfXR4lREtYXZQWqo5aw0Rqv+ZqCGrTORygRk=
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -479,14 +479,14 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM=
github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4=
github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk=
github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=
github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s=
@@ -499,8 +499,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -513,8 +513,6 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@@ -577,8 +575,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
gitlab.com/gitlab-org/api/client-go/v3 v3.16.0 h1:LyxbaO5Pep+gREHptHoYDmYPs4dw9EzGTx6B29+kB7I=
gitlab.com/gitlab-org/api/client-go/v3 v3.16.0/go.mod h1:975X4id//8WM4sQyh9kqODM3fHPHdIRb4Uag2+3GAo8=
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0 h1:Q2odjMtdRLG1HEAlx8mv3A7QoLT6hW7tdttTXLX9Mw0=
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0/go.mod h1:UQl7jIdE6nbYNDtc4XWXeiam0V4MJx459e637POlpnQ=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE=
@@ -606,16 +604,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -624,8 +622,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -657,23 +655,23 @@ golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-1
View File
@@ -439,7 +439,6 @@ func prepareMigrationTasks() []*migration {
newMigration(360, "Move branch protection settings to JSON", v29.MoveBranchProtectionConfigToJSON),
newMigration(361, "Drop legacy commit_id column from comment", v29.DropCommentCommitID),
newMigration(362, "Add reusable_workflow_ref column to action_run_job", v29.AddReusableWorkflowRefToActionRunJob),
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
}
return preparedMigrations
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"gitea.dev/modules/timeutil"
)
func AddReleaseIDToReaction(_ctx context.Context, x base.EngineMigration) error {
// rows older than v123 can hold NULL, which the unique index doesn't treat as a duplicate of ''
if _, err := x.Exec("UPDATE reaction SET original_author = '' WHERE original_author IS NULL"); err != nil {
return err
}
type Reaction struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s) NOT NULL"` // first unique column implies INDEX
IssueID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) NOT NULL"`
CommentID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) INDEX NOT NULL"`
ReleaseID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT(0)"`
OriginalAuthorID int64 `xorm:"UNIQUE(s) NOT NULL DEFAULT(0)"`
OriginalAuthor string `xorm:"UNIQUE(s) NOT NULL DEFAULT('')"`
Type string `xorm:"UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
return x.Sync(new(Reaction))
}
+5 -2
View File
@@ -725,7 +725,10 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
if c.Reactions != nil {
return nil
}
c.Reactions, _, err = FindReactions(ctx, FindReactionsOptions{CommentID: c.ID})
c.Reactions, _, err = FindReactions(ctx, FindReactionsOptions{
IssueID: c.IssueID,
CommentID: c.ID,
})
if err != nil {
return err
}
@@ -1207,7 +1210,7 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
return err
}
return DeleteCommentAllReactions(ctx, comment.ID)
return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID})
}
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
+5 -3
View File
@@ -225,11 +225,13 @@ func (issue *Issue) loadCommentsByType(ctx context.Context, tp CommentType) (err
return err
}
func (issue *Issue) loadAllReactions(ctx context.Context) (err error) {
func (issue *Issue) loadReactions(ctx context.Context) (err error) {
if issue.Reactions != nil {
return nil
}
reactions, _, err := FindReactions(ctx, FindReactionsOptions{IssueIDWithComments: issue.ID})
reactions, _, err := FindReactions(ctx, FindReactionsOptions{
IssueID: issue.ID,
})
if err != nil {
return err
}
@@ -338,7 +340,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
}
}
return issue.loadAllReactions(ctx)
return issue.loadReactions(ctx)
}
// IsPinned returns if a Issue is pinned
+110 -84
View File
@@ -58,20 +58,17 @@ func (err ErrReactionAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// Reaction represents a reaction on an issue, comment or release.
// Reaction represents a reactions on issues and comments.
type Reaction struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s) NOT NULL"` // first unique column implies INDEX
IssueID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) NOT NULL"`
CommentID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) INDEX NOT NULL"`
ReleaseID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT(0)"`
OriginalAuthorID int64 `xorm:"UNIQUE(s) NOT NULL DEFAULT(0)"`
OriginalAuthor string `xorm:"UNIQUE(s) NOT NULL DEFAULT('')"`
Type string `xorm:"UNIQUE(s) NOT NULL"`
User *user_model.User `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
ID int64 `xorm:"pk autoincr"`
Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
CommentID int64 `xorm:"INDEX UNIQUE(s)"`
UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
OriginalAuthor string `xorm:"INDEX UNIQUE(s)"`
User *user_model.User `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
// LoadUser load user of reaction
@@ -111,48 +108,53 @@ func init() {
// FindReactionsOptions describes the conditions to Find reactions
type FindReactionsOptions struct {
db.ListOptions
IssueIDOnly int64
IssueIDWithComments int64
CommentID int64
ReleaseID int64
UserID int64
IssueID int64
CommentID int64
UserID int64
Reaction string
}
func (opts *FindReactionsOptions) toConds() builder.Cond {
// If Issue ID is set add to Query
cond := builder.NewCond()
if opts.IssueIDOnly > 0 {
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueIDOnly}).And(builder.Expr("reaction.comment_id = 0"))
}
if opts.IssueIDWithComments > 0 {
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueIDWithComments})
if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
}
// If CommentID is > 0 add to Query
// If it is 0 Query ignore CommentID to select
// If it is -1 it explicit search of Issue Reactions where CommentID = 0
if opts.CommentID > 0 {
cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
}
if opts.ReleaseID > 0 {
cond = cond.And(builder.Eq{"reaction.release_id": opts.ReleaseID})
} else if opts.CommentID == -1 {
cond = cond.And(builder.Eq{"reaction.comment_id": 0})
}
if opts.UserID > 0 {
cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID, "reaction.original_author_id": 0})
cond = cond.And(builder.Eq{
"reaction.user_id": opts.UserID,
"reaction.original_author_id": 0,
})
}
if !cond.IsValid() {
setting.PanicInDevOrTesting("FindReactionsOptions.toConds: no conditions specified")
cond = cond.And(builder.Expr("1=0"))
if opts.Reaction != "" {
cond = cond.And(builder.Eq{"reaction.type": opts.Reaction})
}
return cond
}
// FindCommentReactions returns a ReactionList of all reactions from a comment
func FindCommentReactions(ctx context.Context, commentID int64) (ReactionList, int64, error) {
return FindReactions(ctx, FindReactionsOptions{CommentID: commentID})
// FindCommentReactions returns a ReactionList of all reactions from an comment
func FindCommentReactions(ctx context.Context, issueID, commentID int64) (ReactionList, int64, error) {
return FindReactions(ctx, FindReactionsOptions{
IssueID: issueID,
CommentID: commentID,
})
}
// FindIssueReactions returns a ReactionList of all reactions from an issue
func FindIssueReactions(ctx context.Context, issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) {
return FindReactions(ctx, FindReactionsOptions{
ListOptions: listOptions,
IssueIDOnly: issueID,
IssueID: issueID,
CommentID: -1,
})
}
@@ -175,14 +177,37 @@ func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList
return reactions, count, err
}
func FindReleaseReactions(ctx context.Context, releaseIDs ...int64) (ReactionList, error) {
reactions := make(ReactionList, 0, 10)
err := db.GetEngine(ctx).
In("release_id", releaseIDs).
In("`type`", setting.UI.ReleaseReactions).
Asc("created_unix", "id").
Find(&reactions)
return reactions, err
func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
reaction := &Reaction{
Type: opts.Type,
UserID: opts.DoerID,
IssueID: opts.IssueID,
CommentID: opts.CommentID,
}
findOpts := FindReactionsOptions{
IssueID: opts.IssueID,
CommentID: opts.CommentID,
Reaction: opts.Type,
UserID: opts.DoerID,
}
if findOpts.CommentID == 0 {
// explicit search of Issue Reactions where CommentID = 0
findOpts.CommentID = -1
}
existingR, _, err := FindReactions(ctx, findOpts)
if err != nil {
return nil, err
}
if len(existingR) > 0 {
return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
}
if err := db.Insert(ctx, reaction); err != nil {
return nil, err
}
return reaction, nil
}
// ReactionOptions defines options for creating or deleting reactions
@@ -191,63 +216,56 @@ type ReactionOptions struct {
DoerID int64
IssueID int64
CommentID int64
ReleaseID int64
}
// CreateReaction creates reaction for issue, comment or release.
func CreateReaction(ctx context.Context, allowed container.Set[string], opts *ReactionOptions) (*Reaction, error) {
if !allowed.Contains(opts.Type) {
// CreateReaction creates reaction for issue or comment.
func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
if !setting.UI.ReactionsLookup.Contains(opts.Type) {
return nil, ErrForbiddenIssueReaction{opts.Type}
}
return db.WithTx2(ctx, func(ctx context.Context) (*Reaction, error) {
return createReaction(ctx, opts)
})
}
// DeleteReaction deletes reaction for issue or comment.
func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
reaction := &Reaction{
Type: opts.Type,
UserID: opts.DoerID,
IssueID: opts.IssueID,
CommentID: opts.CommentID,
ReleaseID: opts.ReleaseID,
}
err := db.Insert(ctx, reaction)
if err != nil {
if has, _ := db.GetEngine(ctx).Get(reaction); has {
return reaction, ErrReactionAlreadyExist{Reaction: opts.Type}
}
return nil, err
sess := db.GetEngine(ctx).Where("original_author_id = 0")
if opts.CommentID == -1 {
reaction.CommentID = 0
sess.MustCols("comment_id")
}
return reaction, nil
_, err := sess.Delete(reaction)
return err
}
// DeleteIssueReaction deletes a reaction on issue.
func DeleteIssueReaction(ctx context.Context, doerID, issueID int64, content string) error {
// here and below: the migration reactions have unrelated user_id (might be the doer?)
// so skip the deletion if the reaction is from an external user (original_author_id != 0)
sess := db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0 AND user_id = ? AND original_author_id = 0 AND `type` = ?", issueID, doerID, content)
_, err := sess.Delete(&Reaction{})
return err
return DeleteReaction(ctx, &ReactionOptions{
Type: content,
DoerID: doerID,
IssueID: issueID,
CommentID: -1,
})
}
// DeleteCommentReaction deletes a reaction on comment.
func DeleteCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) error {
sess := db.GetEngine(ctx).Where("issue_id = ? AND comment_id = ? AND user_id = ? AND original_author_id = 0 AND `type` = ?", issueID, commentID, doerID, content)
_, err := sess.Delete(&Reaction{})
return err
}
func DeleteCommentAllReactions(ctx context.Context, commentID int64) error {
sess := db.GetEngine(ctx).Where("comment_id = ?", commentID)
_, err := sess.Delete(&Reaction{})
return err
}
func DeleteUserReactions(ctx context.Context, userID int64) error {
sess := db.GetEngine(ctx).Where("user_id = ?", userID)
_, err := sess.Delete(&Reaction{})
return err
}
func DeleteReleaseReaction(ctx context.Context, doerID, releaseID int64, content string) error {
sess := db.GetEngine(ctx).Where("release_id = ? AND user_id = ? AND `type` = ?", releaseID, doerID, content)
_, err := sess.Delete(&Reaction{})
return err
return DeleteReaction(ctx, &ReactionOptions{
Type: content,
DoerID: doerID,
IssueID: issueID,
CommentID: commentID,
})
}
// ReactionList represents list of reactions
@@ -284,15 +302,23 @@ func (list ReactionList) getUserIDs() []int64 {
})
}
func valuesUser(m map[int64]*user_model.User) []*user_model.User {
values := make([]*user_model.User, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
// newMigrationOriginalUser creates and returns a fake user for external user
func newMigrationOriginalUser(name string) *user_model.User {
return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)}
}
// LoadUsers loads reactions' all users
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) (map[int64]*user_model.User, error) {
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
if len(list) == 0 {
return map[int64]*user_model.User{}, nil
return nil, nil
}
userIDs := list.getUserIDs()
@@ -313,7 +339,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
reaction.User = user_model.NewGhostUser()
}
}
return userMaps, nil
return valuesUser(userMaps), nil
}
// GetFirstUsers returns first reacted user display names separated by comma
@@ -324,7 +350,7 @@ func (list ReactionList) GetFirstUsers() string {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(reaction.User.DisplayNameWithFullName())
buffer.WriteString(reaction.User.Name)
if rem--; rem == 0 {
break
}
+6 -4
View File
@@ -428,6 +428,9 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
// GetCurrentReview returns the current pending review of reviewer for given issue
func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Issue) (*Review, error) {
if reviewer == nil {
return nil, nil //nolint:nilnil // return nil when reviewer is nil
}
reviews, err := FindReviews(ctx, FindReviewOptions{
Types: []ReviewType{ReviewTypePending},
IssueID: issue.ID,
@@ -439,10 +442,9 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
if len(reviews) == 0 {
return nil, ErrReviewNotExist{}
}
pendingReview := reviews[0]
pendingReview.Reviewer = reviewer
pendingReview.Issue = issue
return pendingReview, nil
reviews[0].Reviewer = reviewer
reviews[0].Issue = issue
return reviews[0], nil
}
// ReviewExists returns whether a review exists for a particular line of code in the PR
-13
View File
@@ -39,19 +39,6 @@ const (
UpdateStyleRebase UpdateStyle = "rebase"
)
// SquashMergeMessage is the source of the default squash merge commit message body, values match GitHub's squash_merge_commit_message: https://docs.github.com/en/rest/repos/repos#update-a-repository
type SquashMergeMessage string
const (
SquashMergeMessageBlank SquashMergeMessage = "BLANK"
SquashMergeMessageCommitMessages SquashMergeMessage = "COMMIT_MESSAGES"
SquashMergeMessagePRBody SquashMergeMessage = "PR_BODY"
)
func (m SquashMergeMessage) IsValid() bool {
return m == SquashMergeMessageBlank || m == SquashMergeMessageCommitMessages || m == SquashMergeMessagePRBody
}
// UpdateDefaultBranch updates the default branch
func UpdateDefaultBranch(ctx context.Context, repo *Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).Cols("default_branch").Update(repo)
-2
View File
@@ -131,7 +131,6 @@ type PullRequestsConfig struct {
DefaultUpdateStyle UpdateStyle
DefaultDeleteBranchAfterMerge bool
DefaultMergeStyle MergeStyle
DefaultSquashMergeMessage SquashMergeMessage
DefaultAllowMaintainerEdit bool
DefaultTargetBranch string
}
@@ -151,7 +150,6 @@ func DefaultPullRequestsConfig() *PullRequestsConfig {
cfg.DefaultDeleteBranchAfterMerge = setting.Repository.PullRequest.DefaultDeleteBranchAfterMerge
cfg.DefaultMergeStyle = MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle)
cfg.DefaultMergeStyle = util.IfZero(cfg.DefaultMergeStyle, MergeStyleMerge)
cfg.DefaultSquashMergeMessage = SquashMergeMessage(setting.Repository.PullRequest.DefaultSquashMergeMessage)
return cfg
}
-3
View File
@@ -112,9 +112,6 @@ func IsWatchingRepo(ctx context.Context, userID, repoID int64) bool {
}
func watchRepoByMode(ctx context.Context, watch *Watch, mode WatchMode) (err error) {
if watch.UserID <= 0 {
return nil
}
if watch.Mode == mode {
return nil
}
+4 -10
View File
@@ -34,7 +34,7 @@ func TestGetWatchers(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
watches, err := repo_model.GetWatchers(t.Context(), repo.ID)
assert.NoError(t, err)
// One watcher is inactive, thus minus 1
// One watchers are inactive, thus minus 1
assert.Len(t, watches, repo.NumWatches-1)
for _, watch := range watches {
assert.Equal(t, repo.ID, watch.RepoID)
@@ -90,30 +90,24 @@ func TestWatchIfAuto(t *testing.T) {
setting.Service.AutoWatchOnChanges = true
// Must not add watch (user 8 has "don't watch" set)
// Must not add watch
assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 8, 1, true))
watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, prevCount)
// Should not add watch (read operation)
// Should not add watch
assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, false))
watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, prevCount)
// Should add watch (write operation)
// Should add watch
assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, true))
watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, prevCount+1)
// Should not add watch (system user should not be able to watch)
assert.NoError(t, repo_model.WatchIfAuto(t.Context(), user_model.ActionsUserID, 1, true))
watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err)
assert.Len(t, watchers, prevCount+1)
// Should remove watch, inhibit from adding auto
assert.NoError(t, repo_model.WatchRepoAuto(t.Context(), user12, repo, false))
watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
-9
View File
@@ -465,7 +465,6 @@ func (u *User) EmailTo() string {
}
// TODO: DefaultShowFullName causes messy logic, there are already too many methods to display a user's "display name", need to refactor them
// Search this TODO to see more details
// * user.Name / user.FullName: directly used in templates
// * user.DisplayName(): always show FullName if it's not empty, otherwise show Name
// * user.GetDisplayName(): show FullName if it's not empty and DefaultShowFullName is set, otherwise show Name
@@ -492,14 +491,6 @@ func (u *User) GetDisplayName() string {
return u.Name
}
// DisplayNameWithFullName returns the username, full name is included if it is not empty
func (u *User) DisplayNameWithFullName() string {
if fullName := strings.TrimSpace(u.FullName); fullName != "" {
return fmt.Sprintf("%s (%s)", u.Name, fullName)
}
return u.Name
}
// ShortName ellipses username to length (still used by many templates), it calls GetDisplayName and respects DEFAULT_SHOW_FULL_NAME
func (u *User) ShortName(length int) string {
return util.EllipsisDisplayString(u.GetDisplayName(), length)
+11 -6
View File
@@ -295,13 +295,18 @@ func TestNewGitSig(t *testing.T) {
}
func TestDisplayName(t *testing.T) {
u := &user_model.User{Name: "name"}
assert.Equal(t, "name", u.DisplayName())
assert.Equal(t, "name", u.DisplayNameWithFullName())
users := make([]*user_model.User, 0, 20)
err := db.GetEngine(t.Context()).Find(&users)
assert.NoError(t, err)
u = &user_model.User{Name: "name", FullName: "Full Name"}
assert.Equal(t, "Full Name", u.DisplayName())
assert.Equal(t, "name (Full Name)", u.DisplayNameWithFullName())
for _, user := range users {
displayName := user.DisplayName()
assert.Equal(t, strings.TrimSpace(displayName), displayName)
if len(strings.TrimSpace(user.FullName)) == 0 {
assert.Equal(t, user.Name, displayName)
}
assert.NotEmpty(t, strings.TrimSpace(displayName))
}
}
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
+2 -11
View File
@@ -7,28 +7,19 @@ import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"strings"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/setting"
)
func LineBlameCommitID(ctx context.Context, repo RepositoryFacade, revision, file string, line uint) (string, error) {
func LineBlame(ctx context.Context, repo RepositoryFacade, revision, file string, line uint) (string, error) {
stdout, _, err := gitcmd.NewCommand("blame").WithRepo(repo).
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
AddDashesAndList(file).
RunStdString(ctx)
if err != nil {
return "", err
}
commitID, _, _ := strings.Cut(stdout, " ")
if !IsStringValidObjectID(nil, commitID) {
return "", fmt.Errorf("LineBlameCommitID: invalid commit ID %q", commitID)
}
return commitID, nil
return stdout, err
}
// BlamePart represents block of blame - continuous lines with one sha
-12
View File
@@ -11,18 +11,6 @@ import (
"github.com/stretchr/testify/assert"
)
func TestLineBlameCommitID(t *testing.T) {
storage := mockRepository("repo5_pulls")
const revision = "f32b0a9dfd09a60f616f29158f772cedd89942d2"
commitID, err := LineBlameCommitID(t.Context(), storage, revision, "README.md", 1)
assert.NoError(t, err)
assert.Equal(t, "72866af952e98d02a73003501836074b286a78f6", commitID)
commitID, err = LineBlameCommitID(t.Context(), storage, revision, "missing.txt", 1)
assert.Error(t, err)
assert.Empty(t, commitID)
}
func TestReadingBlameOutput(t *testing.T) {
setting.AppDataPath = t.TempDir()
ctx := t.Context()
+14 -6
View File
@@ -46,13 +46,21 @@ func GetDivergingCommits(ctx context.Context, repo RepositoryFacade, baseBranch,
// GetCommitIDsBetweenReverse returns the last commit IDs between two commits in reverse order (from old to new) with limit.
// If the result exceeds the limit, the old commits IDs will be ignored
func GetCommitIDsBetweenReverse(ctx context.Context, repo RepositoryFacade, startRef, endRef, notRef string, limit int) ([]string, error) {
cmd := gitcmd.NewCommand("rev-list", "--reverse").
AddArguments("-n").AddDynamicArguments(strconv.Itoa(limit)).
AddDynamicArguments(startRef + ".." + endRef)
if notRef != "" { // --not should be kept as the last parameter of git command, otherwise the result will be wrong
cmd.AddOptionValues("--not", notRef)
genCmd := func(reversions ...string) *gitcmd.Command {
cmd := gitcmd.NewCommand("rev-list", "--reverse").
AddArguments("-n").AddDynamicArguments(strconv.Itoa(limit)).
AddDynamicArguments(reversions...)
if notRef != "" { // --not should be kept as the last parameter of git command, otherwise the result will be wrong
cmd.AddOptionValues("--not", notRef)
}
return cmd
}
stdout, _, err := genCmd(startRef + ".." + endRef).WithRepo(repo).RunStdString(ctx)
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
// if the start and end are not related (no merge base), just get all commits pushed by "end ref"
// previously it would return the results of git rev-list before last so let's try that...
stdout, _, err = genCmd(endRef).WithRepo(repo).RunStdString(ctx)
}
stdout, _, err := cmd.WithRepo(repo).RunStdString(ctx)
if err != nil {
return nil, err
}
+17 -13
View File
@@ -5,7 +5,6 @@ package git
import (
"bufio"
"cmp"
"context"
"fmt"
"io"
@@ -201,18 +200,22 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
}
if old {
begin, _ = strconv.ParseInt(groups["beginOld"], 10, 64)
end, _ = strconv.ParseInt(cmp.Or(groups["endOld"], "1"), 10, 64)
end, _ = strconv.ParseInt(groups["endOld"], 10, 64)
// init otherLine with begin of opposite side
otherLine, _ = strconv.ParseInt(groups["beginNew"], 10, 64)
} else {
begin, _ = strconv.ParseInt(groups["beginNew"], 10, 64)
end, _ = strconv.ParseInt(cmp.Or(groups["endNew"], "1"), 10, 64)
if groups["endNew"] != "" {
end, _ = strconv.ParseInt(groups["endNew"], 10, 64)
} else {
end = 0
}
// init otherLine with begin of opposite side
otherLine, _ = strconv.ParseInt(groups["beginOld"], 10, 64)
}
end += begin // end is for real only the number of lines in hunk
// lof is between begin and end
if begin <= line && line < end {
if begin <= line && end >= line {
hunk = append(hunk, lof)
currentLine = begin
continue
@@ -262,26 +265,27 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
oldBegin = otherLine
newBegin = currentLine
}
// headers + hunk header
newHunk := make([]string, headerLines)
// transfer existing headers
copy(newHunk, hunk[:headerLines])
// transfer last n lines
newHunk = append(newHunk, hunk[len(hunk)-numbersOfLine-1:]...)
// calculate newBegin, ... by counting lines
start := len(hunk)
for count := 0; count < numbersOfLine && start > headerLines+1; {
start--
switch hunk[start][0] {
for i := len(hunk) - 1; i >= len(hunk)-numbersOfLine; i-- {
switch hunk[i][0] {
case '+':
newBegin--
newNumOfLines++
case '-':
oldBegin--
oldNumOfLines++
case '\\':
continue
default:
oldBegin--
newBegin--
newNumOfLines++
oldNumOfLines++
}
count++
}
// "git diff" outputs "@@ -1 +1,3 @@" for "OLD" => "A\nB\nC"
@@ -290,9 +294,9 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
// For example: "L1\nL2" => "A\nB", then the patch shows "L2" as line 1 on the left (deleted part)
// construct the new hunk header
hunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
return strings.Join(append(hunk[:headerLines+1], hunk[start:]...), "\n"), nil
return strings.Join(newHunk, "\n"), nil
}
// GetAffectedFiles returns the affected files between two commits
+1 -9
View File
@@ -120,18 +120,10 @@ func TestCutDiffAroundLine(t *testing.T) {
assert.Empty(t, emptyResult)
// Line is out of scope
emptyResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 7, false, 3)
emptyResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
assert.NoError(t, err)
assert.Empty(t, emptyResult)
noNewlineDiff := "diff --git a/a b/a\n--- a/a\n+++ b/a\n@@ -1 +1 @@\n-a\n\\ No newline at end of file\n+a"
result, err = CutDiffAroundLine(strings.NewReader(noNewlineDiff), 1, false, 2)
assert.NoError(t, err)
assert.Equal(t, "diff --git a/a b/a\n--- a/a\n+++ b/a\n@@ -1,1 +1,1 @@\n-a\n\\ No newline at end of file\n+a", result)
result, err = CutDiffAroundLine(strings.NewReader(noNewlineDiff), 1, true, 2)
assert.NoError(t, err)
assert.Equal(t, "diff --git a/a b/a\n--- a/a\n+++ b/a\n@@ -1 +1 @@\n-a", result)
// Handle minus diffs properly
minusDiff, err := CutDiffAroundLine(strings.NewReader(breakingDiff), 2, false, 4)
assert.NoError(t, err)
+3 -7
View File
@@ -68,12 +68,12 @@ func DefaultFeatures() *Features {
func loadGitVersionFeatures() (*Features, error) {
stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background())
if runErr != nil {
return nil, fmt.Errorf(`failed to run command "git version": %w`, runErr)
return nil, runErr
}
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
if err != nil {
return nil, fmt.Errorf(`failed to parse output of command "git version": %w`, err)
return nil, err
}
features := &Features{gitVersion: ver}
@@ -104,11 +104,7 @@ func parseGitVersionLine(s string) (*version.Version, error) {
if len(versionFields) > 3 {
versionFields = versionFields[:3]
}
ver, err := version.NewVersion(strings.Join(versionFields, "."))
if err != nil {
return nil, fmt.Errorf("invalid git version: %q: %w", s, err)
}
return ver, nil
return version.NewVersion(strings.Join(versionFields, "."))
}
func checkGitVersionCompatibility(gitVer *version.Version) error {
+5 -2
View File
@@ -93,12 +93,15 @@ const (
StderrPathSpec StderrPrefix = "fatal: pathspec"
StderrBadRevision StderrPrefix = "fatal: bad revision"
StderrNoSuchPath StderrPrefix = "fatal: no such path"
StderrBadObject StderrPrefix = "fatal: bad object "
StderrNoSuchRemote StderrPrefix = "error: no such remote"
StderrAuthenticationFailed StderrPrefix = "fatal: Authentication failed for"
StderrCouldNotReadUsername StderrPrefix = "fatal: could not read Username"
StderrFileNoEnoughLines StderrRegexp = `^fatal: file .* has only \d+ lines?`
StderrUnknownRevisionOrPath StderrRegexp = "^fatal: .*: unknown revision or path not in the working tree"
StderrNoMergeBase StderrRegexp = "^fatal: .*: no merge base"
StderrFileNoEnoughLines StderrRegexp = `^fatal: file .* has only \d+ lines?`
)
func matchStderrCheck(stderr string, checkIntf StderrCheck) (match bool) {
+2
View File
@@ -14,6 +14,8 @@ func TestIsStderr(t *testing.T) {
check StderrCheck
stderr string
}{
{StderrUnknownRevisionOrPath, "fatal: ambiguous argument 'origin': unknown revision or path not in the working tree...."},
{StderrNoMergeBase, "fatal: origin/main..HEAD: no merge base...."},
{StderrFileNoEnoughLines, "fatal: file foo/bar has only 1 line"},
{StderrAuthenticationFailed, "Cloning into 'repo'...\r\nremote: Invalid username or token.\r\nfatal: Authentication failed for 'https://host/repo.git/'\r\n"},
}
+2 -2
View File
@@ -64,9 +64,9 @@ func (err *ErrInvalidCloneAddr) Unwrap() error {
return util.ErrInvalidArgument
}
// IsRemoteNotExistError checks whether a "git remote" subcommand failed because the remote does not exist
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
func IsRemoteNotExistError(err error) bool {
return gitcmd.IsErrorExitCode(err, 2)
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote)
}
// ParseRemoteAddr checks if given remote address is valid,
+18 -9
View File
@@ -317,19 +317,28 @@ func (repo *Repository) CommitsByFileAndRange(ctx context.Context, opts CommitsB
// CommitsBetween returns a list that contains commits between [after, before). After is the first item in the slice.
// If "before" and "after" are not related, it returns the all commits for the "after" commit.
func (repo *Repository) CommitsBetween(ctx context.Context, afterRef, beforeRef RefName, limit int, optSkip ...int) ([]*Commit, error) {
cmd := gitcmd.NewCommand("rev-list").WithRepo(repo)
if limit >= 0 {
cmd.AddOptionValues("--max-count", strconv.Itoa(limit))
}
if len(optSkip) > 0 {
cmd.AddOptionValues("--skip", strconv.Itoa(optSkip[0]))
gitCmd := func() *gitcmd.Command {
cmd := gitcmd.NewCommand("rev-list").WithRepo(repo)
if limit >= 0 {
cmd.AddOptionValues("--max-count", strconv.Itoa(limit))
}
if len(optSkip) > 0 {
cmd.AddOptionValues("--skip", strconv.Itoa(optSkip[0]))
}
return cmd
}
var stdout []byte
var err error
if beforeRef == "" {
cmd.AddDynamicArguments(afterRef.String())
stdout, _, err = gitCmd().AddDynamicArguments(afterRef.String()).RunStdBytes(ctx)
} else {
cmd.AddDynamicArguments(beforeRef.String() + ".." + afterRef.String())
stdout, _, err = gitCmd().AddDynamicArguments(beforeRef.String() + ".." + afterRef.String()).RunStdBytes(ctx)
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// if the beforeRef and afterRef are not related (no merge base), just get all commits pushed by afterRef
stdout, _, err = gitCmd().AddDynamicArguments(afterRef.String()).RunStdBytes(ctx)
}
}
stdout, _, err := cmd.RunStdBytes(ctx)
if err != nil {
return nil, err
}
+5
View File
@@ -46,6 +46,11 @@ func (repo *Repository) GetDiffNumChangedFiles(ctx context.Context, base, head s
WithRepo(repo).
WithStdoutCopy(w).
RunWithStderr(ctx); err != nil {
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
// git >= 2.28 now returns an error if base and head have become unrelated.
// it doesn't make sense to count the changed files in this case because UI won't display such diff
return 0, nil
}
return 0, err
}
return w.numLines, nil
+24 -10
View File
@@ -83,7 +83,7 @@ type Tree struct {
}
// ListEntries returns all entries of current tree.
func (t *Tree) ListEntries(_ctx context.Context, gitRepo *Repository) (Entries, error) {
func (t *Tree) ListEntries(ctx context.Context, gitRepo *Repository) (Entries, error) {
if t.entriesParsed {
return t.entries, nil
}
@@ -109,22 +109,36 @@ func (t *Tree) ListEntries(_ctx context.Context, gitRepo *Repository) (Entries,
return nil, err
}
}
if info.Type != "tree" { // resolve annotated tags to their tree, a blob yields ErrNotExist
if err := DiscardFull(rd, info.Size+1); err != nil {
return nil, err
}
info, rd, err = batch.QueryContent(t.ID.String() + "^{tree}")
if info.Type == "tree" {
t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size)
if err != nil {
return nil, err
}
t.entriesParsed = true
return t.entries, nil
}
t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size)
if err != nil {
// Not a tree just use ls-tree instead
if err := DiscardFull(rd, info.Size+1); err != nil {
return nil, err
}
t.entriesParsed = true
return t.entries, nil
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithRepo(gitRepo).RunStdBytes(ctx)
if runErr != nil {
if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) {
return nil, ErrNotExist{
ID: t.ID.String(),
}
}
return nil, runErr
}
t.entries, err = parseTreeEntries(stdout, t)
if err == nil {
t.entriesParsed = true
}
return t.entries, err
}
// listEntriesRecursive returns all entries of current tree recursively including all subtrees
-19
View File
@@ -8,7 +8,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSubTree_Issue29101(t *testing.T) {
@@ -27,24 +26,6 @@ func TestSubTree_Issue29101(t *testing.T) {
}
}
func TestTreeListEntriesResolvesTagAndRejectsBlob(t *testing.T) {
repo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
require.NoError(t, err)
defer repo.Close()
entries, err := newTree(MustIDFromString("3ad28a9149a2864384548f3d17ed7f38014c9e8a")).ListEntries(t.Context(), repo)
require.NoError(t, err)
names := make([]string, 0, len(entries))
for _, entry := range entries {
names = append(names, entry.Name())
}
assert.Equal(t, []string{"file1.txt", "file2.txt", "foo"}, names)
assert.True(t, entries[2].IsDir())
_, err = newTree(MustIDFromString("e2129701f1a4d54dc44f03c93bca0a2aec7c5449")).ListEntries(t.Context(), repo)
assert.True(t, IsErrNotExist(err))
}
func Test_GetTreePathLatestCommit(t *testing.T) {
repo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo6_blame"))
assert.NoError(t, err)
-17
View File
@@ -8,8 +8,6 @@ import (
"compress/gzip"
"io"
"regexp"
"slices"
"strings"
"sync"
"gitea.dev/modules/packages"
@@ -26,8 +24,6 @@ var (
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
// ErrInvalidVersion indicates an invalid version in the metadata.gz file
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
// ErrInvalidMetadata indicates metadata that would corrupt the generated index
ErrInvalidMetadata = util.NewInvalidArgumentErrorf("package metadata is invalid")
)
var globalVars = sync.OnceValue(func() (ret struct {
@@ -225,19 +221,6 @@ func parseMetadataFile(r io.Reader) (*Package, error) {
}
}
values := []string{m.Platform}
reqs := slices.Concat(m.RequiredRubyVersion, m.RequiredRubygemsVersion)
for _, dep := range slices.Concat(m.RuntimeDependencies, m.DevelopmentDependencies) {
values = append(values, dep.Name)
reqs = append(reqs, dep.Version...)
}
for _, req := range reqs {
values = append(values, req.Restriction, req.Version)
}
if slices.ContainsFunc(values, func(s string) bool { return strings.ContainsAny(s, "\r\n") }) {
return nil, ErrInvalidMetadata // the compact index is line-based, a line break would forge entries
}
return &Package{
Name: spec.Name,
Version: spec.Version.Version,
@@ -43,19 +43,6 @@ version:
assert.Nil(t, rp)
}
})
t.Run("LineBreakInIndexedMetadata", func(t *testing.T) {
for _, field := range []string{
`platform: "ruby\nx"`,
`dependencies: [{name: "d\nx"}]`,
`dependencies: [{name: d, requirement: {requirements: [[">=", {version: "1\nx"}]]}}]`,
`required_ruby_version: {requirements: [[">=", {version: "1\nx"}]]}`,
} {
rp, err := parseMetadataFile(test.CompressGzip("name: g\nversion:\n version: 1\n" + field + "\n"))
assert.ErrorIs(t, err, ErrInvalidMetadata, field)
assert.Nil(t, rp)
}
})
}
func TestParseMetadataFile(t *testing.T) {
-64
View File
@@ -1,64 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"strconv"
"gitea.dev/modules/util"
)
type deprecatedKey struct {
oldSection, oldKey, newSection, newKey string
convert func(old ConfigKey) string
}
func invertBool(old ConfigKey) string {
return strconv.FormatBool(!old.MustBool())
}
// keep rows, users upgrading from older versions still need them
var deprecatedKeys = []deprecatedKey{
{"server", "LFS_CONTENT_PATH", "lfs", "PATH", nil},
{"server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", nil},
{"server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", nil},
{"server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", nil},
{"server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", nil},
{"mailer", "MAILER_TYPE", "mailer", "PROTOCOL", func(old ConfigKey) string { return old.In("", []string{"sendmail"}) }},
{"mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", func(old ConfigKey) string { return util.Iif(old.MustBool(), "smtps", "smtp+starttls") }},
{"mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", invertBool},
{"mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", nil},
{"mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", nil},
{"mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", nil},
{"mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", nil},
{"mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", invertBool},
{"repository", "DISABLE_MIRRORS", "mirror", "DISABLE_NEW_PULL", nil},
{"git.reflog", "ENABLED", "git.config", "core.logAllRefUpdates", func(old ConfigKey) string { return old.In("true", []string{"true", "false"}) }},
{"git.reflog", "EXPIRATION", "git.config", "gc.reflogExpire", func(old ConfigKey) string { return old.String() + ".days" }},
{"oauth2", "ENABLE", "oauth2", "ENABLED", nil},
{"cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", nil},
{"repository.pull-request", "POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES", "repository.pull-request", "DEFAULT_SQUASH_MERGE_MESSAGE", func(old ConfigKey) string { return util.Iif(old.MustBool(), "COMMIT_MESSAGES", "PR_BODY") }},
}
// applyDeprecatedKeys copies each deprecated key of the table to its replacement unless the replacement is present
func applyDeprecatedKeys(rootCfg ConfigProvider) {
for _, deprecated := range deprecatedKeys {
oldSec, _ := rootCfg.GetSection(deprecated.oldSection)
if oldSec == nil || !oldSec.HasKey(deprecated.oldKey) {
continue
}
deprecatedSetting(rootCfg, deprecated.oldSection, deprecated.oldKey, deprecated.newSection, deprecated.newKey)
if newSec, _ := rootCfg.GetSection(deprecated.newSection); newSec != nil && newSec.HasKey(deprecated.newKey) {
continue
}
oldKey := oldSec.Key(deprecated.oldKey)
value := oldKey.String()
if deprecated.convert != nil {
value = deprecated.convert(oldKey)
}
if value != "" {
_, _ = rootCfg.Section(deprecated.newSection).NewKey(deprecated.newKey, value)
}
}
}
+2 -2
View File
@@ -392,9 +392,9 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
StartupProblems = append(StartupProblems, msg)
}
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey string) {
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead", oldSection, oldKey, newSection, newKey)
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
}
}
+10
View File
@@ -88,6 +88,16 @@ func loadGitFrom(rootCfg ConfigProvider) {
GitConfig.SetOption("diff.algorithm", "histogram")
GitConfig.SetOption("core.logAllRefUpdates", "true")
secGitReflog := rootCfg.Section("git.reflog")
if secGitReflog.HasKey("ENABLED") {
deprecatedSetting(rootCfg, "git.reflog", "ENABLED", "git.config", "core.logAllRefUpdates", "1.21")
GitConfig.SetOption("core.logAllRefUpdates", secGitReflog.Key("ENABLED").In("true", []string{"true", "false"}))
}
if secGitReflog.HasKey("EXPIRATION") {
deprecatedSetting(rootCfg, "git.reflog", "EXPIRATION", "git.config", "gc.reflogExpire", "1.21")
GitConfig.SetOption("gc.reflogExpire", secGitReflog.Key("EXPIRATION").String()+".days")
}
for _, key := range secGitConfig.Keys() {
GitConfig.SetOption(key.Name(), key.String())
}
-1
View File
@@ -56,7 +56,6 @@ ENABLED = false
EXPIRATION = 123
`)
assert.NoError(t, err)
applyDeprecatedKeys(cfg)
loadGitFrom(cfg)
assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates"))
+12
View File
@@ -40,6 +40,18 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
lfsSec, _ := rootCfg.GetSection("lfs")
// Specifically default PATH to LFS_CONTENT_PATH
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
if val := sec.Key("LFS_CONTENT_PATH").String(); val != "" {
if lfsSec == nil {
lfsSec = rootCfg.Section("lfs")
}
lfsSec.Key("PATH").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT
}
var err error
LFS.Storage, err = getStorage(rootCfg, "lfs", "", lfsSec)
if err != nil {
-2
View File
@@ -29,7 +29,6 @@ PATH = path_used
`
cfg, err = NewConfigProviderFromData(iniStr)
assert.NoError(t, err)
applyDeprecatedKeys(cfg)
assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "local", LFS.Storage.Type)
@@ -41,7 +40,6 @@ LFS_CONTENT_PATH = deprecatedpath
`
cfg, err = NewConfigProviderFromData(iniStr)
assert.NoError(t, err)
applyDeprecatedKeys(cfg)
assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "local", LFS.Storage.Type)
+6 -6
View File
@@ -59,8 +59,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) {
sec.Key("logger.default.MODE").MustString(",") // FIXME: INI-MUST-SIDE-EFFECT
}
deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE")
deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE")
deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE", "1.21")
deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE", "1.21")
if val := sec.Key("ACCESS").String(); val != "" {
sec.Key("logger.access.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT
}
@@ -68,8 +68,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) {
sec.Key("logger.access.MODE").SetValue("")
}
deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE")
deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE")
deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE", "1.21")
deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE", "1.21")
if val := sec.Key("ROUTER").String(); val != "" {
sec.Key("logger.router.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT
}
@@ -81,8 +81,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) {
sec.Key("logger.router.MODE").SetValue("")
}
deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE")
deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE")
deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE", "1.21")
deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE", "1.21")
if val := sec.Key("XORM").String(); val != "" {
sec.Key("logger.xorm.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT
}
+48 -2
View File
@@ -76,7 +76,14 @@ func loadMailerFrom(rootCfg ConfigProvider) {
// Handle Deprecations and map on to new configuration
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR")
deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0")
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
if sec.Key("MAILER_TYPE").String() == "sendmail" {
sec.Key("PROTOCOL").MustString("sendmail") // FIXME: INI-MUST-SIDE-EFFECT
}
}
deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0")
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
givenHost := sec.Key("HOST").String()
addr, port, err := net.SplitHostPort(givenHost)
@@ -92,8 +99,47 @@ func loadMailerFrom(rootCfg ConfigProvider) {
sec.Key("SMTP_PORT").MustString(port) // FIXME: INI-MUST-SIDE-EFFECT
}
deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0")
if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
if sec.Key("IS_TLS_ENABLED").MustBool() {
sec.Key("PROTOCOL").MustString("smtps") // FIXME: INI-MUST-SIDE-EFFECT
} else {
sec.Key("PROTOCOL").MustString("smtp+starttls") // FIXME: INI-MUST-SIDE-EFFECT
}
}
deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0")
if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT
}
deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0")
if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT
}
deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0")
if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT
}
deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0")
if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String())
}
deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0")
if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String())
}
deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0")
if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)) // FIXME: INI-MUST-SIDE-EFFECT
}
if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" {
log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls` instead.")
log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0")
sec.Key("PROTOCOL").SetValue("smtp+starttls")
}
+3 -3
View File
@@ -27,9 +27,9 @@ func loadMigrationsFrom(rootCfg ConfigProvider) {
egressModeSet := sec.HasKey("EGRESS_MODE")
Migrations.EgressMode = normalizePolicyMode(sec.Key("EGRESS_MODE").String())
deprecatedSetting(rootCfg, "migrations", "ALLOWED_DOMAINS", "migrations", "ALLOWED_HOST_LIST")
deprecatedSetting(rootCfg, "migrations", "BLOCKED_DOMAINS", "migrations", "BLOCKED_HOST_LIST")
deprecatedSetting(rootCfg, "migrations", "ALLOW_LOCALNETWORKS", "migrations", "ALLOWED_HOST_LIST")
deprecatedSetting(rootCfg, "migrations", "ALLOWED_DOMAINS", "migrations", "ALLOWED_HOST_LIST", "v28.0.0")
deprecatedSetting(rootCfg, "migrations", "BLOCKED_DOMAINS", "migrations", "BLOCKED_HOST_LIST", "v28.0.0")
deprecatedSetting(rootCfg, "migrations", "ALLOW_LOCALNETWORKS", "migrations", "ALLOWED_HOST_LIST", "v28.0.0")
Migrations.AllowedHostList = ConfigSectionKeyString(sec, "ALLOWED_HOST_LIST")
if Migrations.AllowedHostList == "" {
var hosts []string
+9
View File
@@ -25,6 +25,15 @@ var Mirror = struct {
}
func loadMirrorFrom(rootCfg ConfigProvider) {
// Handle old configuration through `[repository]` `DISABLE_MIRRORS`
// - please note this was badly named and only disabled the creation of new pull mirrors
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED", "v1.19.0")
if ConfigSectionKeyBool(rootCfg.Section("repository"), "DISABLE_MIRRORS") {
Mirror.DisableNewPull = true
}
if err := rootCfg.Section("mirror").MapTo(&Mirror); err != nil {
log.Fatal("Failed to map Mirror settings: %v", err)
}
+6
View File
@@ -122,6 +122,12 @@ func loadOAuth2From(rootCfg ConfigProvider) {
OAuth2.DefaultApplications = nil
}
// Handle the rename of ENABLE to ENABLED
deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0")
if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") {
OAuth2.Enabled = sec.Key("ENABLE").MustBool(OAuth2.Enabled)
}
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
}
+3 -8
View File
@@ -6,7 +6,6 @@ package setting
import (
"os/exec"
"path/filepath"
"slices"
"strings"
"gitea.dev/modules/log"
@@ -95,7 +94,7 @@ var (
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
DefaultSquashMergeMessage string
PopulateSquashCommentWithCommitMessages bool
AddCoCommitterTrailers bool
RetargetChildrenOnMerge bool
DelayCheckForInactiveDays int
@@ -189,7 +188,7 @@ var (
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
DefaultSquashMergeMessage string
PopulateSquashCommentWithCommitMessages bool
AddCoCommitterTrailers bool
RetargetChildrenOnMerge bool
DelayCheckForInactiveDays int
@@ -207,7 +206,7 @@ var (
DefaultMergeMessageAllAuthors: false,
DefaultMergeMessageMaxApprovers: 10,
DefaultMergeMessageOfficialApproversOnly: true,
DefaultSquashMergeMessage: "PR_BODY",
PopulateSquashCommentWithCommitMessages: false,
AddCoCommitterTrailers: true,
RetargetChildrenOnMerge: true,
DelayCheckForInactiveDays: 7,
@@ -340,10 +339,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
log.Fatal("Failed to map Repository.PullRequest settings: %v", err)
}
if !slices.Contains([]string{"BLANK", "COMMIT_MESSAGES", "PR_BODY"}, Repository.PullRequest.DefaultSquashMergeMessage) { // uppercase unlike other config values, to match GitHub's squash_merge_commit_message which the API also uses
log.Fatal("Invalid repository.pull-request.DEFAULT_SQUASH_MERGE_MESSAGE: %s", Repository.PullRequest.DefaultSquashMergeMessage)
}
if !rootCfg.Section("packages").Key("ENABLED").MustBool(Packages.Enabled) {
Repository.DisabledRepoUnits = append(Repository.DisabledRepoUnits, "repo.packages")
}
+4
View File
@@ -180,6 +180,10 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
deprecatedSetting(rootCfg, "cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", "v1.26.0")
if !sec.HasKey("X_FRAME_OPTIONS") {
Security.XFrameOptions = rootCfg.Section("cors").Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions)
}
if err := sec.MapTo(&Security); err != nil {
log.Fatal("Failed to map security settings: %v", err)
}
+28 -4
View File
@@ -151,7 +151,14 @@ func loadServerFrom(rootCfg ConfigProvider) {
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
if sec.HasKey("ENABLE_ACME") {
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
} else {
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
}
protocolCfg := sec.Key("PROTOCOL").String()
if protocolCfg != "https" && EnableAcme {
@@ -168,12 +175,29 @@ func loadServerFrom(rootCfg ConfigProvider) {
AcmeProfile = sec.Key("ACME_PROFILE").MustString("")
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false)
if sec.HasKey("ACME_ACCEPTTOS") {
AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false)
} else {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0")
AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
}
if !AcmeTOS {
log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).")
}
AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https")
AcmeEmail = sec.Key("ACME_EMAIL").MustString("")
if sec.HasKey("ACME_DIRECTORY") {
AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https")
} else {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0")
AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
}
if sec.HasKey("ACME_EMAIL") {
AcmeEmail = sec.Key("ACME_EMAIL").MustString("")
} else {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
}
} else {
CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String()
+1 -1
View File
@@ -158,7 +158,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
Service.RegisterManualConfirm = false
}
if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST")
deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")
}
Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST")
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
-1
View File
@@ -117,7 +117,6 @@ func LoadCommonSettings() {
func loadCommonSettingsFrom(cfg ConfigProvider) error {
// a lot of logic depends on InstallLock value, so it must be loaded before any other settings
InstallLock = HasInstallLock(cfg)
applyDeprecatedKeys(cfg)
// WARNING: don't change the sequence except you know what you are doing.
loadRunModeFrom(cfg)
+3 -3
View File
@@ -10,9 +10,9 @@ func loadTaskFrom(rootCfg ConfigProvider) {
taskSec := rootCfg.Section("task")
queueTaskSec := rootCfg.Section("queue.task")
deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE")
deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR")
deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH")
deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE", "v1.19.0")
deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR", "v1.19.0")
deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH", "v1.19.0")
switch taskSec.Key("QUEUE_TYPE").MustString("channel") {
case "channel":
+4 -6
View File
@@ -30,9 +30,7 @@ var UI = struct {
FileIconTheme string
FolderIconTheme string
Reactions []string
ReleaseReactions []string
ReactionsLookup container.Set[string] `ini:"-"`
ReleaseReactionsLookup container.Set[string] `ini:"-"`
CustomEmojis []string
CustomEmojisMap map[string]string `ini:"-"`
EnabledEmojis []string
@@ -100,7 +98,6 @@ var UI = struct {
FileIconTheme: `material`,
FolderIconTheme: `basic`,
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
ReleaseReactions: []string{`+1`, `laugh`, `hooray`, `heart`, `rocket`, `eyes`},
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
ExploreDefaultSort: "recentupdate",
@@ -173,9 +170,10 @@ func loadUIFrom(rootCfg ConfigProvider) {
// because many private repositories do not have "description/topic", users just want to search by their names.
UI.OnlyShowRelevantRepos = sec.Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false)
UI.ReactionsLookup = container.SetOf(UI.Reactions...)
UI.ReleaseReactionsLookup = container.SetOf(UI.ReleaseReactions...)
UI.ReactionsLookup = make(container.Set[string])
for _, reaction := range UI.Reactions {
UI.ReactionsLookup.Add(reaction)
}
UI.CustomEmojisMap = make(map[string]string)
for _, emoji := range UI.CustomEmojis {
UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
+1 -1
View File
@@ -35,7 +35,7 @@ func loadWebhookFrom(rootCfg ConfigProvider) {
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
deprecatedSetting(rootCfg, "webhook", "ALLOWED_HOST_LIST", "security", "ALLOWED_HOST_LIST")
deprecatedSetting(rootCfg, "webhook", "ALLOWED_HOST_LIST", "security", "ALLOWED_HOST_LIST", "v28.0.0")
Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString(Security.AllowedHostList)
if Webhook.AllowedHostList != Security.AllowedHostList {
checkHostList("[webhook] ALLOWED_HOST_LIST", Webhook.AllowedHostList, false)
-3
View File
@@ -120,7 +120,6 @@ type Repository struct {
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
DefaultMergeStyle string `json:"default_merge_style"`
DefaultUpdateStyle string `json:"default_update_style"`
SquashMergeCommitMessage string `json:"squash_merge_commit_message"`
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"`
@@ -237,8 +236,6 @@ type EditRepoOption struct {
DefaultMergeStyle *string `json:"default_merge_style,omitempty"`
// set to an update style to be used by this repository: "merge" or "rebase".
DefaultUpdateStyle *string `json:"default_update_style,omitempty"`
// set the default squash merge commit message body: "PR_BODY", "COMMIT_MESSAGES", or "BLANK".
SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"`
// set to `true` to allow edits from maintainers by default
DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"`
// set to `true` to archive this repository.
+3
View File
@@ -93,6 +93,9 @@ func newFuncMapWebPage() template.FuncMap {
"ShowFooterPoweredBy": func() bool {
return setting.Other.ShowFooterPoweredBy
},
"AllowedReactions": func() []string {
return setting.UI.Reactions
},
"MetaAuthor": func() string {
return setting.UI.Meta.Author
},
-4
View File
@@ -2148,10 +2148,6 @@
"repo.settings.pulls.allow_merge_update": "Enable updating pull request branch by merge",
"repo.settings.pulls.allow_rebase_update": "Enable updating pull request branch by rebase",
"repo.settings.pulls.default_update_style": "Default branch update style",
"repo.settings.pulls.default_squash_merge_message": "Default squash merge commit message",
"repo.settings.pulls.default_squash_merge_message.title": "Pull request title",
"repo.settings.pulls.default_squash_merge_message.title_and_commit_details": "Pull request title and commit details",
"repo.settings.pulls.default_squash_merge_message.title_and_description": "Pull request title and description",
"repo.settings.pulls.default_target_branch": "Default target branch for new pull requests",
"repo.settings.pulls.default_target_branch_default": "Default branch (%s)",
"repo.settings.pulls.default_delete_branch_after_merge": "Delete pull request branch after merge by default",
+6 -7
View File
@@ -602,7 +602,7 @@
"user.followers": "abonnés",
"user.show_more": "Voir plus",
"user.starred": "Dépôts favoris",
"user.watched": "Dépôts observés",
"user.watched": "Dépôts surveillés",
"user.code": "Code",
"user.projects": "Projets",
"user.overview": "Vue d'ensemble",
@@ -929,7 +929,6 @@
"settings.delete_account_title": "Supprimer ce compte d’utilisateur",
"settings.delete_account_desc": "Êtes-vous sûr de vouloir supprimer définitivement ce compte d’utilisateur ?",
"settings.email_notifications.enable": "Notifier par courriel",
"settings.email_notifications.onmention_and_custom": "Courriel uniquement sur Mentions et Observations personnalisées",
"settings.email_notifications.disable": "Ne pas notifier",
"settings.email_notifications.submit": "Définir les préférences de courriel",
"settings.email_notifications.andyourown": "Inclure vos propres notifications",
@@ -1142,10 +1141,10 @@
"repo.generated_from": "généré depuis",
"repo.fork_from_self": "Vous ne pouvez pas bifurquer un dépôt que vous possédez.",
"repo.fork_guest_user": "Connectez-vous pour bifurquer ce dépôt.",
"repo.watch_guest_user": "Connectez-vous pour observer ce dépôt.",
"repo.watch_guest_user": "Connectez-vous pour consulter ce dépôt.",
"repo.star_guest_user": "Connectez-vous pour ajouter aux favoris ce dépôt.",
"repo.watch": "Observer",
"repo.watching": "Observé",
"repo.watch": "Suivre",
"repo.watching": "Suivi",
"repo.ignoring": "Ignoré",
"repo.watch.options.issues": "Tickets",
"repo.watch.options.pull_requests": "Demandes d'ajout",
@@ -2512,7 +2511,7 @@
"repo.settings.visibility.private.text": "Rendre le dépôt privé rendra non seulement le dépôt visible uniquement aux membres autorisés, mais peut également rompre la relation entre lui et ses bifurcations, observateurs, et favoris.",
"repo.settings.visibility.private.bullet_title": "<strong>Changer la visibilité en privé :</strong>",
"repo.settings.visibility.private.bullet_one": "Rendra le dépôt visible uniquement aux membres autorisés.",
"repo.settings.visibility.private.bullet_two": "Applique la visibilité aux bifurcations et retire les <strong>observateurs</strong> et les <strong>favoris</strong>.",
"repo.settings.visibility.private.bullet_two": "Applique la visibilité aux bifurcation et retire les <strong>observateurs</strong> et les <strong>favoris</strong>.",
"repo.settings.visibility.private.stats_stars": "Il y a <strong>%d</strong> favori(s) sur ce dépôt qui pourrai(en)t être perdu(s).",
"repo.settings.visibility.private.stats_watchers": "Il y a <strong>%d</strong> observateur(s) sur ce dépôt qui pourrai(en)t être perdu(s).",
"repo.settings.visibility.private.stats_forks": "Il y a <strong>%d</strong> bifurcation(s) associée(s) à ce dépôt.",
@@ -3549,7 +3548,7 @@
"notification.mark_all_as_read": "Tout marquer comme lu",
"notification.mark_page_as_read": "Marquer cette page comme lue",
"notification.subscriptions": "Abonnements",
"notification.watching": "Observé",
"notification.watching": "Suivi",
"notification.no_subscriptions": "Pas d'abonnements",
"gpg.default_key": "Signé avec la clé par défaut",
"gpg.error.extract_sign": "Impossible d'extraire la signature",
-3
View File
@@ -926,7 +926,6 @@
"settings.delete_account_title": "Scrios Cuntas Úsáide",
"settings.delete_account_desc": "An bhfuil tú cinnte gur mhaith leat an cuntas úsáideora seo a scriosadh go buan?",
"settings.email_notifications.enable": "Cumasaigh Fógraí Ríomhphoist",
"settings.email_notifications.onmention_and_custom": "Ríomhphost amháin nuair a dhéantar tagairt, agus uaireadóirí saincheaptha",
"settings.email_notifications.disable": "Díchumasaigh Fógraí Ríomhphoist",
"settings.email_notifications.submit": "Socraigh rogha ríomhphoist",
"settings.email_notifications.andyourown": "Agus Do Fógraí Féin",
@@ -3988,8 +3987,6 @@
"actions.general.token_permissions.maximum.description": "Beidh ceadanna éifeachtacha an phoist gníomhartha teoranta ag na ceadanna uasta.",
"actions.general.token_permissions.fork_pr_note": "Mura gcuirtear tús le post trí iarratas tarraingthe ó fhorc, ní rachaidh a cheadanna éifeachtacha thar na ceadanna léite amháin.",
"actions.general.token_permissions.customize_max_permissions": "Saincheap na ceadanna uasta",
"actions.general.token_permissions.id_token": "Comhartha OpenID Connect",
"actions.general.token_permissions.id_token.description": "Ceadaigh do phoist a dhearbhaíonn \"id-token: write\" comharthaí OpenID Connect gearrshaolacha a iarraidh chun fíordheimhniú a dhéanamh i gcoinne seirbhísí seachtracha.",
"actions.general.cross_repo": "Rochtain Tras-Stórtha",
"actions.general.cross_repo_desc": "Ceadaigh rochtain (léamh amháin) a bheith ag na stórtha uile san úinéir seo ar na stórtha roghnaithe le GITEA_TOKEN agus poist Gníomhartha á reáchtáil.",
"actions.general.cross_repo_selected": "Stórtha roghnaithe",
-11
View File
@@ -87,7 +87,6 @@
"add_all": "すべて追加",
"dismiss": "閉じる",
"remove": "除去",
"rename": "名前を変更",
"remove_all": "すべて除去",
"remove_label_str": "アイテム「%s」を削除",
"edit": "編集",
@@ -907,7 +906,6 @@
"settings.webauthn_desc": "セキュリティキーは暗号化キーを内蔵するハードウェア ・ デバイスです。 2要素認証に使用できます。 セキュリティキーは<a rel=\"noreferrer\" target=\"_blank\" href=\"%s\">WebAuthn Authenticator</a>規格をサポートしている必要があります。",
"settings.webauthn_register_key": "セキュリティキーを追加",
"settings.webauthn_nickname": "ニックネーム",
"settings.webauthn_nickname_been_used": "同じニックネームのシークレットキーが既に存在しています。",
"settings.webauthn_delete_key": "セキュリティキーの登録解除",
"settings.webauthn_delete_key_desc": "セキュリティキーの登録を解除すると、今後そのセキュリティキーでサインインすることはできなくなります。 続行しますか?",
"settings.webauthn_key_loss_warning": "セキュリティキーを紛失すると、アカウントへのアクセスを失います。",
@@ -2298,7 +2296,6 @@
"repo.settings.event_release_desc": "リポジトリでリリースが作成・更新・削除されたとき。",
"repo.settings.event_push": "プッシュ",
"repo.settings.event_force_push": "強制プッシュ",
"repo.settings.event_branch_delete": "ブランチの抹消",
"repo.settings.event_push_desc": "Gitがリポジトリにプッシュを行ったとき。",
"repo.settings.event_repository": "リポジトリ",
"repo.settings.event_repository_desc": "リポジトリが作成・削除・名前変更されたとき。",
@@ -2410,12 +2407,6 @@
"repo.settings.protect_enable_force_push_all_desc": "プッシュ権限を持つ人は誰でも、このブランチに強制プッシュを行うことができます。",
"repo.settings.protect_enable_force_push_allowlist": "許可リストで強制プッシュを制限",
"repo.settings.protect_enable_force_push_allowlist_desc": "許可リストに登録された、プッシュ権限を持つユーザーまたはチームのみが、このブランチに強制プッシュを行うことができます。",
"repo.settings.protect_disable_deletion": "ブランチの抹消を無効化",
"repo.settings.protect_disable_deletion_desc": "このブランチではブランチ抹消は許可されません。",
"repo.settings.protect_enable_deletion_all": "ブランチの抹消を有効化",
"repo.settings.protect_enable_deletion_all_desc": "プッシュ権限を持つ人は誰でも、このブランチの抹消を行うことができます。",
"repo.settings.protect_enable_deletion_allowlist": "許可リストでブランチ抹消を制限",
"repo.settings.protect_enable_deletion_allowlist_desc": "許可リストに登録された、プッシュ権限を持つユーザーまたはチームのみが、このブランチの抹消を行うことができます。",
"repo.settings.protect_enable_merge": "マージ有効",
"repo.settings.protect_enable_merge_desc": "書き込み権限を持つ人は誰でも、このブランチにプルリクエストをマージすることができます。",
"repo.settings.protect_whitelist_committers": "許可リストでプッシュを制限",
@@ -2426,8 +2417,6 @@
"repo.settings.protect_force_push_allowlist_users": "強制プッシュ許可リストに含むユーザー:",
"repo.settings.protect_force_push_allowlist_teams": "強制プッシュ許可リストに含むチーム:",
"repo.settings.protect_force_push_allowlist_deploy_keys": "強制プッシュ可能なプッシュ権限を持つデプロイキーを許可リストに含める。",
"repo.settings.protect_deletion_allowlist_users": "抹消許可リストに含むユーザー:",
"repo.settings.protect_deletion_allowlist_teams": "抹消許可リストに含むチーム:",
"repo.settings.protect_merge_whitelist_committers": "マージ許可リストを有効にする",
"repo.settings.protect_merge_whitelist_committers_desc": "許可リストに登録したユーザーまたはチームにのみ、このブランチへのプルリクエストのマージを許可します。",
"repo.settings.protect_merge_whitelist_users": "マージ許可リストに含むユーザー:",
-3
View File
@@ -929,7 +929,6 @@
"settings.delete_account_title": "Eliminar conta de utilizador",
"settings.delete_account_desc": "Tem a certeza que quer eliminar permanentemente esta conta de utilizador?",
"settings.email_notifications.enable": "Habilitar notificações por email",
"settings.email_notifications.onmention_and_custom": "Email somente em vigilâncias de menções e personalizadas",
"settings.email_notifications.disable": "Desabilitar notificações por email",
"settings.email_notifications.submit": "Definir preferência do email",
"settings.email_notifications.andyourown": "e as suas próprias notificações",
@@ -4001,8 +4000,6 @@
"actions.general.token_permissions.maximum.description": "As permissões concretas para o trabalho das operações serão limitadas pelas permissões máximas.",
"actions.general.token_permissions.fork_pr_note": "Se o trabalho for iniciado por um pedido de integração de uma derivação, as suas permissões concretas não irão exceder as permissões apenas de leitura.",
"actions.general.token_permissions.customize_max_permissions": "Personalizar as permissões máximas",
"actions.general.token_permissions.id_token": "Código OpenID Connect",
"actions.general.token_permissions.id_token.description": "Permitir que trabalhos que declaram \"id-token: write\" solicitem códigos OpenID Connect de curta duração para se autenticarem em serviços externos.",
"actions.general.cross_repo": "Acesso entre repositórios",
"actions.general.cross_repo_desc": "Permitir que os repositórios escolhidos sejam acedidos (somente leitura) por todos os repositórios deste proprietário com GITEA_TOKEN ao executar os trabalhos das operações.",
"actions.general.cross_repo_selected": "Repositórios escolhidos",
-12
View File
@@ -929,7 +929,6 @@
"settings.delete_account_title": "删除当前帐户",
"settings.delete_account_desc": "确定要永久删除此用户帐户吗?",
"settings.email_notifications.enable": "启用邮件通知",
"settings.email_notifications.onmention_and_custom": "仅在被提及或发生自定义关注事件时发送邮件",
"settings.email_notifications.disable": "停用邮件通知",
"settings.email_notifications.submit": "设置邮件通知",
"settings.email_notifications.andyourown": "同时接收由自己操作触发的通知",
@@ -2299,7 +2298,6 @@
"repo.settings.event_release_desc": "发布、更新或删除版本时。",
"repo.settings.event_push": "推送",
"repo.settings.event_force_push": "强制推送",
"repo.settings.event_branch_delete": "分支删除",
"repo.settings.event_push_desc": "Git 仓库推送",
"repo.settings.event_repository": "仓库",
"repo.settings.event_repository_desc": "仓库被创建、删除或重命名。",
@@ -2411,12 +2409,6 @@
"repo.settings.protect_enable_force_push_all_desc": "任何拥有推送权限的人都可以对这个分支进行强制推送。",
"repo.settings.protect_enable_force_push_allowlist": "强制推送白名单",
"repo.settings.protect_enable_force_push_allowlist_desc": "只有具有推送权限且列入白名单的用户或团队,才能强制推送到此分支。",
"repo.settings.protect_disable_deletion": "禁止删除分支",
"repo.settings.protect_disable_deletion_desc": "不允许删除此分支。",
"repo.settings.protect_enable_deletion_all": "允许删除分支",
"repo.settings.protect_enable_deletion_all_desc": "任何拥有推送权限的人都可以删除此分支。",
"repo.settings.protect_enable_deletion_allowlist": "分支删除白名单",
"repo.settings.protect_enable_deletion_allowlist_desc": "只有具有推送权限且列入白名单的用户或团队,才能删除此分支。",
"repo.settings.protect_enable_merge": "启用合并",
"repo.settings.protect_enable_merge_desc": "任何具有写入权限的人都可以将合并请求合并到此分支中。",
"repo.settings.protect_whitelist_committers": "受白名单限制的推送",
@@ -2427,8 +2419,6 @@
"repo.settings.protect_force_push_allowlist_users": "允许强制推送的白名单用户:",
"repo.settings.protect_force_push_allowlist_teams": "允许强制推送的白名单团队:",
"repo.settings.protect_force_push_allowlist_deploy_keys": "允许具有推送权限的白名单部署密钥进行强制推送。",
"repo.settings.protect_deletion_allowlist_users": "删除白名单用户:",
"repo.settings.protect_deletion_allowlist_teams": "删除白名单团队:",
"repo.settings.protect_merge_whitelist_committers": "启用合并白名单",
"repo.settings.protect_merge_whitelist_committers_desc": "仅允许白名单用户或团队合并合并请求到此分支。",
"repo.settings.protect_merge_whitelist_users": "合并白名单用户:",
@@ -4001,8 +3991,6 @@
"actions.general.token_permissions.maximum.description": "工作流任务的实际权限将受到最大权限的限制。",
"actions.general.token_permissions.fork_pr_note": "如果任务是由来自派生仓库的合并请求触发的,那么它的实际权限不会超过只读权限。",
"actions.general.token_permissions.customize_max_permissions": "自定义最大权限",
"actions.general.token_permissions.id_token": "OpenID Connect 令牌",
"actions.general.token_permissions.id_token.description": "允许声明了「id-token: write」的作业请求短期有效的 OpenID Connect 令牌,以向外部服务进行身份验证。",
"actions.general.cross_repo": "跨仓库访问",
"actions.general.cross_repo_desc": "允许此所有者下的所有仓库在运行工作流任务时,通过 GITEA_TOKEN 以只读方式访问所选仓库。",
"actions.general.cross_repo_selected": "选择的仓库",
+16 -16
View File
@@ -1,6 +1,6 @@
{
"type": "module",
"packageManager": "pnpm@12.10.1",
"packageManager": "pnpm@12.13.0",
"engines": {
"node": ">= 22.18.0",
"pnpm": ">= 11.0.0"
@@ -29,7 +29,7 @@
"@replit/codemirror-lang-svelte": "6.0.0",
"@replit/codemirror-vscode-keymap": "6.0.2",
"@resvg/resvg-wasm": "2.6.2",
"@vitejs/plugin-vue": "6.0.9",
"@vitejs/plugin-vue": "6.0.10",
"asciinema-player": "3.17.0",
"chart.js": "4.5.1",
"chartjs-plugin-zoom": "2.2.0",
@@ -46,46 +46,46 @@
"jquery": "4.0.0",
"js-yaml": "5.4.3",
"katex": "0.19.0",
"mermaid": "12.1.0",
"mermaid": "12.2.0",
"online-3d-viewer": "0.18.0",
"pdfobject": "2.3.1",
"postcss": "8.5.29",
"postcss": "8.5.30",
"rolldown-license-plugin": "3.0.14",
"sortablejs": "1.15.7",
"swagger-ui-dist": "5.33.1",
"swagger-ui-dist": "5.33.2",
"tailwindcss": "3.4.19",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"vanilla-colorful": "0.7.2",
"vite": "8.3.3",
"vite": "8.3.4",
"vite-string-plugin": "2.0.7",
"vue": "3.5.43"
"vue": "3.5.44"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
"@eslint/json": "2.1.1",
"@playwright/test": "1.63.0",
"@playwright/test": "1.64.0",
"@stylistic/eslint-plugin": "5.10.0",
"@stylistic/stylelint-plugin": "5.3.1",
"@types/codemirror": "5.60.18",
"@types/jquery": "4.0.1",
"@types/katex": "0.16.8",
"@types/node": "26.6.4",
"@types/node": "26.6.5",
"@types/pdfobject": "2.2.5",
"@types/sortablejs": "1.15.9",
"@types/swagger-ui-dist": "3.30.6",
"@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.71.1",
"@vitejs/plugin-vue": "6.0.9",
"@vitest/browser-playwright": "5.0.3",
"@typescript-eslint/parser": "8.71.2",
"@vitejs/plugin-vue": "6.0.10",
"@vitest/browser-playwright": "5.0.4",
"@vitest/eslint-plugin": "1.6.27",
"eslint": "10.12.0",
"eslint-import-resolver-typescript": "4.4.5",
"eslint-plugin-import-x": "4.17.1",
"eslint-plugin-playwright": "2.12.1",
"eslint-plugin-regexp": "3.3.1",
"eslint-plugin-unicorn": "77.0.0",
"eslint-plugin-unicorn": "78.0.0",
"eslint-plugin-vue": "10.11.1",
"eslint-plugin-vue-scoped-css": "3.1.3",
"eslint-plugin-wc": "3.1.0",
@@ -103,9 +103,9 @@
"stylelint-value-no-unknown-custom-properties": "6.1.1",
"svgo": "4.1.0",
"typescript": "6.0.3",
"typescript-eslint": "8.71.1",
"vitest": "5.0.3",
"typescript-eslint": "8.71.2",
"vitest": "5.0.4",
"vue-eslint-parser": "10.4.1",
"vue-tsc": "3.3.12"
"vue-tsc": "3.3.13"
}
}
+461 -403
View File
File diff suppressed because it is too large. Load diff
+15 -9
View File
@@ -12,11 +12,16 @@ import (
"gitea.dev/actionslib/ping/v1/pingv1connect"
"gitea.dev/modules/log"
"connectrpc.com/connect"
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
)
func NewPingServiceHandler() (string, http.Handler) {
return pingv1connect.NewPingServiceHandler(&Service{})
server := connect.NewServer()
pingv1connect.RegisterPingServiceHandler(server, &Service{})
mux := http.NewServeMux()
connecthttp.Mount(mux, server)
return "/" + pingv1connect.PingServiceName + "/", mux
}
var _ pingv1connect.PingServiceHandler = (*Service)(nil)
@@ -25,12 +30,13 @@ type Service struct{}
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),
})
req *pingv1.PingRequest,
) (*pingv1.PingResponse, error) {
info, _ := connect.CallInfoForServerContext(ctx)
log.Trace("Content-Type: %s", info.RequestHeader().Get("Content-Type"))
log.Trace("User-Agent: %s", info.RequestHeader().Get("User-Agent"))
res := &pingv1.PingResponse{
Data: fmt.Sprintf("Hello, %s!", req.Data),
}
return res, nil
}
+15 -17
View File
@@ -11,17 +11,15 @@ import (
pingv1 "gitea.dev/actionslib/ping/v1"
"gitea.dev/actionslib/ping/v1/pingv1connect"
"connectrpc.com/connect"
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
"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)
_, handler := NewPingServiceHandler()
MainServiceTest(t, handler)
}
func MainServiceTest(t *testing.T, h http.Handler) {
@@ -31,31 +29,31 @@ func MainServiceTest(t *testing.T, h http.Handler) {
server.StartTLS()
defer server.Close()
connectClient := pingv1connect.NewPingServiceClient(
connectClient := pingv1connect.NewPingServiceClient(connect.NewClient(connecthttp.NewTransport(
server.Client(),
server.URL,
)
)))
grpcClient := pingv1connect.NewPingServiceClient(
grpcClient := pingv1connect.NewPingServiceClient(connect.NewClient(connecthttp.NewTransport(
server.Client(),
server.URL,
connect.WithGRPC(),
)
connecthttp.WithGRPC(),
)))
grpcWebClient := pingv1connect.NewPingServiceClient(
grpcWebClient := pingv1connect.NewPingServiceClient(connect.NewClient(connecthttp.NewTransport(
server.Client(),
server.URL,
connect.WithGRPCWeb(),
)
connecthttp.WithGRPCWeb(),
)))
clients := []pingv1connect.PingServiceClient{connectClient, grpcClient, grpcWebClient}
t.Run("ping request", func(t *testing.T) {
for _, client := range clients {
result, err := client.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{
result, err := client.Ping(t.Context(), &pingv1.PingRequest{
Data: "foobar",
}))
})
require.NoError(t, err)
assert.Equal(t, "Hello, foobar!", result.Msg.Data)
assert.Equal(t, "Hello, foobar!", result.Data)
}
})
}
+27 -15
View File
@@ -16,7 +16,7 @@ import (
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"connectrpc.com/connect"
"connectrpc.com/connect/v2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -26,27 +26,39 @@ const (
tokenHeaderKey = protocol.TokenHeader
)
var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unaryFunc connect.UnaryFunc) connect.UnaryFunc {
// withErrorMessages sends plain errors to runners as CodeUnknown with their message, like connect v1 did.
func withErrorMessages(next connect.ServerFunc) connect.ServerFunc {
return func(ctx context.Context, spec connect.Spec, stream connect.ServerStream) error {
err := next(ctx, spec, stream)
if _, ok := errors.AsType[*connect.Error](err); err == nil || ok {
return err
}
return connect.NewError(connect.CodeUnknown, err.Error()).WithCause(err)
}
}
func withRunner(next connect.ServerFunc) connect.ServerFunc {
// A plain gRPC status.Error is treated as unknown by Connect and becomes HTTP 500
// To respond an HTTP status code, use connect.Error instead
errUnregisteredRunner := connect.NewError(connect.CodeUnauthenticated, errors.New("unregistered runner"))
return func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) {
methodName := getMethodName(request)
errUnregisteredRunner := connect.NewError(connect.CodeUnauthenticated, "unregistered runner")
return func(ctx context.Context, spec connect.Spec, stream connect.ServerStream) error {
methodName := getMethodName(spec)
if methodName == "Register" {
return unaryFunc(ctx, request)
return next(ctx, spec, stream)
}
uuid := request.Header().Get(uuidHeaderKey)
token := request.Header().Get(tokenHeaderKey)
info, _ := connect.CallInfoForServerContext(ctx)
uuid := info.RequestHeader().Get(uuidHeaderKey)
token := info.RequestHeader().Get(tokenHeaderKey)
runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
return nil, errUnregisteredRunner
return errUnregisteredRunner
}
return nil, status.Error(codes.Internal, err.Error())
return status.Error(codes.Internal, err.Error())
}
if !util.CryptoConstTimeEqual(runner.TokenHash, auth_model.HashToken(token, runner.TokenSalt)) {
return nil, errUnregisteredRunner
return errUnregisteredRunner
}
now := time.Now()
@@ -72,12 +84,12 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
}
ctx = context.WithValue(ctx, runnerCtxKey{}, runner)
return unaryFunc(ctx, request)
return next(ctx, spec, stream)
}
}))
}
func getMethodName(req connect.AnyRequest) string {
splits := strings.Split(req.Spec().Procedure, "/")
func getMethodName(spec connect.Spec) string {
splits := strings.Split(spec.Procedure, "/")
if len(splits) > 0 {
return splits[len(splits)-1]
}
+52 -50
View File
@@ -20,18 +20,20 @@ import (
"gitea.dev/modules/util"
actions_service "gitea.dev/services/actions"
"connectrpc.com/connect"
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
func NewRunnerServiceHandler() (string, http.Handler) {
return runnerv1connect.NewRunnerServiceHandler(
&Service{},
connect.WithCompressMinBytes(1024),
withRunner,
)
server := connect.NewServer(withErrorMessages, withRunner)
runnerv1connect.RegisterRunnerServiceHandler(server, &Service{})
mux := http.NewServeMux()
// runners send all pending log rows in one UpdateLog, so keep v1's unlimited message size
connecthttp.Mount(mux, server, connecthttp.WithCompressMinBytes(1024), connecthttp.WithReadMaxBytes(0))
return "/" + runnerv1connect.RunnerServiceName + "/", mux
}
var _ runnerv1connect.RunnerServiceClient = (*Service)(nil)
@@ -41,13 +43,13 @@ type Service struct{}
// 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 == "" {
req *runnerv1.RegisterRequest,
) (*runnerv1.RegisterResponse, error) {
if req.Token == "" || req.Name == "" {
return nil, errors.New("missing runner token, name")
}
runnerToken, err := actions_model.GetRunnerToken(ctx, req.Msg.Token)
runnerToken, err := actions_model.GetRunnerToken(ctx, req.Token)
if err != nil {
return nil, errors.New("runner registration token not found")
}
@@ -68,19 +70,19 @@ func (s *Service) Register(
}
}
labels := req.Msg.Labels
hasCancellingSupport := slices.Contains(req.Msg.GetCapabilities(), runnerCapabilityCancelling)
labels := req.Labels
hasCancellingSupport := slices.Contains(req.GetCapabilities(), runnerCapabilityCancelling)
// create new runner
name := util.EllipsisDisplayString(req.Msg.Name, 255)
name := util.EllipsisDisplayString(req.Name, 255)
runner := &actions_model.ActionRunner{
UUID: uuid.New().String(),
Name: name,
OwnerID: runnerToken.OwnerID,
RepoID: runnerToken.RepoID,
Version: req.Msg.Version,
Version: req.Version,
AgentLabels: labels,
Ephemeral: req.Msg.Ephemeral,
Ephemeral: req.Ephemeral,
HasCancellingSupport: hasCancellingSupport,
}
runner.GenerateAndFillToken()
@@ -96,7 +98,7 @@ func (s *Service) Register(
return nil, errors.New("can't update runner token status")
}
res := connect.NewResponse(&runnerv1.RegisterResponse{
res := &runnerv1.RegisterResponse{
Runner: &runnerv1.Runner{
Id: runner.ID,
Uuid: runner.UUID,
@@ -106,7 +108,7 @@ func (s *Service) Register(
Labels: runner.AgentLabels,
Ephemeral: runner.Ephemeral,
},
})
}
return res, nil
}
@@ -139,14 +141,18 @@ func applyDeclareRequestToRunner(runner *actions_model.ActionRunner, req declare
func (s *Service) Declare(
ctx context.Context,
req *connect.Request[runnerv1.DeclareRequest],
) (*connect.Response[runnerv1.DeclareResponse], error) {
req *runnerv1.DeclareRequest,
) (*runnerv1.DeclareResponse, error) {
runner := GetRunner(ctx)
if err := actions_model.UpdateRunner(ctx, runner, applyDeclareRequestToRunner(runner, req.Msg)...); err != nil {
if err := actions_model.UpdateRunner(ctx, runner, applyDeclareRequestToRunner(runner, req)...); err != nil {
return nil, status.Errorf(codes.Internal, "update runner: %v", err)
}
resp := connect.NewResponse(&runnerv1.DeclareResponse{
// Capabilities are communicated via headers to avoid a hard dependency on a proto bump.
// Older runners ignore unknown headers; newer runners can use this for feature negotiation.
info, _ := connect.CallInfoForServerContext(ctx)
info.ResponseHeader().Set("X-Gitea-Actions-Capabilities", actions_model.RunnerCapabilities())
return &runnerv1.DeclareResponse{
Runner: &runnerv1.Runner{
Id: runner.ID,
Uuid: runner.UUID,
@@ -155,22 +161,18 @@ func (s *Service) Declare(
Version: runner.Version,
Labels: runner.AgentLabels,
},
})
// Capabilities are communicated via headers to avoid a hard dependency on a proto bump.
// Older runners ignore unknown headers; newer runners can use this for feature negotiation.
resp.Header().Set("X-Gitea-Actions-Capabilities", actions_model.RunnerCapabilities())
return resp, nil
}, 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) {
req *runnerv1.FetchTaskRequest,
) (*runnerv1.FetchTaskResponse, error) {
runner := GetRunner(ctx)
var task *runnerv1.Task
tasksVersion := req.Msg.TasksVersion // task version from runner
tasksVersion := req.TasksVersion // task version from runner
latestVersion, err := actions_model.GetTasksVersionByScope(ctx, runner.OwnerID, runner.RepoID)
if err != nil {
return nil, status.Errorf(codes.Internal, "query tasks version failed: %v", err)
@@ -207,26 +209,26 @@ func (s *Service) FetchTask(
task = t
}
}
res := connect.NewResponse(&runnerv1.FetchTaskResponse{
res := &runnerv1.FetchTaskResponse{
Task: task,
TasksVersion: latestVersion,
})
}
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) {
req *runnerv1.UpdateTaskRequest,
) (*runnerv1.UpdateTaskResponse, error) {
runner := GetRunner(ctx)
task, err := actions_model.UpdateTaskByState(ctx, runner.ID, req.Msg.State)
task, err := actions_model.UpdateTaskByState(ctx, runner.ID, req.State)
if err != nil {
return nil, status.Errorf(codes.Internal, "update task: %v", err)
}
for k, v := range req.Msg.Outputs {
for k, v := range req.Outputs {
if len(k) > 255 {
log.Warn("Ignore the output of task %d because the key is too long: %q", task.ID, k)
continue
@@ -264,7 +266,7 @@ func (s *Service) UpdateTask(
actions_service.NotifyWorkflowJobStatusUpdateWithTask(ctx, task.Job, task)
}
if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
if req.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
if err := actions_service.EmitJobsIfReadyByRun(task.Job.RunID); err != nil {
log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
}
@@ -273,25 +275,25 @@ func (s *Service) UpdateTask(
}
}
return connect.NewResponse(&runnerv1.UpdateTaskResponse{
return &runnerv1.UpdateTaskResponse{
State: &runnerv1.TaskState{
Id: req.Msg.State.Id,
Id: req.State.Id,
Result: task.Status.AsResult(),
},
SentOutputs: sentOutputs,
}), nil
}, nil
}
// UpdateLog uploads log of the task.
func (s *Service) UpdateLog(
ctx context.Context,
req *connect.Request[runnerv1.UpdateLogRequest],
) (*connect.Response[runnerv1.UpdateLogResponse], error) {
req *runnerv1.UpdateLogRequest,
) (*runnerv1.UpdateLogResponse, error) {
runner := GetRunner(ctx)
res := connect.NewResponse(&runnerv1.UpdateLogResponse{})
res := &runnerv1.UpdateLogResponse{}
task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId)
task, err := actions_model.GetTaskByID(ctx, req.TaskId)
if err != nil {
return nil, status.Errorf(codes.Internal, "get task: %v", err)
} else if runner.ID != task.RunnerID {
@@ -301,8 +303,8 @@ func (s *Service) UpdateLog(
// Trim rows the runner already had acked.
var rows []*runnerv1.LogRow
if req.Msg.Index <= ack && int64(len(req.Msg.Rows))+req.Msg.Index > ack {
rows = req.Msg.Rows[ack-req.Msg.Index:]
if req.Index <= ack && int64(len(req.Rows))+req.Index > ack {
rows = req.Rows[ack-req.Index:]
}
// Ack a re-sent finalize idempotently. Appending new rows past the seal errors.
@@ -310,15 +312,15 @@ func (s *Service) UpdateLog(
if len(rows) > 0 {
return nil, status.Errorf(codes.AlreadyExists, "log file has been archived")
}
res.Msg.AckIndex = ack
res.AckIndex = ack
return res, nil
}
// Bail unless we have new rows or a NoMore to finalize. Even with
// NoMore, bail when the runner has outrun the server — archiving a
// log with a gap is worse than asking it to retry.
if len(rows) == 0 && (!req.Msg.NoMore || req.Msg.Index > ack) {
res.Msg.AckIndex = ack
if len(rows) == 0 && (!req.NoMore || req.Index > ack) {
res.AckIndex = ack
return res, nil
}
@@ -335,10 +337,10 @@ func (s *Service) UpdateLog(
task.LogSize += int64(n)
}
res.Msg.AckIndex = task.LogLength
res.AckIndex = task.LogLength
var remove func()
if req.Msg.NoMore {
if req.NoMore {
task.LogInStorage = true
remove, err = actions.TransferLogs(ctx, task.LogFilename)
if err != nil {
+19
View File
@@ -4,14 +4,33 @@
package runner
import (
"errors"
"net/http/httptest"
"testing"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/actionslib/runner/v1/runnerv1connect"
actions_model "gitea.dev/models/actions"
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPlainErrorReachesRunnerAsUnknownWithMessage(t *testing.T) {
_, handler := NewRunnerServiceHandler()
server := httptest.NewServer(handler)
defer server.Close()
client := runnerv1connect.NewRunnerServiceClient(connect.NewClient(connecthttp.NewTransport(server.Client(), server.URL)))
_, err := client.Register(t.Context(), &runnerv1.RegisterRequest{})
connectErr, ok := errors.AsType[*connect.Error](err)
require.True(t, ok)
assert.Equal(t, connect.CodeUnknown, connectErr.Code())
assert.Equal(t, "missing runner token, name", connectErr.Message())
}
func TestApplyDeclareRequestToRunnerAdvertisedCapabilityEnablesCancelling(t *testing.T) {
runner := &actions_model.ActionRunner{}
req := &runnerv1.DeclareRequest{
+2 -2
View File
@@ -335,8 +335,8 @@ func ListIssues(ctx *context.APIContext) {
keyword := ctx.FormTrim("q")
var labelIDs []int64
if labelsParam := ctx.FormTrim("labels"); labelsParam != "" {
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, strings.Split(labelsParam, ","))
if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 {
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, splitted)
if err != nil {
ctx.APIErrorInternal(err)
return
+1 -1
View File
@@ -73,7 +73,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
return
}
reactions, _, err := issues_model.FindCommentReactions(ctx, comment.ID)
reactions, _, err := issues_model.FindCommentReactions(ctx, comment.IssueID, comment.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
+3 -12
View File
@@ -981,23 +981,14 @@ func MergePullRequest(ctx *context.APIContext) {
}
message := strings.TrimSpace(form.MergeTitleField)
useDefaultSquashBody := !form.HasMergeMessageField && !ctx.Req.Form.Has("merge_message_field") && repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash
if len(message) == 0 || useDefaultSquashBody {
defaultTitle, defaultBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
if len(message) == 0 {
message, _, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
if err != nil {
ctx.APIErrorInternal(err)
return
}
message = util.IfZero(message, defaultTitle)
if useDefaultSquashBody {
squashCommitMessages, err := pull_service.GetSquashMergeCommitMessages(ctx, pr)
if err != nil {
ctx.APIErrorInternal(err)
return
}
form.MergeMessageField = git.CommitMessageMerge(squashCommitMessages, defaultBody)
}
}
form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
if len(form.MergeMessageField) > 0 {
message += "\n\n" + form.MergeMessageField
-4
View File
@@ -895,10 +895,6 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
optional.AssignPtrValue(changed, &config.DefaultAllowMaintainerEdit, opts.DefaultAllowMaintainerEdit)
optional.AssignPtrString(changed, &config.DefaultMergeStyle, opts.DefaultMergeStyle)
optional.AssignPtrString(changed, &config.DefaultUpdateStyle, opts.DefaultUpdateStyle)
optional.AssignPtrString(changed, &config.DefaultSquashMergeMessage, opts.SquashMergeCommitMessage)
if opts.SquashMergeCommitMessage != nil && !config.DefaultSquashMergeMessage.IsValid() {
return util.NewInvalidArgumentErrorf("invalid squash_merge_commit_message: %s", config.DefaultSquashMergeMessage)
}
// only validate update-style fields when the caller is actually changing one of them,
// so unrelated PATCH calls don't reject historical configs.
if opts.AllowMergeUpdate != nil || opts.AllowRebaseUpdate != nil || opts.DefaultUpdateStyle != nil {
+1
View File
@@ -669,6 +669,7 @@ func (cpi *comparePageInfoType) prepareCreatePullRequestPage(ctx *context.Contex
}
ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypePullRequests)
+18 -14
View File
@@ -25,7 +25,6 @@ import (
"gitea.dev/modules/log"
"gitea.dev/modules/markup/markdown"
"gitea.dev/modules/optional"
"gitea.dev/modules/setting"
api "gitea.dev/modules/structs"
"gitea.dev/modules/templates"
"gitea.dev/modules/util"
@@ -46,7 +45,7 @@ const (
tplIssueView templates.TplName = "repo/issue/view"
tplPullMergeBox templates.TplName = "repo/issue/view_content/pull_merge_box"
tplReactions templates.TplName = "shared/reactions"
tplReactions templates.TplName = "repo/issue/view_content/reactions"
issueTemplateKey = "IssueTemplate"
issueTemplateTitleKey = "IssueTemplateTitle"
@@ -537,20 +536,25 @@ func ChangeIssueReaction(ctx *context.Context) {
return
}
renderReactions(ctx, fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index), issue.Reactions, setting.UI.Reactions)
}
func renderReactions(ctx *context.Context, actionURL string, reactions issues_model.ReactionList, allowedReactions []string) {
html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ActionURL": actionURL,
"Reactions": reactions.GroupByType(),
"AllowedReactions": allowedReactions,
})
if err != nil {
ctx.ServerError("RenderToHTML", err)
if len(issue.Reactions) == 0 {
ctx.JSON(http.StatusOK, map[string]any{
"empty": true,
"html": "",
})
return
}
ctx.JSON(http.StatusOK, map[string]any{"html": html})
html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index),
"Reactions": issue.Reactions.GroupByType(),
})
if err != nil {
ctx.ServerError("ChangeIssueReaction.HTMLString", err)
return
}
ctx.JSON(http.StatusOK, map[string]any{
"html": html,
})
}
// GetIssueAttachments returns attachments for the issue
+19 -1
View File
@@ -300,7 +300,25 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
renderReactions(ctx, fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID), comment.Reactions, setting.UI.Reactions)
if len(comment.Reactions) == 0 {
ctx.JSON(http.StatusOK, map[string]any{
"empty": true,
"html": "",
})
return
}
html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID),
"Reactions": comment.Reactions.GroupByType(),
})
if err != nil {
ctx.ServerError("ChangeCommentReaction.HTMLString", err)
return
}
ctx.JSON(http.StatusOK, map[string]any{
"html": html,
})
}
// GetCommentAttachments returns attachments for the comment
+1
View File
@@ -111,6 +111,7 @@ func NewIssue(ctx *context.Context) {
isProjectsEnabled := ctx.Repo.Permission.CanRead(unit.TypeProjects)
ctx.Data["IsProjectsEnabled"] = isProjectsEnabled
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
pageMetaData := retrieveRepoIssueMetaDataWithPrefill(ctx, ctx.Repo.Repository, false)
+1 -1
View File
@@ -362,7 +362,7 @@ func ViewIssue(ctx *context.Context) {
}
ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanRead(unit.TypeProjects)
ctx.Data["AllowedIssueReactions"] = setting.UI.Reactions
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
if err := issue.LoadAttributes(ctx); err != nil {
+18 -21
View File
@@ -370,7 +370,8 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCompareInfo(ctx *context.Conte
pull := prInfo.issue.PullRequest
prInfo.CompareInfo, err = git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo, baseRef, git.RefName(pull.GetGitHeadRefName()), false, false)
if err != nil {
if !errors.Is(err, util.ErrNotExist) && !gitcmd.IsStderr(err, gitcmd.StderrNotValidObjectName) {
isKnownErrorForBroken := errors.Is(err, util.ErrNotExist) || gitcmd.IsStderr(err, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(err, gitcmd.StderrUnknownRevisionOrPath)
if !isKnownErrorForBroken {
log.Error("GetCompareInfo: %v", err)
}
prInfo.IsPullRequestBroken = true
@@ -884,39 +885,35 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
}
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
var numPendingCodeComments int64
var currentPendingReview *issues_model.Review
if ctx.Doer != nil {
currentPendingReview, err = issues_model.GetCurrentReview(ctx, ctx.Doer, issue)
if err != nil && !issues_model.IsErrReviewNotExist(err) {
ctx.ServerError("GetCurrentReview", err)
currentReview, err := issues_model.GetCurrentReview(ctx, ctx.Doer, issue)
if err != nil && !issues_model.IsErrReviewNotExist(err) {
ctx.ServerError("GetCurrentReview", err)
return
}
numPendingCodeComments := int64(0)
if currentReview != nil {
numPendingCodeComments, err = issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{
Type: issues_model.CommentTypeCode,
ReviewID: currentReview.ID,
IssueID: issue.ID,
})
if err != nil {
ctx.ServerError("CountComments", err)
return
}
if currentPendingReview != nil {
numPendingCodeComments, err = issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{
Type: issues_model.CommentTypeCode,
ReviewID: currentPendingReview.ID,
IssueID: issue.ID,
})
if err != nil {
ctx.ServerError("CountComments", err)
return
}
}
}
ctx.Data["CurrentReview"] = currentPendingReview
ctx.Data["CurrentReview"] = currentReview
ctx.Data["PendingCodeCommentNumber"] = numPendingCodeComments
ctx.Data["IsIssuePoster"] = ctx.Doer != nil && issue.IsPoster(ctx.Doer.ID)
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
// For files changed page
PrepareBranchList(ctx)
if ctx.Written() {
return
}
ctx.Data["AllowedIssueReactions"] = setting.UI.Reactions
upload.AddUploadContext(ctx, "comment")
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
+2 -1
View File
@@ -55,6 +55,7 @@ func RenderNewCodeCommentForm(ctx *context.Context) {
return
}
ctx.Data["AfterCommitID"] = pullHeadCommitID
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
ctx.HTML(http.StatusOK, tplNewComment)
}
@@ -185,7 +186,7 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori
return
}
ctx.Data["AllowedIssueReactions"] = setting.UI.Reactions
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
ctx.Data["comments"] = comments
+13 -85
View File
@@ -14,7 +14,6 @@ import (
"gitea.dev/models/db"
git_model "gitea.dev/models/git"
issues_model "gitea.dev/models/issues"
"gitea.dev/models/renderhelper"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
@@ -74,7 +73,6 @@ type ReleaseInfo struct {
Release *repo_model.Release
CommitStatus *git_model.CommitStatus
CommitStatuses []*git_model.CommitStatus
Reactions issues_model.ReactionList
}
func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) ([]*ReleaseInfo, error) {
@@ -83,31 +81,17 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
return nil, err
}
releaseIDs := make([]int64, 0, len(releases))
for _, release := range releases {
release.Repo = ctx.Repo.Repository
releaseIDs = append(releaseIDs, release.ID)
}
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
return nil, err
}
reactions, err := issues_model.FindReleaseReactions(ctx, releaseIDs...)
if err != nil {
return nil, err
}
cacheUsers, err := reactions.LoadUsers(ctx, ctx.Repo.Repository)
if err != nil {
return nil, err
}
reactionsByRelease := make(map[int64]issues_model.ReactionList)
for _, reaction := range reactions {
reactionsByRelease[reaction.ReleaseID] = append(reactionsByRelease[reaction.ReleaseID], reaction)
}
// Temporary cache commits count of used branches to speed up.
countCache := make(map[string]int64)
cacheUsers := make(map[int64]*user_model.User)
if ctx.Doer != nil {
cacheUsers[ctx.Doer.ID] = ctx.Doer
}
@@ -154,8 +138,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
}
info := &ReleaseInfo{
Release: r,
Reactions: reactionsByRelease[r.ID],
Release: r,
}
if canReadCode {
@@ -171,22 +154,10 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
return releaseInfos, nil
}
func prepareReleasePageCommon(ctx *context.Context) (ret struct {
writeAccess bool
},
) {
ctx.Data["PageIsReleaseList"] = true
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
ctx.Data["AllowedReleaseReactions"] = release_service.AllowedReleaseReactions()
ret.writeAccess = ctx.Repo.Permission.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = ret.writeAccess && !ctx.Repo.Repository.IsArchived
return ret
}
// Releases render releases list page
func Releases(ctx *context.Context) {
preparedData := prepareReleasePageCommon(ctx)
ctx.Data["PageIsReleaseList"] = true
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
listOptions := db.ListOptions{
Page: ctx.FormInt("page"),
@@ -199,10 +170,13 @@ func Releases(ctx *context.Context) {
listOptions.PageSize = setting.API.MaxResponseItems
}
writeAccess := ctx.Repo.Permission.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
ListOptions: listOptions,
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: preparedData.writeAccess,
IncludeDrafts: writeAccess,
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
@@ -301,14 +275,17 @@ func releasesOrTagsFeed(ctx *context.Context, isReleasesOnly bool, formatType st
// SingleRelease renders a single release's page
func SingleRelease(ctx *context.Context) {
preparedData := prepareReleasePageCommon(ctx)
ctx.Data["PageIsReleaseList"] = true
writeAccess := ctx.Repo.Permission.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 1},
RepoID: ctx.Repo.Repository.ID,
TagNames: []string{ctx.PathParam("*")},
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: preparedData.writeAccess,
IncludeDrafts: writeAccess,
IncludeTags: true,
})
if err != nil {
@@ -704,52 +681,3 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
redirect()
}
func ChangeReleaseReaction(ctx *context.Context) {
form := context.GetFetchActionForm[*forms.ReactionForm](ctx)
if form == nil {
return
}
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
ctx.ServerError("GetReleaseForRepoByID", err)
return
}
if rel.IsTag || rel.IsDraft {
ctx.NotFound(nil)
return
}
rel.Repo = ctx.Repo.Repository
switch ctx.PathParam("action") {
case "react":
_, err = release_service.CreateReleaseReaction(ctx, ctx.Doer, rel, form.Content)
if errors.Is(err, util.ErrPermissionDenied) {
ctx.HTTPError(http.StatusForbidden)
return
} else if err != nil && !errors.Is(err, util.ErrAlreadyExist) {
ctx.ServerError("CreateReleaseReaction", err)
return
}
case "unreact":
if err = issues_model.DeleteReleaseReaction(ctx, ctx.Doer.ID, rel.ID, form.Content); err != nil {
ctx.ServerError("DeleteReleaseReaction", err)
return
}
default:
ctx.NotFound(nil)
return
}
reactions, err := issues_model.FindReleaseReactions(ctx, rel.ID)
if err != nil {
ctx.ServerError("FindReleaseReactions", err)
return
}
if _, err = reactions.LoadUsers(ctx, ctx.Repo.Repository); err != nil {
ctx.ServerError("LoadUsers", err)
return
}
renderReactions(ctx, fmt.Sprintf("%s/releases/%d/reactions", ctx.Repo.RepoLink, rel.ID), reactions, release_service.AllowedReleaseReactions())
}
-5
View File
@@ -680,7 +680,6 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
DefaultUpdateStyle: defaultUpdateStyle,
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
DefaultSquashMergeMessage: util.IfZero(repo_model.SquashMergeMessage(form.PullsDefaultSquashMergeMessage), repo_model.DefaultPullRequestsConfig().DefaultSquashMergeMessage),
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
DefaultTargetBranch: strings.TrimSpace(form.DefaultTargetBranch),
}
@@ -688,10 +687,6 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
ctx.JSONErrorAuto(err)
return
}
if !prConfig.DefaultSquashMergeMessage.IsValid() {
ctx.JSONErrorAuto(util.NewInvalidArgumentErrorf("invalid default squash merge message: %s", prConfig.DefaultSquashMergeMessage))
return
}
units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, prConfig))
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
+3 -4
View File
@@ -224,13 +224,13 @@ func verifyAuthWithOptionsWeb(options *common.VerifyOptions) func(ctx *context.C
}
if options.SignInRequired && !ctx.IsSigned {
ctx.RedirectForLogin(middleware.RedirectLinkUserLogin(ctx.Req))
ctx.Redirect(middleware.RedirectLinkUserLogin(ctx.Req))
return
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned && ctx.GetSiteCookie(setting.CookieRememberName) != "" {
ctx.RedirectForLogin(middleware.RedirectLinkUserLogin(ctx.Req))
ctx.Redirect(middleware.RedirectLinkUserLogin(ctx.Req))
return
}
@@ -1503,7 +1503,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
}, ctxDataSet(reqctx.ContextData{"EnableFeed": setting.Other.EnableFeed}))
m.Get("/releases/attachments/{uuid}", webAuth.AllowBasic, webAuth.AllowOAuth2, repo.GetAttachment)
m.Get("/releases/download/{vTag}/{fileName}", webAuth.AllowBasic, webAuth.AllowOAuth2, repo.RedirectDownload)
m.Post("/releases/{id}/reactions/{action}", reqSignIn, context.RepoMustNotBeArchived(), repo.ChangeReleaseReaction)
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", web.Bind[*forms.NewReleaseForm](), repo.NewReleasePost)
@@ -1662,7 +1661,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
m.Post("/comments", web.Bind[*forms.CodeCommentForm](), repo.SetShowOutdatedComments, repo.CreateCodeComment)
m.Post("/submit", web.Bind[*forms.SubmitReviewForm](), repo.SubmitReview)
}, reqSignIn, context.RepoMustNotBeArchived())
}, context.RepoMustNotBeArchived())
})
})
}, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader)
+1 -11
View File
@@ -148,26 +148,16 @@ func (b *Base) PlainText(status int, text string) {
// Redirect redirects the request
func (b *Base) Redirect(location string, status ...int) {
code := util.OptionalArg(status, http.StatusSeeOther)
// In case the request is made by "fetch-action" module, make JS redirect to the new location
// Otherwise, the JS fetch will follow the redirection and read a "login" page, embed it to the current page, which is not expected.
if httplib.IsGiteaFetchActionRequest(b.Req) {
b.JSON(http.StatusOK, map[string]any{"redirect": location})
return
}
code := util.OptionalArg(status, http.StatusSeeOther)
http.Redirect(b.Resp, b.Req, location, code)
}
func (b *Base) RedirectForLogin(location string) {
if httplib.IsGiteaFetchActionRequest(b.Req) {
// let fetch-action handles the redirect automatically
b.JSON(http.StatusUnauthorized, map[string]any{"redirect": location})
} else {
// keep the old behaivor, use 303 to redirect to login page
http.Redirect(b.Resp, b.Req, location, http.StatusSeeOther)
}
}
type ServeHeaderOptions = httplib.ServeHeaderOptions
func (b *Base) SetServeHeaders(opts ServeHeaderOptions) {
-1
View File
@@ -104,7 +104,6 @@ type uploadOptions struct {
// AddUploadContext renders template values for dropzone
func AddUploadContext(ctx *context.Context, uploadType string) {
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
switch uploadType {
case "release":
ctx.Data["UploadOptions"] = uploadOptions{
-3
View File
@@ -105,7 +105,6 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
defaultDeleteBranchAfterMerge := false
defaultMergeStyle := repo_model.MergeStyleMerge
defaultUpdateStyle := repo_model.UpdateStyleMerge
defaultSquashMergeMessage := repo_model.DefaultPullRequestsConfig().DefaultSquashMergeMessage
defaultAllowMaintainerEdit := false
defaultTargetBranch := ""
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
@@ -124,7 +123,6 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
defaultMergeStyle = config.DefaultMergeStyle
defaultUpdateStyle = config.DefaultUpdateStyle
defaultSquashMergeMessage = config.DefaultSquashMergeMessage
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
defaultTargetBranch = config.DefaultTargetBranch
}
@@ -253,7 +251,6 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
DefaultMergeStyle: string(defaultMergeStyle),
DefaultUpdateStyle: string(defaultUpdateStyle),
SquashMergeCommitMessage: string(defaultSquashMergeMessage),
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
DefaultTargetBranch: defaultTargetBranch,
AvatarURL: repo.AvatarLink(ctx),
+9 -14
View File
@@ -133,7 +133,6 @@ type RepoSettingForm struct {
PullsAllowMergeUpdate bool
PullsAllowRebaseUpdate bool
PullsDefaultUpdateStyle string
PullsDefaultSquashMergeMessage string
DefaultDeleteBranchAfterMerge bool
DefaultAllowMaintainerEdit bool
DefaultTargetBranch string
@@ -425,21 +424,20 @@ type MergePullRequestForm struct {
ForceMerge bool `json:"force_merge,omitempty"`
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
DeleteBranchAfterMerge *bool `json:"delete_branch_after_merge,omitempty"`
HasMergeMessageField bool `form:"-" json:"-"`
}
func (f *MergePullRequestForm) UnmarshalJSON(b []byte) error {
// This is for backward compatibility, to support both field names like "do" and "Do",
// because old code doesn't have "json" tag for these fields
type aux struct {
Do1 string `json:"do"`
Do2 string `json:"Do"`
MergeTitleField1 string `json:"merge_title_field"`
MergeTitleField2 string `json:"MergeTitleField"`
MergeMessageField1 *string `json:"merge_message_field"`
MergeMessageField2 *string `json:"MergeMessageField"`
MergeCommitID1 string `json:"merge_commit_id"`
MergeCommitID2 string `json:"MergeCommitID"`
Do1 string `json:"do"`
Do2 string `json:"Do"`
MergeTitleField1 string `json:"merge_title_field"`
MergeTitleField2 string `json:"MergeTitleField"`
MergeMessageField1 string `json:"merge_message_field"`
MergeMessageField2 string `json:"MergeMessageField"`
MergeCommitID1 string `json:"merge_commit_id"`
MergeCommitID2 string `json:"MergeCommitID"`
HeadCommitID string `json:"head_commit_id"`
ForceMerge bool `json:"force_merge"`
@@ -452,10 +450,7 @@ func (f *MergePullRequestForm) UnmarshalJSON(b []byte) error {
}
f.Do = util.IfZero(a.Do1, a.Do2)
f.MergeTitleField = util.IfZero(a.MergeTitleField1, a.MergeTitleField2)
f.MergeMessageField, f.HasMergeMessageField = "", false
if mergeMessageField := util.IfZero(a.MergeMessageField1, a.MergeMessageField2); mergeMessageField != nil {
f.MergeMessageField, f.HasMergeMessageField = *mergeMessageField, true
}
f.MergeMessageField = util.IfZero(a.MergeMessageField1, a.MergeMessageField2)
f.MergeCommitID = util.IfZero(a.MergeCommitID1, a.MergeCommitID2)
f.HeadCommitID = a.HeadCommitID
f.ForceMerge = a.ForceMerge
-15
View File
@@ -51,7 +51,6 @@ func TestMergePullRequestForm(t *testing.T) {
ForceMerge: true,
MergeWhenChecksSucceed: true,
DeleteBranchAfterMerge: new(true),
HasMergeMessageField: true,
}
t.Run("NewFields", func(t *testing.T) {
@@ -85,18 +84,4 @@ func TestMergePullRequestForm(t *testing.T) {
require.NoError(t, json.Unmarshal([]byte(input), &m))
assert.Equal(t, expected, m)
})
t.Run("OmittedMergeMessageFieldClearsPreviousMessage", func(t *testing.T) {
form := &MergePullRequestForm{MergeMessageField: "previous message", HasMergeMessageField: true}
require.NoError(t, json.Unmarshal([]byte(`{"do": "squash"}`), &form))
assert.Empty(t, form.MergeMessageField)
assert.False(t, form.HasMergeMessageField)
})
t.Run("EmptyMergeMessageField", func(t *testing.T) {
var form MergePullRequestForm
require.NoError(t, json.Unmarshal([]byte(`{"do": "squash", "merge_message_field": ""}`), &form))
assert.Empty(t, form.MergeMessageField)
assert.True(t, form.HasMergeMessageField)
})
}
-16
View File
@@ -1596,22 +1596,6 @@ func CommentAsDiff(ctx context.Context, c *issues_model.Comment) (*Diff, error)
return diff, nil
}
// GetCodeCommentPatch returns the code snippet stored with a code comment
func GetCodeCommentPatch(ctx context.Context, gitRepo *git.Repository, mergeBase, headCommitID, treePath string, line int64) (string, error) {
patch, err := git.GetFileDiffCutAroundLine(ctx, gitRepo, mergeBase, headCommitID, treePath,
int64((&issues_model.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines,
)
contextCommitID := util.Iif(line < 0, mergeBase, headCommitID)
if err != nil || patch != "" || contextCommitID == "" {
return patch, err
}
patch, err = GeneratePatchForUnchangedLine(ctx, gitRepo, contextCommitID, treePath, line, setting.UI.CodeCommentLines)
if err != nil {
log.Debug("Unable to generate patch for unchanged line (file=%s, line=%d, commit=%s): %v", treePath, line, contextCommitID, err)
}
return patch, nil
}
// GeneratePatchForUnchangedLine creates a patch showing code context for an unchanged line
func GeneratePatchForUnchangedLine(ctx context.Context, gitRepo *git.Repository, commitID, treePath string, line int64, contextLines int) (string, error) {
commit, err := gitRepo.GetCommit(ctx, commitID)
+2 -3
View File
@@ -8,7 +8,6 @@ import (
issues_model "gitea.dev/models/issues"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
)
// CreateIssueReaction creates a reaction on an issue.
@@ -21,7 +20,7 @@ func CreateIssueReaction(ctx context.Context, doer *user_model.User, issue *issu
return nil, user_model.ErrBlockedUser
}
return issues_model.CreateReaction(ctx, setting.UI.ReactionsLookup, &issues_model.ReactionOptions{
return issues_model.CreateReaction(ctx, &issues_model.ReactionOptions{
Type: content,
DoerID: doer.ID,
IssueID: issue.ID,
@@ -42,7 +41,7 @@ func CreateCommentReaction(ctx context.Context, doer *user_model.User, comment *
return nil, user_model.ErrBlockedUser
}
return issues_model.CreateReaction(ctx, setting.UI.ReactionsLookup, &issues_model.ReactionOptions{
return issues_model.CreateReaction(ctx, &issues_model.ReactionOptions{
Type: content,
DoerID: doer.ID,
IssueID: comment.Issue.ID,
+8 -3
View File
@@ -90,7 +90,9 @@ func TestIssueReactionCount(t *testing.T) {
addReaction(t, user4, issue, nil, "heart")
addReaction(t, ghost, issue, nil, "-1")
reactionsList, _, err := issues_model.FindReactions(t.Context(), issues_model.FindReactionsOptions{IssueIDOnly: issue.ID})
reactionsList, _, err := issues_model.FindReactions(t.Context(), issues_model.FindReactionsOptions{
IssueID: issue.ID,
})
assert.NoError(t, err)
assert.Len(t, reactionsList, 7)
_, err = reactionsList.LoadUsers(t.Context(), repo)
@@ -99,7 +101,7 @@ func TestIssueReactionCount(t *testing.T) {
reactions := reactionsList.GroupByType()
assert.Len(t, reactions["heart"], 4)
assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
assert.Equal(t, "user1 (User One), user2 (< U<se>r Tw<o > ><)", reactions["heart"].GetFirstUsers())
assert.Equal(t, user1.Name+", "+user2.Name, reactions["heart"].GetFirstUsers())
assert.True(t, reactions["heart"].HasUser(1))
assert.False(t, reactions["heart"].HasUser(5))
assert.False(t, reactions["heart"].HasUser(0))
@@ -134,7 +136,10 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
addReaction(t, org3, nil, comment, "heart")
addReaction(t, user4, nil, comment, "+1")
reactionsList, _, err := issues_model.FindReactions(t.Context(), issues_model.FindReactionsOptions{CommentID: comment.ID})
reactionsList, _, err := issues_model.FindReactions(t.Context(), issues_model.FindReactionsOptions{
IssueID: comment.IssueID,
CommentID: comment.ID,
})
assert.NoError(t, err)
assert.Len(t, reactionsList, 4)
+4 -2
View File
@@ -31,7 +31,6 @@ import (
"gitea.dev/modules/timeutil"
"gitea.dev/modules/uri"
"gitea.dev/modules/util"
"gitea.dev/services/gitdiff"
"gitea.dev/services/pull"
repo_service "gitea.dev/services/repository"
)
@@ -906,7 +905,10 @@ func (g *GiteaLocalUploader) CreateReviews(ctx context.Context, reviews ...*base
// SECURITY: The TreePath must be cleaned! use relative path
comment.TreePath = util.PathJoinRel(comment.TreePath)
patch, _ := gitdiff.GetCodeCommentPatch(ctx, g.gitRepo, pr.MergeBase, headCommitID, comment.TreePath, int64(line+comment.Position-1))
patch, _ := git.GetFileDiffCutAroundLine(ctx,
g.gitRepo, pr.MergeBase, headCommitID, comment.TreePath,
int64((&issues_model.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines,
)
if comment.CreatedAt.IsZero() {
comment.CreatedAt = review.CreatedAt
+2 -2
View File
@@ -43,7 +43,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
}
err = git.ManagedRemoteAdd(ctx, repo, remoteName, addr, git.RemoteOptionMirrorFetch)
if err != nil {
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
@@ -56,7 +56,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
}
err = git.ManagedRemoteAdd(ctx, repo.WikiStorageRepo(), remoteName, wikiRemotePath, git.RemoteOptionMirrorFetch)
if err != nil {
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
}
+26 -12
View File
@@ -358,6 +358,26 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
})
}
func checkForInvalidation(ctx context.Context, requests issues_model.PullRequestList, repoID int64, doer *user_model.User, branch string) error {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
if err != nil {
return fmt.Errorf("GetRepositoryByIDCtx: %w", err)
}
gitRepo, err := git.OpenRepository(ctx, repo)
if err != nil {
return fmt.Errorf("gitrepo.OpenRepository: %w", err)
}
go func() {
// FIXME: graceful: We need to tell the manager we're doing something...
err := InvalidateCodeComments(ctx, requests, doer, repo, gitRepo, branch)
if err != nil {
log.Error("PullRequestList.InvalidateCodeComments: %v", err)
}
gitRepo.Close()
}()
return nil
}
type TestPullRequestOptions struct {
RepoID int64
Doer *user_model.User
@@ -416,8 +436,8 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
if err = headBranchPRs.LoadAttributes(ctx); err != nil {
log.Error("PullRequestList.LoadAttributes: %v", err)
}
if invalidationErr := InvalidateCodeComments(ctx, headBranchPRs, repo, opts.Branch); invalidationErr != nil {
log.Error("InvalidateCodeComments: %v", invalidationErr)
if invalidationErr := checkForInvalidation(ctx, headBranchPRs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil {
log.Error("checkForInvalidation: %v", invalidationErr)
}
if err == nil {
for _, pr := range headBranchPRs {
@@ -761,15 +781,9 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
return "", err
}
if err := pr.LoadBaseRepo(ctx); err != nil {
return "", err
}
var mergeMessage string
switch pr.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultSquashMergeMessage {
case repo_model.SquashMergeMessageCommitMessages:
mergeMessage = formatSquashMergeCommitMessages(limitedCommits)
case repo_model.SquashMergeMessagePRBody:
mergeMessage = strings.TrimSpace(pr.Issue.Content)
mergeMessage := strings.TrimSpace(pr.Issue.Content) // use PR's title and description as squash commit message
if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
mergeMessage = formatSquashMergeCommitMessages(limitedCommits) // use PR's commit messages as squash commit message
}
coAuthors := collectSquashMergeCommitCoAuthors(ctx, gitRepo, pr, headCommitRef, mergeBaseRef, limit, limitedCommits)
return buildSquashMergeCommitMessages(mergeMessage, coAuthors), nil
@@ -783,7 +797,7 @@ func buildSquashMergeCommitMessages(mergeMessage string, coAuthors []string) str
msgContent, msgSep, msgTrailer := git.CommitMessageSplitTrailer(mergeMessage)
if (msgSep == "" || msgSep == "\n\n") && msgTrailer == "" {
msgContent = strings.TrimRightFunc(msgContent, unicode.IsSpace)
msgSep = util.Iif(msgContent == "", "", "\n\n---------\n\n")
msgSep = "\n\n---------\n\n"
}
var sb strings.Builder
sb.WriteString(msgContent)
-1
View File
@@ -99,7 +99,6 @@ func TestBuildSquashMergeCommitMessages(t *testing.T) {
coAuthors []string
expected string
}{
{"", []string{"the-user"}, "Co-authored-by: the-user\n"},
{"title", nil, "title"},
{"title", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"},
{"title\n\n", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"},
+99 -77
View File
@@ -17,13 +17,14 @@ import (
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/log"
"gitea.dev/modules/optional"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
"gitea.dev/services/gitdiff"
notify_service "gitea.dev/services/notify"
)
func isErrBlameInvalidated(err error) bool {
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchPath, gitcmd.StderrFileNoEnoughLines, gitcmd.StderrBadObject)
func isErrBlameNotFoundOrNotEnoughLines(err error) bool {
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchPath, gitcmd.StderrFileNoEnoughLines)
}
// ErrDismissRequestOnClosedPR represents an error when a user tries to dismiss a review associated to a closed or merged PR.
@@ -46,27 +47,41 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
// ErrSubmitReviewOnClosedPR represents an error when a user tries to submit an approve or reject review associated to a closed or merged PR.
var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
// LineBlame returns the latest commit at the given line
func lineBlame(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, branch, file string, line uint) (*git.Commit, error) {
sha, err := git.LineBlame(ctx, repo, branch, file, line)
if err != nil {
return nil, err
}
if len(sha) < 40 {
return nil, fmt.Errorf("invalid result of blame: %s", sha)
}
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
return gitRepo.GetCommit(ctx, sha[:objectFormat.FullLength()])
}
// checkInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated.
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *repo_model.Repository, branch string) error {
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error {
// FIXME differentiate between previous and proposed line
commitID, err := git.LineBlameCommitID(ctx, repo, branch, c.TreePath, uint(c.UnsignedLine()))
if isErrBlameInvalidated(err) {
commit, err := lineBlame(ctx, repo, gitRepo, branch, c.TreePath, uint(c.UnsignedLine()))
if isErrBlameNotFoundOrNotEnoughLines(err) {
c.Invalidated = true
return issues_model.UpdateCommentInvalidate(ctx, c)
}
if err != nil {
return err
}
if c.CommitSHA != "" && c.CommitSHA != commitID {
if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() {
c.Invalidated = true
return issues_model.UpdateCommentInvalidate(ctx, c)
}
return nil
}
// InvalidateCodeComments will look up the prs for code comments which got invalidated by change
func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, repo *repo_model.Repository, branch string) error {
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error {
if len(prs) == 0 {
return nil
}
@@ -82,31 +97,35 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
return fmt.Errorf("find code comments: %v", err)
}
for _, comment := range codeComments {
if err := checkInvalidation(ctx, comment, repo, branch); err != nil {
if err := checkInvalidation(ctx, comment, repo, gitRepo, branch); err != nil {
return err
}
}
return nil
}
// CreateCodeComment creates a code comment in a review on the code line:
// When pendingReview=false:
// * no replyReviewID: create a new review and add the comment to it, and submit the review
// * replyReviewID is set: add the comment to the existing submitted review
// When pendingReview=true: get current pending review or create a new one, and add the comment to it
func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, pendingReview bool, replyReviewID int64, latestCommitID string, attachments []string) (_ *issues_model.Comment, err error) {
replyToSubmittedReview := !pendingReview && replyReviewID != 0
if replyToSubmittedReview {
// CreateCodeComment creates a comment on the code line
func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, pendingReview bool, replyReviewID int64, latestCommitID string, attachments []string) (*issues_model.Comment, error) {
var (
existsReview bool
err error
)
// CreateCodeComment() is used for:
// - Single comments
// - Comments that are part of a review
// - Comments that reply to an existing review
if !pendingReview && replyReviewID != 0 {
// It's not part of a review; maybe a reply to a review comment or a single comment.
// Check if there are reviews for that line already; if there are, this is a reply
if replyToSubmittedReview, err = issues_model.ReviewExists(ctx, issue, treePath, line); err != nil {
if existsReview, err = issues_model.ReviewExists(ctx, issue, treePath, line); err != nil {
return nil, err
}
}
// Comments that are replies don't require a review header to show up in the issue view
// Add the code comment to existing submitted review
if replyToSubmittedReview {
if !pendingReview && existsReview {
if err = issue.LoadRepo(ctx); err != nil {
return nil, err
}
@@ -135,43 +154,39 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.
return comment, nil
}
// If no existing submitted review, create a pending review and its comment in a single transaction
// to avoid failed code comment creation leaving a pending review without any comments
comment, err := db.WithTx2(ctx, func(ctx context.Context) (*issues_model.Comment, error) {
curReview, err := issues_model.GetCurrentReview(ctx, doer, issue)
if errors.Is(err, util.ErrNotExist) {
// create a new pending review for the code comment
if curReview, err = issues_model.CreateReview(ctx, issues_model.CreateReviewOptions{
Type: issues_model.ReviewTypePending,
Reviewer: doer,
Issue: issue,
Official: false,
CommitID: latestCommitID,
}); err != nil {
return nil, err
}
} else if err != nil {
review, err := issues_model.GetCurrentReview(ctx, doer, issue)
if err != nil {
if !issues_model.IsErrReviewNotExist(err) {
return nil, err
}
// add the code comment to the pending review
return createCodeComment(ctx,
doer,
issue.Repo,
issue,
content,
treePath,
line,
curReview.ID,
attachments,
)
})
if review, err = issues_model.CreateReview(ctx, issues_model.CreateReviewOptions{
Type: issues_model.ReviewTypePending,
Reviewer: doer,
Issue: issue,
Official: false,
CommitID: latestCommitID,
}); err != nil {
return nil, err
}
}
comment, err := createCodeComment(ctx,
doer,
issue.Repo,
issue,
content,
treePath,
line,
review.ID,
attachments,
)
if err != nil {
return nil, err
}
if !pendingReview {
// Doer is creating a submitted review, so submit the pending review we've just created,
// then the comment shows up in the issue view
if !pendingReview && !existsReview {
// Submit the review we've just created so the comment shows up in the issue view
if _, _, err = SubmitReview(ctx, doer, gitRepo, issue, issues_model.ReviewTypeComment, "", latestCommitID, nil); err != nil {
return nil, err
}
@@ -199,7 +214,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
defer closer.Close()
invalidated := false
headRef := pr.GetGitHeadRefName()
head := pr.GetGitHeadRefName()
if line > 0 {
if reviewID != 0 {
first, err := issues_model.FindComments(ctx, &issues_model.FindCommentsOptions{
@@ -212,55 +227,62 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
Page: 1,
},
})
if err != nil {
return nil, fmt.Errorf("find first comment for %d line %d path %s. Error: %w", reviewID, line, treePath, err)
}
if len(first) > 0 {
// if there are existing comments, use the first comment's information to create the new comment
if err == nil && len(first) > 0 {
commitID = first[0].CommitSHA
invalidated = first[0].Invalidated
patch = first[0].Patch
} else if err != nil && !issues_model.IsErrCommentNotExist(err) {
return nil, fmt.Errorf("Find first comment for %d line %d path %s. Error: %w", reviewID, line, treePath, err)
} else {
// otherwise, try to get the commitID from the review itself
review, err := issues_model.GetReviewByID(ctx, reviewID)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return nil, fmt.Errorf("createCodeComment: get review %d: %w", reviewID, err)
}
if review != nil && review.CommitID != "" {
headRef = review.CommitID
if err == nil && len(review.CommitID) > 0 {
head = review.CommitID
} else if err != nil && !issues_model.IsErrReviewNotExist(err) {
return nil, fmt.Errorf("GetReviewByID %d. Error: %w", reviewID, err)
}
}
}
if commitID == "" {
// FIXME validate treePath (TODO: unclear comment)
// Get latest commit referencing the commented line, error can be ignored then the head commit will be used as fallback
commitID, err = git.LineBlameCommitID(ctx, pr.BaseRepo, headRef, treePath, uint(line))
// Preserve attribution to the reviewed revision, falling back to the PR head if blame fails.
if err != nil && headRef != pr.GetGitHeadRefName() {
commitID, err = git.LineBlameCommitID(ctx, pr.BaseRepo, pr.GetGitHeadRefName(), treePath, uint(line))
}
if err != nil {
log.Debug("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitHeadRefName(), gitRepo.LogString(), treePath, line, err)
if len(commitID) == 0 {
// FIXME validate treePath
// Get latest commit referencing the commented line
// No need for get commit for base branch changes
commit, err := lineBlame(ctx, pr.BaseRepo, gitRepo, head, treePath, uint(line))
if err == nil {
commitID = commit.ID.String()
} else if !isErrBlameNotFoundOrNotEnoughLines(err) {
return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %w", pr.GetGitHeadRefName(), gitRepo.LogString(), treePath, line, err)
}
}
}
// Only fetch diff if comment is review comment
if patch == "" && reviewID != 0 {
if len(patch) == 0 && reviewID != 0 {
headCommitID, err := gitRepo.GetRefCommitID(ctx, pr.GetGitHeadRefName())
if err != nil {
return nil, fmt.Errorf("GetRefCommitID[%s]: %w", pr.GetGitHeadRefName(), err)
}
if commitID == "" {
if len(commitID) == 0 {
commitID = headCommitID
}
patch, err = gitdiff.GetCodeCommentPatch(ctx, gitRepo, pr.MergeBase, headCommitID, treePath, line)
patch, err = git.GetFileDiffCutAroundLine(ctx,
gitRepo, pr.MergeBase, headCommitID, treePath,
int64((&issues_model.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines,
)
if err != nil {
return nil, err
}
// If patch is still empty (unchanged line), generate code context
contextCommitID := util.Iif(line < 0, pr.MergeBase, headCommitID) // the line number is relative to the diff sides, not to the blamed commitID
if patch == "" && contextCommitID != "" {
patch, err = gitdiff.GeneratePatchForUnchangedLine(ctx, gitRepo, contextCommitID, treePath, line, setting.UI.CodeCommentLines)
if err != nil {
// Log the error but don't fail comment creation
log.Debug("Unable to generate patch for unchanged line (file=%s, line=%d, commit=%s): %v", treePath, line, contextCommitID, err)
}
}
}
return issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeCode,
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff. Show more