mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-05 18:32:41 +09:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f076ada601 | ||
|
|
92436b8b2a | ||
|
|
2df7d0835a | ||
|
|
200cb6140d | ||
|
|
2746c6f1aa | ||
|
|
23971a77a0 | ||
|
|
ebac324ff2 | ||
|
|
7df1204795 | ||
|
|
159544a950 | ||
|
|
9780da583d | ||
|
|
a8eaf43f97 | ||
|
|
b6fd8741ee | ||
|
|
2674d27fb8 | ||
|
|
6f3837284d | ||
|
|
c30f4f4be5 | ||
|
|
4578288ea3 | ||
|
|
826fffb59e | ||
|
|
2196ba5e42 | ||
|
|
12347f07ae | ||
|
|
a3c5358d35 | ||
|
|
987d014468 | ||
|
|
e08eed9040 | ||
|
|
4ffa49aa04 | ||
|
|
72837530bf | ||
|
|
eef635523a | ||
|
|
8f45a11919 | ||
|
|
e72d001708 | ||
|
|
8d9ea68f19 | ||
|
|
bf664c2e85 | ||
|
|
52d298890b | ||
|
|
c09e43acf5 | ||
|
|
3b4af01633 | ||
|
|
b4e2d5e8ee | ||
|
|
2984a7c121 | ||
|
|
80cc87b3d8 | ||
|
|
10b6047498 | ||
|
|
2c47b06869 | ||
|
|
31f2a325dc | ||
|
|
fcbbc24cc4 | ||
|
|
1454e1b6eb | ||
|
|
d70348836b | ||
|
|
940a930d13 | ||
|
|
45d21a0d5c | ||
|
|
15ad001aef | ||
|
|
ed1828ca92 | ||
|
|
3cfff5af0d | ||
|
|
6f6c66a07d | ||
|
|
d65af69c2b | ||
|
|
12c24c2189 |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -4,6 +4,66 @@ This changelog goes through the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
|
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
|
||||||
|
|
||||||
|
* BREAKING
|
||||||
|
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
||||||
|
* Package webhook’s Organization was incorrectly used as the User struct. This PR fixes the issue.
|
||||||
|
* This changelog is just a hint. The change is not really breaking because most fields are the same, most users are not affected.
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Clone button enhancements (#33362) (#33404)
|
||||||
|
* Repo homepage styling tweaks (#33289) (#33381)
|
||||||
|
* Add a confirm dialog for "sync fork" (#33270) (#33273)
|
||||||
|
* Make tracked time representation display as hours (#33315) (#33334)
|
||||||
|
* Improve sync fork behavior (#33319) (#33332)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix code button alignment (#33345) (#33351)
|
||||||
|
* Correct bot label `vertical-align` (#33477) (#33480)
|
||||||
|
* Fix SSH LFS memory usage (#33455) (#33460)
|
||||||
|
* Fix issue sidebar dropdown keyboard support (#33447) (#33450)
|
||||||
|
* Fix user avatar (#33439)
|
||||||
|
* Fix `GetCommitBranchStart` bug (#33298) (#33421)
|
||||||
|
* Add pubdate for repository rss and add some tests (#33411) (#33416)
|
||||||
|
* Add missed auto merge feed message on dashboard (#33309) (#33405)
|
||||||
|
* Fix issue suggestion bug (#33389) (#33391)
|
||||||
|
* Make issue suggestion work for all editors (#33340) (#33342)
|
||||||
|
* Fix issue count (#33338) (#33341)
|
||||||
|
* Fix Account linking page (#33325) (#33327)
|
||||||
|
* Fix closed dependency title (#33285) (#33287)
|
||||||
|
* Fix sidebar milestone link (#33269) (#33272)
|
||||||
|
* Fix missing license when sync mirror (#33255) (#33258)
|
||||||
|
* Fix upload file form (#33230) (#33233)
|
||||||
|
* Fix mirror bug (#33224) (#33225)
|
||||||
|
* Fix system admin cannot fork or get private fork with API (#33401) (#33417)
|
||||||
|
* Fix push message behavior (#33215) (#33317)
|
||||||
|
* Trivial fixes (#33304) (#33312)
|
||||||
|
* Fix "stop time tracking button" on navbar (#33084) (#33300)
|
||||||
|
* Fix tag route and empty repo (#33253)
|
||||||
|
* Fix cache test triggered by non memory cache (#33220) (#33221)
|
||||||
|
* Revert empty lfs ref name (#33454) (#33457)
|
||||||
|
* Fix flex width (#33414) (#33418)
|
||||||
|
* Fix commit status events (#33320) #33493
|
||||||
|
* Fix unnecessary comment when moving issue on the same project column (#33496) #33499
|
||||||
|
* Add timetzdata build tag to binary releases (#33463) #33503
|
||||||
|
* MISC
|
||||||
|
* Use ProtonMail/go-crypto to replace keybase/go-crypto (#33402) (#33410)
|
||||||
|
* Update katex to latest version (#33361)
|
||||||
|
* Update go tool dependencies (#32916) (#33355)
|
||||||
|
|
||||||
|
## [1.23.1](https://github.com/go-gitea/gitea/releases/tag/v1.23.1) - 2025-01-09
|
||||||
|
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Move repo size to sidebar (#33155) (#33182)
|
||||||
|
* BUGFIXES
|
||||||
|
* Use updated path to s6-svscan after alpine upgrade (#33185) (#33188)
|
||||||
|
* Fix fuzz test (#33156) (#33158)
|
||||||
|
* Fix raw file API ref handling (#33172) (#33189)
|
||||||
|
* Fix ACME panic (#33178) (#33186)
|
||||||
|
* Fix branch dropdown not display ref name (#33159) (#33183)
|
||||||
|
* Fix assignee list overlapping in Issue sidebar (#33176) (#33181)
|
||||||
|
* Fix sync fork for consistency (#33147) #33192
|
||||||
|
* Fix editor markdown not incrementing in a numbered list (#33187) #33193
|
||||||
|
|
||||||
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
|
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -26,17 +26,17 @@ COMMA := ,
|
|||||||
XGO_VERSION := go-1.23.x
|
XGO_VERSION := go-1.23.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3
|
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
DOCKER_IMAGE ?= gitea/gitea
|
||||||
DOCKER_TAG ?= latest
|
DOCKER_TAG ?= latest
|
||||||
|
|||||||
5
assets/go-licenses.json
generated
5
assets/go-licenses.json
generated
@@ -744,11 +744,6 @@
|
|||||||
"path": "github.com/kevinburke/ssh_config/LICENSE",
|
"path": "github.com/kevinburke/ssh_config/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2017 Kevin Burke.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n===================\n\nThe lexer and parser borrow heavily from github.com/pelletier/go-toml. The\nlicense for that project is copied below.\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
"licenseText": "Copyright (c) 2017 Kevin Burke.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n===================\n\nThe lexer and parser borrow heavily from github.com/pelletier/go-toml. The\nlicense for that project is copied below.\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "github.com/keybase/go-crypto",
|
|
||||||
"path": "github.com/keybase/go-crypto/LICENSE",
|
|
||||||
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "github.com/klauspost/compress",
|
"name": "github.com/klauspost/compress",
|
||||||
"path": "github.com/klauspost/compress/LICENSE",
|
"path": "github.com/klauspost/compress/LICENSE",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
var CmdMigrate = &cli.Command{
|
var CmdMigrate = &cli.Command{
|
||||||
Name: "migrate",
|
Name: "migrate",
|
||||||
Usage: "Migrate the database",
|
Usage: "Migrate the database",
|
||||||
Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
|
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
|
||||||
Action: runMigrate,
|
Action: runMigrate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,10 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
altTLSALPNPort = p
|
altTLSALPNPort = p
|
||||||
}
|
}
|
||||||
|
|
||||||
magic := &certmagic.Default
|
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
|
||||||
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||||
|
magic := certmagic.NewDefault()
|
||||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||||
var certPool *x509.CertPool
|
var certPool *x509.CertPool
|
||||||
if setting.AcmeCARoot != "" {
|
if setting.AcmeCARoot != "" {
|
||||||
|
|||||||
@@ -37,5 +37,5 @@ done
|
|||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
exec "$@"
|
exec "$@"
|
||||||
else
|
else
|
||||||
exec /bin/s6-svscan /etc/s6
|
exec /usr/bin/s6-svscan /etc/s6
|
||||||
fi
|
fi
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -79,7 +79,6 @@ require (
|
|||||||
github.com/jhillyerd/enmime v1.3.0
|
github.com/jhillyerd/enmime v1.3.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.17.11
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8
|
github.com/klauspost/cpuid/v2 v2.2.8
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -510,8 +510,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
|||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4=
|
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
|||||||
16
main_timezones.go
Normal file
16
main_timezones.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go)
|
||||||
|
// Even if the timezone data is missing, users could install the related packages to get it.
|
||||||
|
// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry,
|
||||||
|
// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235
|
||||||
|
// So we import the tzdata package to make sure the timezone data is included in the binary.
|
||||||
|
//
|
||||||
|
// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary.
|
||||||
|
// If we decided to add the tzdata for other platforms, modify the "go:build" directive above.
|
||||||
|
import _ "time/tzdata"
|
||||||
@@ -72,9 +72,9 @@ func (at ActionType) String() string {
|
|||||||
case ActionRenameRepo:
|
case ActionRenameRepo:
|
||||||
return "rename_repo"
|
return "rename_repo"
|
||||||
case ActionStarRepo:
|
case ActionStarRepo:
|
||||||
return "star_repo"
|
return "star_repo" // will not displayed in feeds.tmpl
|
||||||
case ActionWatchRepo:
|
case ActionWatchRepo:
|
||||||
return "watch_repo"
|
return "watch_repo" // will not displayed in feeds.tmpl
|
||||||
case ActionCommitRepo:
|
case ActionCommitRepo:
|
||||||
return "commit_repo"
|
return "commit_repo"
|
||||||
case ActionCreateIssue:
|
case ActionCreateIssue:
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -141,7 +141,11 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
|
|||||||
// Parse Subkeys
|
// Parse Subkeys
|
||||||
subkeys := make([]*GPGKey, len(e.Subkeys))
|
subkeys := make([]*GPGKey, len(e.Subkeys))
|
||||||
for i, k := range e.Subkeys {
|
for i, k := range e.Subkeys {
|
||||||
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
|
subkeyExpiry := expiry
|
||||||
|
if k.Sig.KeyLifetimeSecs != nil {
|
||||||
|
subkeyExpiry = k.PublicKey.CreationTime.Add(time.Duration(*k.Sig.KeyLifetimeSecs) * time.Second)
|
||||||
|
}
|
||||||
|
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, subkeyExpiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrGPGKeyParsing{ParseError: err}
|
return nil, ErrGPGKeyParsing{ParseError: err}
|
||||||
}
|
}
|
||||||
@@ -156,7 +160,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
|
|||||||
|
|
||||||
emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
|
emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
|
||||||
for _, ident := range e.Identities {
|
for _, ident := range e.Identities {
|
||||||
if ident.Revocation != nil {
|
if ident.Revoked(time.Now()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
|
email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
@@ -83,12 +83,12 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
|
|||||||
verified := false
|
verified := false
|
||||||
// Handle provided signature
|
// Handle provided signature
|
||||||
if signature != "" {
|
if signature != "" {
|
||||||
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature))
|
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature))
|
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature))
|
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to validate token signature. Error: %v", err)
|
log.Error("Unable to validate token signature. Error: %v", err)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
@@ -80,7 +80,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
|
|||||||
return pkey, nil
|
return pkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExpiryTime extract the expire time of primary key based on sig
|
// getExpiryTime extract the expiry time of primary key based on sig
|
||||||
func getExpiryTime(e *openpgp.Entity) time.Time {
|
func getExpiryTime(e *openpgp.Entity) time.Time {
|
||||||
expiry := time.Time{}
|
expiry := time.Time{}
|
||||||
// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
|
// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
|
||||||
@@ -88,12 +88,12 @@ func getExpiryTime(e *openpgp.Entity) time.Time {
|
|||||||
for _, ident := range e.Identities {
|
for _, ident := range e.Identities {
|
||||||
if selfSig == nil {
|
if selfSig == nil {
|
||||||
selfSig = ident.SelfSignature
|
selfSig = ident.SelfSignature
|
||||||
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
} else if ident.SelfSignature != nil && ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
||||||
selfSig = ident.SelfSignature
|
selfSig = ident.SelfSignature
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selfSig.KeyLifetimeSecs != nil {
|
if selfSig != nil && selfSig.KeyLifetimeSecs != nil {
|
||||||
expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
|
expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
|
||||||
}
|
}
|
||||||
return expiry
|
return expiry
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,3 +404,25 @@ func TestTryGetKeyIDFromSignature(t *testing.T) {
|
|||||||
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseGPGKey(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
assert.NoError(t, db.Insert(db.DefaultContext, &user_model.EmailAddress{UID: 1, Email: "email1@example.com", IsActivated: true}))
|
||||||
|
|
||||||
|
// create a key for test email
|
||||||
|
e, err := openpgp.NewEntity("name", "comment", "email1@example.com", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
k, err := parseGPGKey(db.DefaultContext, 1, e, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, k.KeyID)
|
||||||
|
assert.NotEmpty(t, k.Emails) // the key is valid, matches the email
|
||||||
|
|
||||||
|
// then revoke the key
|
||||||
|
for _, id := range e.Identities {
|
||||||
|
id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: util.ToPointer(packet.KeyCompromised)})
|
||||||
|
}
|
||||||
|
k, err = parseGPGKey(db.DefaultContext, 1, e, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, k.KeyID)
|
||||||
|
assert.Empty(t, k.Emails) // the key is revoked, matches no email
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
|||||||
BranchName: branchName,
|
BranchName: branchName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
|
||||||
|
// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
|
||||||
|
// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
|
||||||
return &branch, nil
|
return &branch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +443,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RecentlyPushedNewBranch struct {
|
type RecentlyPushedNewBranch struct {
|
||||||
|
BranchRepo *repo_model.Repository
|
||||||
|
BranchName string
|
||||||
BranchDisplayName string
|
BranchDisplayName string
|
||||||
BranchLink string
|
BranchLink string
|
||||||
BranchCompareURL string
|
BranchCompareURL string
|
||||||
@@ -540,7 +545,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
|||||||
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||||
}
|
}
|
||||||
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||||
|
BranchRepo: branch.Repo,
|
||||||
BranchDisplayName: branchDisplayName,
|
BranchDisplayName: branchDisplayName,
|
||||||
|
BranchName: branch.Name,
|
||||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||||
CommitTime: branch.CommitTime,
|
CommitTime: branch.CommitTime,
|
||||||
|
|||||||
@@ -38,13 +38,15 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProjectColumnID return project column id if issue was assigned to one
|
// ProjectColumnID return project column id if issue was assigned to one
|
||||||
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
|
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
|
||||||
var ip project_model.ProjectIssue
|
var ip project_model.ProjectIssue
|
||||||
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
||||||
if err != nil || !has {
|
if err != nil {
|
||||||
return 0
|
return 0, err
|
||||||
|
} else if !has {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
return ip.ProjectColumnID
|
return ip.ProjectColumnID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssuesFromColumn load issues assigned to this column
|
// LoadIssuesFromColumn load issues assigned to this column
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (s Stopwatch) Seconds() int64 {
|
|||||||
|
|
||||||
// Duration returns a human-readable duration string based on local server time
|
// Duration returns a human-readable duration string based on local server time
|
||||||
func (s Stopwatch) Duration() string {
|
func (s Stopwatch) Duration() string {
|
||||||
return util.SecToTime(s.Seconds())
|
return util.SecToHours(s.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
||||||
@@ -201,7 +201,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
|
|||||||
Doer: user,
|
Doer: user,
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Content: util.SecToTime(timediff),
|
Content: util.SecToHours(timediff),
|
||||||
Type: CommentTypeStopTracking,
|
Type: CommentTypeStopTracking,
|
||||||
TimeID: tt.ID,
|
TimeID: tt.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
|
|||||||
for _, o := range oldLicenses {
|
for _, o := range oldLicenses {
|
||||||
// Update already existing license
|
// Update already existing license
|
||||||
if o.License == license {
|
if o.License == license {
|
||||||
|
o.CommitID = commitID
|
||||||
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,27 +38,30 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
|
|||||||
|
|
||||||
u.Avatar = avatars.HashEmail(seed)
|
u.Avatar = avatars.HashEmail(seed)
|
||||||
|
|
||||||
// Don't share the images so that we can delete them easily
|
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
if err != nil {
|
||||||
if err := png.Encode(w, img); err != nil {
|
// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
|
||||||
log.Error("Encode: %v", err)
|
// Don't share the images so that we can delete them easily
|
||||||
|
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||||
|
if err := png.Encode(w, img); err != nil {
|
||||||
|
log.Error("Encode: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("New random avatar created: %d", u.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||||
if u.IsGhost() {
|
if u.IsGhost() || u.IsGiteaActions() {
|
||||||
return avatars.DefaultAvatarLink()
|
return avatars.DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,19 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserAvatarLink(t *testing.T) {
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
|
|||||||
link = u.AvatarLink(db.DefaultContext)
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserAvatarGenerate(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
var err error
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
|
||||||
|
|
||||||
|
// there was no avatar, generate a new one
|
||||||
|
assert.Empty(t, u.Avatar)
|
||||||
|
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, u.Avatar)
|
||||||
|
|
||||||
|
// make sure the generated one exists
|
||||||
|
oldAvatarPath := u.CustomAvatarRelativePath()
|
||||||
|
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||||
|
require.NoError(t, err)
|
||||||
|
// and try to change its content
|
||||||
|
_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// try to generate again
|
||||||
|
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
|
||||||
|
f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
content, _ := io.ReadAll(f)
|
||||||
|
assert.Equal(t, "abcd", string(content))
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ func NewGhostUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsGhostUserName(name string) bool {
|
||||||
|
return strings.EqualFold(name, GhostUserName)
|
||||||
|
}
|
||||||
|
|
||||||
// IsGhost check if user is fake user for a deleted account
|
// IsGhost check if user is fake user for a deleted account
|
||||||
func (u *User) IsGhost() bool {
|
func (u *User) IsGhost() bool {
|
||||||
if u == nil {
|
if u == nil {
|
||||||
@@ -48,6 +52,10 @@ const (
|
|||||||
ActionsEmail = "teabot@gitea.io"
|
ActionsEmail = "teabot@gitea.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsGiteaActionsUserName(name string) bool {
|
||||||
|
return strings.EqualFold(name, ActionsUserName)
|
||||||
|
}
|
||||||
|
|
||||||
// NewActionsUser creates and returns a fake user for running the actions.
|
// NewActionsUser creates and returns a fake user for running the actions.
|
||||||
func NewActionsUser() *User {
|
func NewActionsUser() *User {
|
||||||
return &User{
|
return &User{
|
||||||
@@ -65,6 +73,16 @@ func NewActionsUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) IsActions() bool {
|
func (u *User) IsGiteaActions() bool {
|
||||||
return u != nil && u.ID == ActionsUserID
|
return u != nil && u.ID == ActionsUserID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSystemUserByName(name string) *User {
|
||||||
|
if IsGhostUserName(name) {
|
||||||
|
return NewGhostUser()
|
||||||
|
}
|
||||||
|
if IsGiteaActionsUserName(name) {
|
||||||
|
return NewActionsUser()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
32
models/user/user_system_test.go
Normal file
32
models/user/user_system_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSystemUser(t *testing.T) {
|
||||||
|
u, err := GetPossibleUserByID(db.DefaultContext, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "Ghost", u.Name)
|
||||||
|
assert.Equal(t, "ghost", u.LowerName)
|
||||||
|
assert.True(t, u.IsGhost())
|
||||||
|
assert.True(t, IsGhostUserName("gHost"))
|
||||||
|
|
||||||
|
u, err = GetPossibleUserByID(db.DefaultContext, -2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "gitea-actions", u.Name)
|
||||||
|
assert.Equal(t, "gitea-actions", u.LowerName)
|
||||||
|
assert.True(t, u.IsGiteaActions())
|
||||||
|
assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
|
||||||
|
|
||||||
|
_, err = GetPossibleUserByID(db.DefaultContext, -3)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
@@ -299,6 +299,11 @@ func (w *Webhook) HasPackageEvent() bool {
|
|||||||
(w.ChooseEvents && w.HookEvents.Package)
|
(w.ChooseEvents && w.HookEvents.Package)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Webhook) HasStatusEvent() bool {
|
||||||
|
return w.SendEverything ||
|
||||||
|
(w.ChooseEvents && w.HookEvents.Status)
|
||||||
|
}
|
||||||
|
|
||||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
||||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
||||||
return w.SendEverything ||
|
return w.SendEverything ||
|
||||||
@@ -337,6 +342,7 @@ func (w *Webhook) EventCheckers() []struct {
|
|||||||
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
||||||
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
||||||
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
||||||
|
{w.HasStatusEvent, webhook_module.HookEventStatus},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||||||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||||
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
||||||
"package", "pull_request_review_request",
|
"package", "pull_request_review_request", "status",
|
||||||
},
|
},
|
||||||
(&Webhook{
|
(&Webhook{
|
||||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
|
|||||||
result, err := Auth("gitea", "user1", "false-pwd")
|
result, err := Auth("gitea", "user1", "false-pwd")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualError(t, err, "Authentication failure")
|
assert.EqualError(t, err, "Authentication failure")
|
||||||
assert.Len(t, result)
|
assert.Empty(t, result)
|
||||||
}
|
}
|
||||||
|
|||||||
9
modules/cache/cache.go
vendored
9
modules/cache/cache.go
vendored
@@ -37,10 +37,15 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testCacheKey = "DefaultCache.TestKey"
|
testCacheKey = "DefaultCache.TestKey"
|
||||||
SlowCacheThreshold = 100 * time.Microsecond
|
// SlowCacheThreshold marks cache tests as slow
|
||||||
|
// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
|
||||||
|
// TODO: Replace with metrics histogram
|
||||||
|
SlowCacheThreshold = 30 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test performs delete, put and get operations on a predefined key
|
||||||
|
// returns
|
||||||
func Test() (time.Duration, error) {
|
func Test() (time.Duration, error) {
|
||||||
if defaultCache == nil {
|
if defaultCache == nil {
|
||||||
return 0, fmt.Errorf("default cache not initialized")
|
return 0, fmt.Errorf("default cache not initialized")
|
||||||
|
|||||||
3
modules/cache/cache_test.go
vendored
3
modules/cache/cache_test.go
vendored
@@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
|
|||||||
elapsed, err := Test()
|
elapsed, err := Test()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
||||||
assert.Less(t, elapsed, time.Millisecond)
|
assert.Positive(t, elapsed)
|
||||||
|
assert.Less(t, elapsed, SlowCacheThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCache(t *testing.T) {
|
func TestGetCache(t *testing.T) {
|
||||||
|
|||||||
@@ -360,5 +360,5 @@ func Test_GetCommitBranchStart(t *testing.T) {
|
|||||||
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
|
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, startCommitID)
|
assert.NotEmpty(t, startCommitID)
|
||||||
assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
|
assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
|
|||||||
|
|
||||||
// Test pull names
|
// Test pull names
|
||||||
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
|
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
|
||||||
|
assert.True(t, RefName("refs/pull/1/head").IsPull())
|
||||||
|
assert.True(t, RefName("refs/pull/1/merge").IsPull())
|
||||||
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
|
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
|
||||||
|
|
||||||
// Test for branch names
|
// Test for branch names
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommitBranchStart returns the commit where the branch diverged
|
||||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
|
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
|
||||||
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
||||||
cmd.AddDynamicArguments(endCommitID)
|
cmd.AddDynamicArguments(endCommitID)
|
||||||
@@ -533,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
|||||||
|
|
||||||
parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
|
parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
|
||||||
|
|
||||||
var startCommitID string
|
// check the commits one by one until we find a commit contained by another branch
|
||||||
|
// and we think this commit is the divergence point
|
||||||
for _, commitID := range parts {
|
for _, commitID := range parts {
|
||||||
branches, err := repo.getBranches(env, string(commitID), 2)
|
branches, err := repo.getBranches(env, string(commitID), 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -541,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
|||||||
}
|
}
|
||||||
for _, b := range branches {
|
for _, b := range branches {
|
||||||
if b != branch {
|
if b != branch {
|
||||||
return startCommitID, nil
|
return string(commitID), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startCommitID = string(commitID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ func (r *Request) Param(key, value string) *Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body adds request raw body.
|
// Body adds request raw body. It supports string, []byte and io.Reader as body.
|
||||||
// it supports string and []byte.
|
|
||||||
func (r *Request) Body(data any) *Request {
|
func (r *Request) Body(data any) *Request {
|
||||||
switch t := data.(type) {
|
switch t := data.(type) {
|
||||||
|
case nil: // do nothing
|
||||||
case string:
|
case string:
|
||||||
bf := bytes.NewBufferString(t)
|
bf := bytes.NewBufferString(t)
|
||||||
r.req.Body = io.NopCloser(bf)
|
r.req.Body = io.NopCloser(bf)
|
||||||
@@ -111,6 +111,12 @@ func (r *Request) Body(data any) *Request {
|
|||||||
bf := bytes.NewBuffer(t)
|
bf := bytes.NewBuffer(t)
|
||||||
r.req.Body = io.NopCloser(bf)
|
r.req.Body = io.NopCloser(bf)
|
||||||
r.req.ContentLength = int64(len(t))
|
r.req.ContentLength = int64(len(t))
|
||||||
|
case io.ReadCloser:
|
||||||
|
r.req.Body = t
|
||||||
|
case io.Reader:
|
||||||
|
r.req.Body = io.NopCloser(t)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported request body type %T", t))
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -141,7 +147,7 @@ func (r *Request) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
|
} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
|
||||||
r.Header("Content-Type", "application/x-www-form-urlencoded")
|
r.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
r.Body(paramBody)
|
r.Body(paramBody) // string
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -185,6 +191,7 @@ func (r *Request) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Response executes request client gets response manually.
|
// Response executes request client gets response manually.
|
||||||
|
// Caller MUST close the response body if no error occurs
|
||||||
func (r *Request) Response() (*http.Response, error) {
|
func (r *Request) Response() (*http.Response, error) {
|
||||||
return r.getResponse()
|
return r.getResponse()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
|
|||||||
projectID = issue.Project.ID
|
projectID = issue.Project.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectColumnID, err := issue.ProjectColumnID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
return &internal.IndexerData{
|
return &internal.IndexerData{
|
||||||
ID: issue.ID,
|
ID: issue.ID,
|
||||||
RepoID: issue.RepoID,
|
RepoID: issue.RepoID,
|
||||||
@@ -106,7 +111,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
|
|||||||
NoLabel: len(labels) == 0,
|
NoLabel: len(labels) == 0,
|
||||||
MilestoneID: issue.MilestoneID,
|
MilestoneID: issue.MilestoneID,
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
ProjectColumnID: issue.ProjectColumnID(ctx),
|
ProjectColumnID: projectColumnID,
|
||||||
PosterID: issue.PosterID,
|
PosterID: issue.PosterID,
|
||||||
AssigneeID: issue.AssigneeID,
|
AssigneeID: issue.AssigneeID,
|
||||||
MentionIDs: mentionIDs,
|
MentionIDs: mentionIDs,
|
||||||
|
|||||||
@@ -72,10 +72,14 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
|
|||||||
|
|
||||||
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
|
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
|
||||||
|
|
||||||
|
// Original: In some lfs server implementations, they require the ref attribute. #32838
|
||||||
// `ref` is an "optional object describing the server ref that the objects belong to"
|
// `ref` is an "optional object describing the server ref that the objects belong to"
|
||||||
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
|
// but some (incorrect) lfs servers like aliyun require it, so maybe adding an empty ref here doesn't break the correct ones.
|
||||||
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
|
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
|
||||||
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
|
//
|
||||||
|
// UPDATE: it can't use "empty ref" here because it breaks others like https://github.com/go-gitea/gitea/issues/33453
|
||||||
|
request := &BatchRequest{operation, c.transferNames(), nil, objects}
|
||||||
|
|
||||||
payload := new(bytes.Buffer)
|
payload := new(bytes.Buffer)
|
||||||
err := json.NewEncoder(payload).Encode(request)
|
err := json.NewEncoder(payload).Encode(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -29,7 +28,7 @@ var Capabilities = []string{
|
|||||||
"locking",
|
"locking",
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ transfer.Backend = &GiteaBackend{}
|
var _ transfer.Backend = (*GiteaBackend)(nil)
|
||||||
|
|
||||||
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
|
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
|
||||||
type GiteaBackend struct {
|
type GiteaBackend struct {
|
||||||
@@ -78,17 +77,17 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
|
g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
|
||||||
return nil, statusCodeToErr(resp.StatusCode)
|
return nil, statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
respBytes, err := io.ReadAll(resp.Body)
|
respBytes, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http read error", err)
|
g.logger.Log("http read error", err)
|
||||||
@@ -158,8 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
return pointers, nil
|
return pointers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download implements transfer.Backend. The returned reader must be closed by the
|
// Download implements transfer.Backend. The returned reader must be closed by the caller.
|
||||||
// caller.
|
|
||||||
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
|
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
|
||||||
idMapStr, exists := args[argID]
|
idMapStr, exists := args[argID]
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -187,25 +185,25 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
|
|||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeOctetStream,
|
headerAccept: mimeOctetStream,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, fmt.Errorf("failed to get response: %w", err)
|
||||||
}
|
}
|
||||||
|
// no need to close the body here by "defer resp.Body.Close()", see below
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, 0, statusCodeToErr(resp.StatusCode)
|
return nil, 0, statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
respBytes, err := io.ReadAll(resp.Body)
|
respSize, err := strconv.ParseInt(resp.Header.Get("X-Gitea-LFS-Content-Length"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
|
||||||
}
|
}
|
||||||
respSize := int64(len(respBytes))
|
// transfer.Backend will check io.Closer interface and close this Body reader
|
||||||
respBuf := io.NopCloser(bytes.NewBuffer(respBytes))
|
return resp.Body, respSize, nil
|
||||||
return respBuf, respSize, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartUpload implements transfer.Backend.
|
// Upload implements transfer.Backend.
|
||||||
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
|
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
|
||||||
idMapStr, exists := args[argID]
|
idMapStr, exists := args[argID]
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -234,15 +232,14 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
|||||||
headerContentType: mimeOctetStream,
|
headerContentType: mimeOctetStream,
|
||||||
headerContentLength: strconv.FormatInt(size, 10),
|
headerContentLength: strconv.FormatInt(size, 10),
|
||||||
}
|
}
|
||||||
reqBytes, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
|
||||||
return err
|
req.Body(r)
|
||||||
}
|
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPut, headers, reqBytes)
|
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return statusCodeToErr(resp.StatusCode)
|
return statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
@@ -284,11 +281,12 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
|
|||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
|
return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
|
|||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
@@ -102,7 +102,7 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
|
|||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
@@ -185,7 +185,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er
|
|||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/proxyprotocol"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
||||||
)
|
)
|
||||||
@@ -89,53 +86,19 @@ func statusCodeToErr(code int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInternalRequest(ctx context.Context, url, method string, headers map[string]string, body []byte) *httplib.Request {
|
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
|
||||||
req := httplib.NewRequest(url, method).
|
req := private.NewInternalRequest(ctx, url, method)
|
||||||
SetContext(ctx).
|
|
||||||
SetTimeout(10*time.Second, 60*time.Second).
|
|
||||||
SetTLSClientConfig(&tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if setting.Protocol == setting.HTTPUnix {
|
|
||||||
req.SetTransport(&http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
var d net.Dialer
|
|
||||||
conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
|
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if setting.LocalUseProxyProtocol {
|
|
||||||
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if setting.LocalUseProxyProtocol {
|
|
||||||
req.SetTransport(&http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
var d net.Dialer
|
|
||||||
conn, err := d.DialContext(ctx, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header(k, v)
|
req.Header(k, v)
|
||||||
}
|
}
|
||||||
|
switch body := body.(type) {
|
||||||
req.Body(body)
|
case nil: // do nothing
|
||||||
|
case []byte:
|
||||||
|
req.Body(body) // []byte
|
||||||
|
case io.Reader:
|
||||||
|
req.Body(body) // io.Reader or io.ReadCloser
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported request body type %T", body))
|
||||||
|
}
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type GlobalVarsType struct {
|
|||||||
LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
|
LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalVars = sync.OnceValue[*GlobalVarsType](func() *GlobalVarsType {
|
var GlobalVars = sync.OnceValue(func() *GlobalVarsType {
|
||||||
v := &GlobalVarsType{}
|
v := &GlobalVarsType{}
|
||||||
v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||||
v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
|
v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ type globalVarsType struct {
|
|||||||
nulCleaner *strings.Replacer
|
nulCleaner *strings.Replacer
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue[*globalVarsType](func() *globalVarsType {
|
var globalVars = sync.OnceValue(func() *globalVarsType {
|
||||||
v := &globalVarsType{}
|
v := &globalVarsType{}
|
||||||
// NOTE: All below regex matching do not perform any extra validation.
|
// NOTE: All below regex matching do not perform any extra validation.
|
||||||
// Thus a link is produced even if the linked entity does not exist.
|
// Thus a link is produced even if the linked entity does not exist.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reAttrClass = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
|
var reAttrClass = sync.OnceValue(func() *regexp.Regexp {
|
||||||
// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
|
// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
|
||||||
return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
|
return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// it is copied from old code, which is quite doubtful whether it is correct
|
// it is copied from old code, which is quite doubtful whether it is correct
|
||||||
var reValidIconName = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
|
var reValidIconName = sync.OnceValue(func() *regexp.Regexp {
|
||||||
return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
|
return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
||||||
|
|
||||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
||||||
policy.AllowStyles("color", "background-color").OnElements("span", "p")
|
policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
|
||||||
|
|
||||||
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
|
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type GenerateTokenRequest struct {
|
|||||||
func GenerateActionsRunnerToken(ctx context.Context, scope string) (*ResponseText, ResponseExtra) {
|
func GenerateActionsRunnerToken(ctx context.Context, scope string) (*ResponseText, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
|
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{
|
req := newInternalRequestAPI(ctx, reqURL, "POST", GenerateTokenRequest{
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ type HookProcReceiveRefResult struct {
|
|||||||
// HookPreReceive check whether the provided commits are allowed
|
// HookPreReceive check whether the provided commits are allowed
|
||||||
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
|
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra
|
return extra
|
||||||
@@ -94,7 +94,7 @@ func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOp
|
|||||||
// HookPostReceive updates services and users
|
// HookPostReceive updates services and users
|
||||||
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
|
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||||
return requestJSONResp(req, &HookPostReceiveResult{})
|
return requestJSONResp(req, &HookPostReceiveResult{})
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookO
|
|||||||
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
|
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||||
return requestJSONResp(req, &HookProcReceiveResult{})
|
return requestJSONResp(req, &HookProcReceiveResult{})
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
|
|||||||
url.PathEscape(repoName),
|
url.PathEscape(repoName),
|
||||||
url.PathEscape(branch),
|
url.PathEscape(branch),
|
||||||
)
|
)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra
|
return extra
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
|
|||||||
// SSHLog sends ssh error log response
|
// SSHLog sends ssh error log response
|
||||||
func SSHLog(ctx context.Context, isErr bool, msg string) error {
|
func SSHLog(ctx context.Context, isErr bool, msg string) error {
|
||||||
reqURL := setting.LocalURL + "api/internal/ssh/log"
|
reqURL := setting.LocalURL + "api/internal/ssh/log"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
|
req := newInternalRequestAPI(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra.Error
|
return extra.Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func getClientIP() string {
|
|||||||
return strings.Fields(sshConnEnv)[0]
|
return strings.Fields(sshConnEnv)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInternalRequest(ctx context.Context, url, method string, body ...any) *httplib.Request {
|
func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
|
||||||
if setting.InternalToken == "" {
|
if setting.InternalToken == "" {
|
||||||
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
|
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
|
||||||
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
||||||
@@ -82,13 +82,17 @@ Ensure you are running in the correct environment or set the correct configurati
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
|
||||||
|
req := NewInternalRequest(ctx, url, method)
|
||||||
if len(body) == 1 {
|
if len(body) == 1 {
|
||||||
req.Header("Content-Type", "application/json")
|
req.Header("Content-Type", "application/json")
|
||||||
jsonBytes, _ := json.Marshal(body[0])
|
jsonBytes, _ := json.Marshal(body[0])
|
||||||
req.Body(jsonBytes)
|
req.Body(jsonBytes)
|
||||||
} else if len(body) > 1 {
|
} else if len(body) > 1 {
|
||||||
log.Fatal("Too many arguments for newInternalRequest")
|
log.Fatal("Too many arguments for newInternalRequestAPI")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetTimeout(10*time.Second, 60*time.Second)
|
req.SetTimeout(10*time.Second, 60*time.Second)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
||||||
// Ask for running deliver hook and test pull request tasks.
|
// Ask for running deliver hook and test pull request tasks.
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra.Error
|
return extra.Error
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
|||||||
func AuthorizedPublicKeyByContent(ctx context.Context, content string) (*ResponseText, ResponseExtra) {
|
func AuthorizedPublicKeyByContent(ctx context.Context, content string) (*ResponseText, ResponseExtra) {
|
||||||
// Ask for running deliver hook and test pull request tasks.
|
// Ask for running deliver hook and test pull request tasks.
|
||||||
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
|
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
req.Param("content", content)
|
req.Param("content", content)
|
||||||
return requestJSONResp(req, &ResponseText{})
|
return requestJSONResp(req, &ResponseText{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type Email struct {
|
|||||||
func SendEmail(ctx context.Context, subject, message string, to []string) (*ResponseText, ResponseExtra) {
|
func SendEmail(ctx context.Context, subject, message string, to []string) (*ResponseText, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + "api/internal/mail/send"
|
reqURL := setting.LocalURL + "api/internal/mail/send"
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", Email{
|
req := newInternalRequestAPI(ctx, reqURL, "POST", Email{
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
Message: message,
|
Message: message,
|
||||||
To: to,
|
To: to,
|
||||||
|
|||||||
@@ -18,21 +18,21 @@ import (
|
|||||||
// Shutdown calls the internal shutdown function
|
// Shutdown calls the internal shutdown function
|
||||||
func Shutdown(ctx context.Context) ResponseExtra {
|
func Shutdown(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/shutdown"
|
reqURL := setting.LocalURL + "api/internal/manager/shutdown"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Shutting down")
|
return requestJSONClientMsg(req, "Shutting down")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart calls the internal restart function
|
// Restart calls the internal restart function
|
||||||
func Restart(ctx context.Context) ResponseExtra {
|
func Restart(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/restart"
|
reqURL := setting.LocalURL + "api/internal/manager/restart"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Restarting")
|
return requestJSONClientMsg(req, "Restarting")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadTemplates calls the internal reload-templates function
|
// ReloadTemplates calls the internal reload-templates function
|
||||||
func ReloadTemplates(ctx context.Context) ResponseExtra {
|
func ReloadTemplates(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/reload-templates"
|
reqURL := setting.LocalURL + "api/internal/manager/reload-templates"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Reloaded")
|
return requestJSONClientMsg(req, "Reloaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ type FlushOptions struct {
|
|||||||
// FlushQueues calls the internal flush-queues function
|
// FlushQueues calls the internal flush-queues function
|
||||||
func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra {
|
func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/flush-queues"
|
reqURL := setting.LocalURL + "api/internal/manager/flush-queues"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", FlushOptions{Timeout: timeout, NonBlocking: nonBlocking})
|
req := newInternalRequestAPI(ctx, reqURL, "POST", FlushOptions{Timeout: timeout, NonBlocking: nonBlocking})
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
req.SetReadWriteTimeout(timeout + 10*time.Second)
|
req.SetReadWriteTimeout(timeout + 10*time.Second)
|
||||||
}
|
}
|
||||||
@@ -55,28 +55,28 @@ func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) R
|
|||||||
// PauseLogging pauses logging
|
// PauseLogging pauses logging
|
||||||
func PauseLogging(ctx context.Context) ResponseExtra {
|
func PauseLogging(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/pause-logging"
|
reqURL := setting.LocalURL + "api/internal/manager/pause-logging"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Logging Paused")
|
return requestJSONClientMsg(req, "Logging Paused")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResumeLogging resumes logging
|
// ResumeLogging resumes logging
|
||||||
func ResumeLogging(ctx context.Context) ResponseExtra {
|
func ResumeLogging(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/resume-logging"
|
reqURL := setting.LocalURL + "api/internal/manager/resume-logging"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Logging Restarted")
|
return requestJSONClientMsg(req, "Logging Restarted")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseReopenLogging releases and reopens logging files
|
// ReleaseReopenLogging releases and reopens logging files
|
||||||
func ReleaseReopenLogging(ctx context.Context) ResponseExtra {
|
func ReleaseReopenLogging(ctx context.Context) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/release-and-reopen-logging"
|
reqURL := setting.LocalURL + "api/internal/manager/release-and-reopen-logging"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Logging Restarted")
|
return requestJSONClientMsg(req, "Logging Restarted")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogSQL sets database logging
|
// SetLogSQL sets database logging
|
||||||
func SetLogSQL(ctx context.Context, on bool) ResponseExtra {
|
func SetLogSQL(ctx context.Context, on bool) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/set-log-sql?on=" + strconv.FormatBool(on)
|
reqURL := setting.LocalURL + "api/internal/manager/set-log-sql?on=" + strconv.FormatBool(on)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Log SQL setting set")
|
return requestJSONClientMsg(req, "Log SQL setting set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ type LoggerOptions struct {
|
|||||||
// AddLogger adds a logger
|
// AddLogger adds a logger
|
||||||
func AddLogger(ctx context.Context, logger, writer, mode string, config map[string]any) ResponseExtra {
|
func AddLogger(ctx context.Context, logger, writer, mode string, config map[string]any) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/manager/add-logger"
|
reqURL := setting.LocalURL + "api/internal/manager/add-logger"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", LoggerOptions{
|
req := newInternalRequestAPI(ctx, reqURL, "POST", LoggerOptions{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
@@ -103,7 +103,7 @@ func AddLogger(ctx context.Context, logger, writer, mode string, config map[stri
|
|||||||
// RemoveLogger removes a logger
|
// RemoveLogger removes a logger
|
||||||
func RemoveLogger(ctx context.Context, logger, writer string) ResponseExtra {
|
func RemoveLogger(ctx context.Context, logger, writer string) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(logger), url.PathEscape(writer))
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(logger), url.PathEscape(writer))
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
return requestJSONClientMsg(req, "Removed")
|
return requestJSONClientMsg(req, "Removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ func RemoveLogger(ctx context.Context, logger, writer string) ResponseExtra {
|
|||||||
func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces, json bool, cancel string) ResponseExtra {
|
func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces, json bool, cancel string) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/processes?flat=%t&no-system=%t&stacktraces=%t&json=%t&cancel-pid=%s", flat, noSystem, stacktraces, json, url.QueryEscape(cancel))
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/processes?flat=%t&no-system=%t&stacktraces=%t&json=%t&cancel-pid=%s", flat, noSystem, stacktraces, json, url.QueryEscape(cancel))
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "GET")
|
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
||||||
callback := func(resp *http.Response, extra *ResponseExtra) {
|
callback := func(resp *http.Response, extra *ResponseExtra) {
|
||||||
_, extra.Error = io.Copy(out, resp.Body)
|
_, extra.Error = io.Copy(out, resp.Body)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type RestoreParams struct {
|
|||||||
func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) ResponseExtra {
|
func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + "api/internal/restore_repo"
|
reqURL := setting.LocalURL + "api/internal/restore_repo"
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", RestoreParams{
|
req := newInternalRequestAPI(ctx, reqURL, "POST", RestoreParams{
|
||||||
RepoDir: repoDir,
|
RepoDir: repoDir,
|
||||||
OwnerName: ownerName,
|
OwnerName: ownerName,
|
||||||
RepoName: repoName,
|
RepoName: repoName,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type KeyAndOwner struct {
|
|||||||
// ServNoCommand returns information about the provided key
|
// ServNoCommand returns information about the provided key
|
||||||
func ServNoCommand(ctx context.Context, keyID int64) (*asymkey_model.PublicKey, *user_model.User, error) {
|
func ServNoCommand(ctx context.Context, keyID int64) (*asymkey_model.PublicKey, *user_model.User, error) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/none/%d", keyID)
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/none/%d", keyID)
|
||||||
req := newInternalRequest(ctx, reqURL, "GET")
|
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
||||||
keyAndOwner, extra := requestJSONResp(req, &KeyAndOwner{})
|
keyAndOwner, extra := requestJSONResp(req, &KeyAndOwner{})
|
||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
return nil, nil, extra.Error
|
return nil, nil, extra.Error
|
||||||
@@ -58,6 +58,6 @@ func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, m
|
|||||||
reqURL += fmt.Sprintf("&verb=%s", url.QueryEscape(verb))
|
reqURL += fmt.Sprintf("&verb=%s", url.QueryEscape(verb))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req := newInternalRequest(ctx, reqURL, "GET")
|
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
||||||
return requestJSONResp(req, &ServCommandResults{})
|
return requestJSONResp(req, &ServCommandResults{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
@@ -52,9 +51,6 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
|||||||
{
|
{
|
||||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "ref file is empty") {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
|
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func Clean(storage ObjectStorage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveFrom saves data to the ObjectStorage with path p from the callback
|
// SaveFrom saves data to the ObjectStorage with path p from the callback
|
||||||
func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) error) error {
|
func SaveFrom(objStorage ObjectStorage, path string, callback func(w io.Writer) error) error {
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
defer pr.Close()
|
defer pr.Close()
|
||||||
go func() {
|
go func() {
|
||||||
@@ -103,7 +103,7 @@ func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) err
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err := objStorage.Save(p, pr, -1)
|
_, err := objStorage.Save(path, pr, -1)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,14 +116,7 @@ var (
|
|||||||
_ Payloader = &PackagePayload{}
|
_ Payloader = &PackagePayload{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// _________ __
|
// CreatePayload represents a payload information of create event.
|
||||||
// \_ ___ \_______ ____ _____ _/ |_ ____
|
|
||||||
// / \ \/\_ __ \_/ __ \\__ \\ __\/ __ \
|
|
||||||
// \ \____| | \/\ ___/ / __ \| | \ ___/
|
|
||||||
// \______ /|__| \___ >____ /__| \___ >
|
|
||||||
// \/ \/ \/ \/
|
|
||||||
|
|
||||||
// CreatePayload FIXME
|
|
||||||
type CreatePayload struct {
|
type CreatePayload struct {
|
||||||
Sha string `json:"sha"`
|
Sha string `json:"sha"`
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
@@ -157,13 +150,6 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) {
|
|||||||
return hook, nil
|
return hook, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ________ .__ __
|
|
||||||
// \______ \ ____ | | _____/ |_ ____
|
|
||||||
// | | \_/ __ \| | _/ __ \ __\/ __ \
|
|
||||||
// | ` \ ___/| |_\ ___/| | \ ___/
|
|
||||||
// /_______ /\___ >____/\___ >__| \___ >
|
|
||||||
// \/ \/ \/ \/
|
|
||||||
|
|
||||||
// PusherType define the type to push
|
// PusherType define the type to push
|
||||||
type PusherType string
|
type PusherType string
|
||||||
|
|
||||||
@@ -186,13 +172,6 @@ func (p *DeletePayload) JSONPayload() ([]byte, error) {
|
|||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ___________ __
|
|
||||||
// \_ _____/__________| | __
|
|
||||||
// | __)/ _ \_ __ \ |/ /
|
|
||||||
// | \( <_> ) | \/ <
|
|
||||||
// \___ / \____/|__| |__|_ \
|
|
||||||
// \/ \/
|
|
||||||
|
|
||||||
// ForkPayload represents fork payload
|
// ForkPayload represents fork payload
|
||||||
type ForkPayload struct {
|
type ForkPayload struct {
|
||||||
Forkee *Repository `json:"forkee"`
|
Forkee *Repository `json:"forkee"`
|
||||||
@@ -232,13 +211,6 @@ func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
|
|||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// __________ .__
|
|
||||||
// \______ \ ____ | | ____ _____ ______ ____
|
|
||||||
// | _// __ \| | _/ __ \\__ \ / ___// __ \
|
|
||||||
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
|
|
||||||
// |____|_ /\___ >____/\___ >____ /____ >\___ >
|
|
||||||
// \/ \/ \/ \/ \/ \/
|
|
||||||
|
|
||||||
// HookReleaseAction defines hook release action type
|
// HookReleaseAction defines hook release action type
|
||||||
type HookReleaseAction string
|
type HookReleaseAction string
|
||||||
|
|
||||||
@@ -302,13 +274,6 @@ func (p *PushPayload) Branch() string {
|
|||||||
return strings.ReplaceAll(p.Ref, "refs/heads/", "")
|
return strings.ReplaceAll(p.Ref, "refs/heads/", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// .___
|
|
||||||
// | | ______ ________ __ ____
|
|
||||||
// | |/ ___// ___/ | \_/ __ \
|
|
||||||
// | |\___ \ \___ \| | /\ ___/
|
|
||||||
// |___/____ >____ >____/ \___ >
|
|
||||||
// \/ \/ \/
|
|
||||||
|
|
||||||
// HookIssueAction FIXME
|
// HookIssueAction FIXME
|
||||||
type HookIssueAction string
|
type HookIssueAction string
|
||||||
|
|
||||||
@@ -371,13 +336,6 @@ type ChangesPayload struct {
|
|||||||
Ref *ChangesFromPayload `json:"ref,omitempty"`
|
Ref *ChangesFromPayload `json:"ref,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// __________ .__ .__ __________ __
|
|
||||||
// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_
|
|
||||||
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
|
|
||||||
// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | |
|
|
||||||
// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__|
|
|
||||||
// \/ \/ |__| \/ \/
|
|
||||||
|
|
||||||
// PullRequestPayload represents a payload information of pull request event.
|
// PullRequestPayload represents a payload information of pull request event.
|
||||||
type PullRequestPayload struct {
|
type PullRequestPayload struct {
|
||||||
Action HookIssueAction `json:"action"`
|
Action HookIssueAction `json:"action"`
|
||||||
@@ -402,13 +360,6 @@ type ReviewPayload struct {
|
|||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// __ __.__ __ .__
|
|
||||||
// / \ / \__| | _|__|
|
|
||||||
// \ \/\/ / | |/ / |
|
|
||||||
// \ /| | <| |
|
|
||||||
// \__/\ / |__|__|_ \__|
|
|
||||||
// \/ \/
|
|
||||||
|
|
||||||
// HookWikiAction an action that happens to a wiki page
|
// HookWikiAction an action that happens to a wiki page
|
||||||
type HookWikiAction string
|
type HookWikiAction string
|
||||||
|
|
||||||
@@ -435,13 +386,6 @@ func (p *WikiPayload) JSONPayload() ([]byte, error) {
|
|||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
//__________ .__ __
|
|
||||||
//\______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
|
|
||||||
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
|
|
||||||
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
|
|
||||||
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
|
|
||||||
// \/ \/|__| \/ \/
|
|
||||||
|
|
||||||
// HookRepoAction an action that happens to a repo
|
// HookRepoAction an action that happens to a repo
|
||||||
type HookRepoAction string
|
type HookRepoAction string
|
||||||
|
|
||||||
@@ -480,7 +424,7 @@ type PackagePayload struct {
|
|||||||
Action HookPackageAction `json:"action"`
|
Action HookPackageAction `json:"action"`
|
||||||
Repository *Repository `json:"repository"`
|
Repository *Repository `json:"repository"`
|
||||||
Package *Package `json:"package"`
|
Package *Package `json:"package"`
|
||||||
Organization *User `json:"organization"`
|
Organization *Organization `json:"organization"`
|
||||||
Sender *User `json:"sender"`
|
Sender *User `json:"sender"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,3 +133,11 @@ type EditBranchProtectionOption struct {
|
|||||||
type UpdateBranchProtectionPriories struct {
|
type UpdateBranchProtectionPriories struct {
|
||||||
IDs []int64 `json:"ids"`
|
IDs []int64 `json:"ids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeUpstreamRequest struct {
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MergeUpstreamResponse struct {
|
||||||
|
MergeStyle string `json:"merge_type"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func NewFuncMap() template.FuncMap {
|
|||||||
// time / number / format
|
// time / number / format
|
||||||
"FileSize": base.FileSize,
|
"FileSize": base.FileSize,
|
||||||
"CountFmt": base.FormatNumberSI,
|
"CountFmt": base.FormatNumberSI,
|
||||||
"Sec2Time": util.SecToTime,
|
"Sec2Time": util.SecToHours,
|
||||||
|
|
||||||
"TimeEstimateString": timeEstimateString,
|
"TimeEstimateString": timeEstimateString,
|
||||||
|
|
||||||
|
|||||||
@@ -8,59 +8,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecToTime converts an amount of seconds to a human-readable string. E.g.
|
// SecToHours converts an amount of seconds to a human-readable hours string.
|
||||||
// 66s -> 1 minute 6 seconds
|
// This is stable for planning and managing timesheets.
|
||||||
// 52410s -> 14 hours 33 minutes
|
// Here it only supports hours and minutes, because a work day could contain 6 or 7 or 8 hours.
|
||||||
// 563418 -> 6 days 12 hours
|
func SecToHours(durationVal any) string {
|
||||||
// 1563418 -> 2 weeks 4 days
|
|
||||||
// 3937125s -> 1 month 2 weeks
|
|
||||||
// 45677465s -> 1 year 6 months
|
|
||||||
func SecToTime(durationVal any) string {
|
|
||||||
duration, _ := ToInt64(durationVal)
|
duration, _ := ToInt64(durationVal)
|
||||||
|
hours := duration / 3600
|
||||||
|
minutes := (duration / 60) % 60
|
||||||
|
|
||||||
formattedTime := ""
|
formattedTime := ""
|
||||||
|
formattedTime = formatTime(hours, "hour", formattedTime)
|
||||||
// The following four variables are calculated by taking
|
formattedTime = formatTime(minutes, "minute", formattedTime)
|
||||||
// into account the previously calculated variables, this avoids
|
|
||||||
// pitfalls when using remainders. As that could lead to incorrect
|
|
||||||
// results when the calculated number equals the quotient number.
|
|
||||||
remainingDays := duration / (60 * 60 * 24)
|
|
||||||
years := remainingDays / 365
|
|
||||||
remainingDays -= years * 365
|
|
||||||
months := remainingDays * 12 / 365
|
|
||||||
remainingDays -= months * 365 / 12
|
|
||||||
weeks := remainingDays / 7
|
|
||||||
remainingDays -= weeks * 7
|
|
||||||
days := remainingDays
|
|
||||||
|
|
||||||
// The following three variables are calculated without depending
|
|
||||||
// on the previous calculated variables.
|
|
||||||
hours := (duration / 3600) % 24
|
|
||||||
minutes := (duration / 60) % 60
|
|
||||||
seconds := duration % 60
|
|
||||||
|
|
||||||
// Extract only the relevant information of the time
|
|
||||||
// If the time is greater than a year, it makes no sense to display seconds.
|
|
||||||
switch {
|
|
||||||
case years > 0:
|
|
||||||
formattedTime = formatTime(years, "year", formattedTime)
|
|
||||||
formattedTime = formatTime(months, "month", formattedTime)
|
|
||||||
case months > 0:
|
|
||||||
formattedTime = formatTime(months, "month", formattedTime)
|
|
||||||
formattedTime = formatTime(weeks, "week", formattedTime)
|
|
||||||
case weeks > 0:
|
|
||||||
formattedTime = formatTime(weeks, "week", formattedTime)
|
|
||||||
formattedTime = formatTime(days, "day", formattedTime)
|
|
||||||
case days > 0:
|
|
||||||
formattedTime = formatTime(days, "day", formattedTime)
|
|
||||||
formattedTime = formatTime(hours, "hour", formattedTime)
|
|
||||||
case hours > 0:
|
|
||||||
formattedTime = formatTime(hours, "hour", formattedTime)
|
|
||||||
formattedTime = formatTime(minutes, "minute", formattedTime)
|
|
||||||
default:
|
|
||||||
formattedTime = formatTime(minutes, "minute", formattedTime)
|
|
||||||
formattedTime = formatTime(seconds, "second", formattedTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The formatTime() function always appends a space at the end. This will be trimmed
|
// The formatTime() function always appends a space at the end. This will be trimmed
|
||||||
return strings.TrimRight(formattedTime, " ")
|
return strings.TrimRight(formattedTime, " ")
|
||||||
@@ -76,6 +34,5 @@ func formatTime(value int64, name, formattedTime string) string {
|
|||||||
} else if value > 1 {
|
} else if value > 1 {
|
||||||
formattedTime = fmt.Sprintf("%s%d %ss ", formattedTime, value, name)
|
formattedTime = fmt.Sprintf("%s%d %ss ", formattedTime, value, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return formattedTime
|
return formattedTime
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,17 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSecToTime(t *testing.T) {
|
func TestSecToHours(t *testing.T) {
|
||||||
second := int64(1)
|
second := int64(1)
|
||||||
minute := 60 * second
|
minute := 60 * second
|
||||||
hour := 60 * minute
|
hour := 60 * minute
|
||||||
day := 24 * hour
|
day := 24 * hour
|
||||||
year := 365 * day
|
|
||||||
|
|
||||||
assert.Equal(t, "1 minute 6 seconds", SecToTime(minute+6*second))
|
assert.Equal(t, "1 minute", SecToHours(minute+6*second))
|
||||||
assert.Equal(t, "1 hour", SecToTime(hour))
|
assert.Equal(t, "1 hour", SecToHours(hour))
|
||||||
assert.Equal(t, "1 hour", SecToTime(hour+second))
|
assert.Equal(t, "1 hour", SecToHours(hour+second))
|
||||||
assert.Equal(t, "14 hours 33 minutes", SecToTime(14*hour+33*minute+30*second))
|
assert.Equal(t, "14 hours 33 minutes", SecToHours(14*hour+33*minute+30*second))
|
||||||
assert.Equal(t, "6 days 12 hours", SecToTime(6*day+12*hour+30*minute+18*second))
|
assert.Equal(t, "156 hours 30 minutes", SecToHours(6*day+12*hour+30*minute+18*second))
|
||||||
assert.Equal(t, "2 weeks 4 days", SecToTime((2*7+4)*day+2*hour+16*minute+58*second))
|
assert.Equal(t, "98 hours 16 minutes", SecToHours(4*day+2*hour+16*minute+58*second))
|
||||||
assert.Equal(t, "4 weeks", SecToTime(4*7*day))
|
assert.Equal(t, "672 hours", SecToHours(4*7*day))
|
||||||
assert.Equal(t, "4 weeks 1 day", SecToTime((4*7+1)*day))
|
|
||||||
assert.Equal(t, "1 month 2 weeks", SecToTime((6*7+3)*day+13*hour+38*minute+45*second))
|
|
||||||
assert.Equal(t, "11 months", SecToTime(year-25*day))
|
|
||||||
assert.Equal(t, "1 year 5 months", SecToTime(year+163*day+10*hour+11*minute+5*second))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type timeStrGlobalVarsType struct {
|
|||||||
// In the future, it could be some configurable options to help users
|
// In the future, it could be some configurable options to help users
|
||||||
// to convert the working time to different units.
|
// to convert the working time to different units.
|
||||||
|
|
||||||
var timeStrGlobalVars = sync.OnceValue[*timeStrGlobalVarsType](func() *timeStrGlobalVarsType {
|
var timeStrGlobalVars = sync.OnceValue(func() *timeStrGlobalVarsType {
|
||||||
v := &timeStrGlobalVarsType{}
|
v := &timeStrGlobalVarsType{}
|
||||||
v.re = regexp.MustCompile(`(?i)(\d+)\s*([hms])`)
|
v.re = regexp.MustCompile(`(?i)(\d+)\s*([hms])`)
|
||||||
v.units = []struct {
|
v.units = []struct {
|
||||||
|
|||||||
@@ -242,10 +242,10 @@ func TestReserveLineBreakForTextarea(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOptionalArg(t *testing.T) {
|
func TestOptionalArg(t *testing.T) {
|
||||||
foo := func(other any, optArg ...int) int {
|
foo := func(_ any, optArg ...int) int {
|
||||||
return OptionalArg(optArg)
|
return OptionalArg(optArg)
|
||||||
}
|
}
|
||||||
bar := func(other any, optArg ...int) int {
|
bar := func(_ any, optArg ...int) int {
|
||||||
return OptionalArg(optArg, 42)
|
return OptionalArg(optArg, 42)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 0, foo(nil))
|
assert.Equal(t, 0, foo(nil))
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type HookEvents struct {
|
|||||||
Repository bool `json:"repository"`
|
Repository bool `json:"repository"`
|
||||||
Release bool `json:"release"`
|
Release bool `json:"release"`
|
||||||
Package bool `json:"package"`
|
Package bool `json:"package"`
|
||||||
|
Status bool `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookEvent represents events that will delivery hook.
|
// HookEvent represents events that will delivery hook.
|
||||||
|
|||||||
@@ -38,14 +38,6 @@ const (
|
|||||||
// Event returns the HookEventType as an event string
|
// Event returns the HookEventType as an event string
|
||||||
func (h HookEventType) Event() string {
|
func (h HookEventType) Event() string {
|
||||||
switch h {
|
switch h {
|
||||||
case HookEventCreate:
|
|
||||||
return "create"
|
|
||||||
case HookEventDelete:
|
|
||||||
return "delete"
|
|
||||||
case HookEventFork:
|
|
||||||
return "fork"
|
|
||||||
case HookEventPush:
|
|
||||||
return "push"
|
|
||||||
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
|
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
|
||||||
return "issues"
|
return "issues"
|
||||||
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
|
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
|
||||||
@@ -59,14 +51,9 @@ func (h HookEventType) Event() string {
|
|||||||
return "pull_request_rejected"
|
return "pull_request_rejected"
|
||||||
case HookEventPullRequestReviewComment:
|
case HookEventPullRequestReviewComment:
|
||||||
return "pull_request_comment"
|
return "pull_request_comment"
|
||||||
case HookEventWiki:
|
default:
|
||||||
return "wiki"
|
return string(h)
|
||||||
case HookEventRepository:
|
|
||||||
return "repository"
|
|
||||||
case HookEventRelease:
|
|
||||||
return "release"
|
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookType is the type of a webhook
|
// HookType is the type of a webhook
|
||||||
|
|||||||
@@ -1951,6 +1951,7 @@ pulls.upstream_diverging_prompt_behind_1 = This branch is %[1]d commit behind %[
|
|||||||
pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
|
pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
|
||||||
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
|
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
|
||||||
pulls.upstream_diverging_merge = Sync fork
|
pulls.upstream_diverging_merge = Sync fork
|
||||||
|
pulls.upstream_diverging_merge_confirm = Would you like to merge "%[1]s" onto "%[2]s"?
|
||||||
|
|
||||||
pull.deleted_branch = (deleted):%s
|
pull.deleted_branch = (deleted):%s
|
||||||
pull.agit_documentation = Review documentation about AGit
|
pull.agit_documentation = Review documentation about AGit
|
||||||
@@ -2324,6 +2325,8 @@ settings.event_fork = Fork
|
|||||||
settings.event_fork_desc = Repository forked.
|
settings.event_fork_desc = Repository forked.
|
||||||
settings.event_wiki = Wiki
|
settings.event_wiki = Wiki
|
||||||
settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
|
settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
|
||||||
|
settings.event_statuses = Statuses
|
||||||
|
settings.event_statuses_desc = Commit Status updated from the API.
|
||||||
settings.event_release = Release
|
settings.event_release = Release
|
||||||
settings.event_release_desc = Release published, updated or deleted in a repository.
|
settings.event_release_desc = Release published, updated or deleted in a repository.
|
||||||
settings.event_push = Push
|
settings.event_push = Push
|
||||||
@@ -3560,7 +3563,8 @@ conda.install = To install the package using Conda, run the following command:
|
|||||||
container.details.type = Image Type
|
container.details.type = Image Type
|
||||||
container.details.platform = Platform
|
container.details.platform = Platform
|
||||||
container.pull = Pull the image from the command line:
|
container.pull = Pull the image from the command line:
|
||||||
container.digest = Digest:
|
container.images = Images
|
||||||
|
container.digest = Digest
|
||||||
container.multi_arch = OS / Arch
|
container.multi_arch = OS / Arch
|
||||||
container.layers = Image Layers
|
container.layers = Image Layers
|
||||||
container.labels = Labels
|
container.labels = Labels
|
||||||
|
|||||||
@@ -1687,7 +1687,6 @@ issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a hr
|
|||||||
issues.stop_tracking_history=`a fini de travailler sur <b>%s</b> %s.`
|
issues.stop_tracking_history=`a fini de travailler sur <b>%s</b> %s.`
|
||||||
issues.cancel_tracking_history=`a abandonné son minuteur %s.`
|
issues.cancel_tracking_history=`a abandonné son minuteur %s.`
|
||||||
issues.del_time=Supprimer ce minuteur du journal
|
issues.del_time=Supprimer ce minuteur du journal
|
||||||
issues.add_time_history=`a pointé du temps de travail %s.`
|
|
||||||
issues.del_time_history=`a supprimé son temps de travail %s.`
|
issues.del_time_history=`a supprimé son temps de travail %s.`
|
||||||
issues.add_time_manually=Temps pointé manuellement
|
issues.add_time_manually=Temps pointé manuellement
|
||||||
issues.add_time_hours=Heures
|
issues.add_time_hours=Heures
|
||||||
|
|||||||
@@ -1687,10 +1687,8 @@ issues.time_estimate_invalid=预计时间格式无效
|
|||||||
issues.start_tracking_history=`开始工作 %s`
|
issues.start_tracking_history=`开始工作 %s`
|
||||||
issues.tracker_auto_close=当此工单关闭时,自动停止计时器
|
issues.tracker_auto_close=当此工单关闭时,自动停止计时器
|
||||||
issues.tracking_already_started=`你已经开始对 <a href="%s">另一个工单</a> 进行时间跟踪!`
|
issues.tracking_already_started=`你已经开始对 <a href="%s">另一个工单</a> 进行时间跟踪!`
|
||||||
issues.stop_tracking_history=`停止工作 %s`
|
|
||||||
issues.cancel_tracking_history=`取消时间跟踪 %s`
|
issues.cancel_tracking_history=`取消时间跟踪 %s`
|
||||||
issues.del_time=删除此时间跟踪日志
|
issues.del_time=删除此时间跟踪日志
|
||||||
issues.add_time_history=`添加计时 %s`
|
|
||||||
issues.del_time_history=`已删除时间 %s`
|
issues.del_time_history=`已删除时间 %s`
|
||||||
issues.add_time_manually=手动添加时间
|
issues.add_time_manually=手动添加时间
|
||||||
issues.add_time_hours=小时
|
issues.add_time_hours=小时
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -33,7 +33,7 @@
|
|||||||
"htmx.org": "2.0.4",
|
"htmx.org": "2.0.4",
|
||||||
"idiomorph": "0.3.0",
|
"idiomorph": "0.3.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.11",
|
"katex": "0.16.21",
|
||||||
"license-checker-webpack-plugin": "0.2.1",
|
"license-checker-webpack-plugin": "0.2.1",
|
||||||
"mermaid": "11.4.1",
|
"mermaid": "11.4.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
@@ -11057,9 +11057,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/katex": {
|
"node_modules/katex": {
|
||||||
"version": "0.16.11",
|
"version": "0.16.21",
|
||||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz",
|
||||||
"integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==",
|
"integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://opencollective.com/katex",
|
"https://opencollective.com/katex",
|
||||||
"https://github.com/sponsors/katex"
|
"https://github.com/sponsors/katex"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"htmx.org": "2.0.4",
|
"htmx.org": "2.0.4",
|
||||||
"idiomorph": "0.3.0",
|
"idiomorph": "0.3.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.11",
|
"katex": "0.16.21",
|
||||||
"license-checker-webpack-plugin": "0.2.1",
|
"license-checker-webpack-plugin": "0.2.1",
|
||||||
"mermaid": "11.4.1",
|
"mermaid": "11.4.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
|
|||||||
@@ -1190,6 +1190,7 @@ func Routes() *web.Router {
|
|||||||
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
||||||
m.Combo("/forks").Get(repo.ListForks).
|
m.Combo("/forks").Get(repo.ListForks).
|
||||||
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
||||||
|
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Get("", repo.ListBranches)
|
m.Get("", repo.ListBranches)
|
||||||
m.Get("/*", repo.GetBranch)
|
m.Get("/*", repo.GetBranch)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@@ -1194,3 +1195,47 @@ func UpdateBranchProtectionPriories(ctx *context.APIContext) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MergeUpstream(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/merge-upstream repository repoMergeUpstream
|
||||||
|
// ---
|
||||||
|
// summary: Merge a branch from upstream
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/MergeUpstreamRequest"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/MergeUpstreamResponse"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
|
||||||
|
mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "MergeUpstream", err)
|
||||||
|
return
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "MergeUpstream", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "MergeUpstream", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, &api.MergeUpstreamResponse{MergeStyle: mergeStyle})
|
||||||
|
}
|
||||||
|
|||||||
@@ -132,13 +132,15 @@ func CreateFork(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
|
if !ctx.Doer.IsAdmin {
|
||||||
if err != nil {
|
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
|
||||||
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
if err != nil {
|
||||||
return
|
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
||||||
} else if !isMember {
|
return
|
||||||
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
|
} else if !isMember {
|
||||||
return
|
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
forker = org.AsUser()
|
forker = org.AsUser()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,3 +448,15 @@ type swaggerCompare struct {
|
|||||||
// in:body
|
// in:body
|
||||||
Body api.Compare `json:"body"`
|
Body api.Compare `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:response MergeUpstreamRequest
|
||||||
|
type swaggerMergeUpstreamRequest struct {
|
||||||
|
// in:body
|
||||||
|
Body api.MergeUpstreamRequest `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:response MergeUpstreamResponse
|
||||||
|
type swaggerMergeUpstreamResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body api.MergeUpstreamResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
|||||||
Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
||||||
Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
||||||
Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
||||||
|
Package: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
||||||
|
Status: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
||||||
},
|
},
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var tplLinkAccount base.TplName = "user/auth/link_account"
|
|||||||
|
|
||||||
// LinkAccount shows the page where the user can decide to login or create a new account
|
// LinkAccount shows the page where the user can decide to login or create a new account
|
||||||
func LinkAccount(ctx *context.Context) {
|
func LinkAccount(ctx *context.Context) {
|
||||||
|
// FIXME: these common template variables should be prepared in one common function, but not just copy-paste again and again.
|
||||||
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration
|
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration
|
||||||
ctx.Data["Title"] = ctx.Tr("link_account")
|
ctx.Data["Title"] = ctx.Tr("link_account")
|
||||||
ctx.Data["LinkAccountMode"] = true
|
ctx.Data["LinkAccountMode"] = true
|
||||||
@@ -43,6 +44,7 @@ func LinkAccount(ctx *context.Context) {
|
|||||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||||
|
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||||
ctx.Data["ShowRegistrationButton"] = false
|
ctx.Data["ShowRegistrationButton"] = false
|
||||||
|
|
||||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||||
@@ -50,6 +52,11 @@ func LinkAccount(ctx *context.Context) {
|
|||||||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
|
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
|
||||||
|
|
||||||
gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
|
gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
|
||||||
|
|
||||||
|
// If you'd like to quickly debug the "link account" page layout, just uncomment the blow line
|
||||||
|
// Don't worry, when the below line exists, the lint won't pass: ineffectual assignment to gothUser (ineffassign)
|
||||||
|
// gothUser, ok = goth.User{Email: "invalid-email", Name: "."}, true // intentionally use invalid data to avoid pass the registration check
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// no account in session, so just redirect to the login page, then the user could restart the process
|
// no account in session, so just redirect to the login page, then the user could restart the process
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/login")
|
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||||
@@ -135,6 +142,8 @@ func LinkAccountPostSignIn(ctx *context.Context) {
|
|||||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||||
|
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||||
|
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||||
ctx.Data["ShowRegistrationButton"] = false
|
ctx.Data["ShowRegistrationButton"] = false
|
||||||
|
|
||||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||||
@@ -223,6 +232,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
|
|||||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||||
|
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||||
|
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||||
ctx.Data["ShowRegistrationButton"] = false
|
ctx.Data["ShowRegistrationButton"] = false
|
||||||
|
|
||||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType stri
|
|||||||
},
|
},
|
||||||
Description: commit.Message(),
|
Description: commit.Message(),
|
||||||
Content: commit.Message(),
|
Content: commit.Message(),
|
||||||
|
Created: commit.Committer.When,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
|
|||||||
},
|
},
|
||||||
Description: commit.Message(),
|
Description: commit.Message(),
|
||||||
Content: commit.Message(),
|
Content: commit.Message(),
|
||||||
|
Created: commit.Committer.When,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -855,7 +855,7 @@ func Run(ctx *context_module.Context) {
|
|||||||
inputs := make(map[string]any)
|
inputs := make(map[string]any)
|
||||||
if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil {
|
if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil {
|
||||||
for name, config := range workflowDispatch.Inputs {
|
for name, config := range workflowDispatch.Inputs {
|
||||||
value := ctx.Req.PostForm.Get(name)
|
value := ctx.Req.PostFormValue(name)
|
||||||
if config.Type == "boolean" {
|
if config.Type == "boolean" {
|
||||||
// https://www.w3.org/TR/html401/interact/forms.html
|
// https://www.w3.org/TR/html401/interact/forms.html
|
||||||
// https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked
|
// https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func RemoveDependency(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dependency Type
|
// Dependency Type
|
||||||
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
|
depTypeStr := ctx.Req.PostFormValue("dependencyType")
|
||||||
|
|
||||||
var depType issues_model.DependencyType
|
var depType issues_model.DependencyType
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func DeleteTime(c *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToTime(t.Time)))
|
c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToHours(t.Time)))
|
||||||
c.JSONRedirect("")
|
c.JSONRedirect("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func IssueWatch(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch"))
|
watch, err := strconv.ParseBool(ctx.Req.PostFormValue("watch"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("watch is not bool", err)
|
ctx.ServerError("watch is not bool", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -701,9 +701,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
|
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
} else if prInfo == nil {
|
|
||||||
ctx.NotFound("ViewPullFiles", nil)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
|
headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
|
|||||||
Wiki: form.Wiki,
|
Wiki: form.Wiki,
|
||||||
Repository: form.Repository,
|
Repository: form.Repository,
|
||||||
Package: form.Package,
|
Package: form.Package,
|
||||||
|
Status: form.Status,
|
||||||
},
|
},
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,10 +215,28 @@ func prepareRecentlyPushedNewBranches(ctx *context.Context) {
|
|||||||
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||||||
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||||||
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
var finalBranches []*git_model.RecentlyPushedNewBranch
|
||||||
|
branches, err := git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
|
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, branch := range branches {
|
||||||
|
divergingInfo, err := repo_service.GetBranchDivergingInfo(ctx,
|
||||||
|
branch.BranchRepo, branch.BranchName, // "base" repo for diverging info
|
||||||
|
opts.BaseRepo, opts.BaseRepo.DefaultBranch, // "head" repo for diverging info
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetBranchDivergingInfo failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
branchRepoHasNewCommits := divergingInfo.BaseHasNewCommits
|
||||||
|
baseRepoCommitsBehind := divergingInfo.HeadCommitsBehind
|
||||||
|
if branchRepoHasNewCommits || baseRepoCommitsBehind > 0 {
|
||||||
|
finalBranches = append(finalBranches, branch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Data["RecentlyPushedNewBranches"] = finalBranches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +267,7 @@ func handleRepoEmptyOrBroken(ctx *context.Context) {
|
|||||||
} else if reallyEmpty {
|
} else if reallyEmpty {
|
||||||
showEmpty = true // the repo is really empty
|
showEmpty = true // the repo is really empty
|
||||||
updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady)
|
updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady)
|
||||||
} else if ctx.Repo.Commit == nil {
|
} else if branches, _, _ := ctx.Repo.GitRepo.GetBranches(0, 1); len(branches) == 0 {
|
||||||
showEmpty = true // it is not really empty, but there is no branch
|
showEmpty = true // it is not really empty, but there is no branch
|
||||||
// at the moment, other repo units like "actions" are not able to handle such case,
|
// at the moment, other repo units like "actions" are not able to handle such case,
|
||||||
// so we just mark the repo as empty to prevent from displaying these units.
|
// so we just mark the repo as empty to prevent from displaying these units.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/avatars"
|
"code.gitea.io/gitea/models/avatars"
|
||||||
@@ -21,32 +20,23 @@ func cacheableRedirect(ctx *context.Context, location string) {
|
|||||||
ctx.Redirect(location)
|
ctx.Redirect(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarByUserName redirect browser to user avatar of requested size
|
// AvatarByUsernameSize redirect browser to user avatar of requested size
|
||||||
func AvatarByUserName(ctx *context.Context) {
|
func AvatarByUsernameSize(ctx *context.Context) {
|
||||||
userName := ctx.PathParam(":username")
|
username := ctx.PathParam("username")
|
||||||
size := int(ctx.PathParamInt64(":size"))
|
user := user_model.GetSystemUserByName(username)
|
||||||
|
if user == nil {
|
||||||
var user *user_model.User
|
|
||||||
if strings.ToLower(userName) != user_model.GhostUserLowerName {
|
|
||||||
var err error
|
var err error
|
||||||
if user, err = user_model.GetUserByName(ctx, userName); err != nil {
|
if user, err = user_model.GetUserByName(ctx, username); err != nil {
|
||||||
if user_model.IsErrUserNotExist(err) {
|
ctx.NotFoundOrServerError("GetUserByName", user_model.IsErrUserNotExist, err)
|
||||||
ctx.NotFound("GetUserByName", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.ServerError("Invalid user: "+userName, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
user = user_model.NewGhostUser()
|
|
||||||
}
|
}
|
||||||
|
cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, int(ctx.PathParamInt64("size"))))
|
||||||
cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarByEmailHash redirects the browser to the email avatar link
|
// AvatarByEmailHash redirects the browser to the email avatar link
|
||||||
func AvatarByEmailHash(ctx *context.Context) {
|
func AvatarByEmailHash(ctx *context.Context) {
|
||||||
hash := ctx.PathParam(":hash")
|
hash := ctx.PathParam("hash")
|
||||||
email, err := avatars.GetEmailForHash(ctx, hash)
|
email, err := avatars.GetEmailForHash(ctx, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("invalid avatar hash: "+hash, err)
|
ctx.ServerError("invalid avatar hash: "+hash, err)
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import (
|
|||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -578,17 +578,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
|||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Fill stats to post to ctx.Data.
|
// Fill stats to post to ctx.Data.
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
issueStats, err := getUserIssueStats(ctx, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
|
issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
|
||||||
func(o *issue_indexer.SearchOptions) {
|
func(o *issue_indexer.SearchOptions) {
|
||||||
o.IsFuzzyKeyword = isFuzzy
|
o.IsFuzzyKeyword = isFuzzy
|
||||||
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
|
|
||||||
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
|
|
||||||
// because the doer may create issues or be mentioned in any public repo.
|
|
||||||
// So we need search issues in all public repos.
|
|
||||||
o.AllPublic = ctx.Doer.ID == ctxUser.ID
|
|
||||||
o.MentionID = nil
|
|
||||||
o.ReviewRequestedID = nil
|
|
||||||
o.ReviewedID = nil
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -742,7 +734,7 @@ func UsernameSubRoute(ctx *context.Context) {
|
|||||||
switch {
|
switch {
|
||||||
case strings.HasSuffix(username, ".png"):
|
case strings.HasSuffix(username, ".png"):
|
||||||
if reloadParam(".png") {
|
if reloadParam(".png") {
|
||||||
AvatarByUserName(ctx)
|
AvatarByUsernameSize(ctx)
|
||||||
}
|
}
|
||||||
case strings.HasSuffix(username, ".keys"):
|
case strings.HasSuffix(username, ".keys"):
|
||||||
if reloadParam(".keys") {
|
if reloadParam(".keys") {
|
||||||
@@ -777,10 +769,19 @@ func UsernameSubRoute(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
|
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
|
||||||
ret = &issues_model.IssueStats{}
|
ret = &issues_model.IssueStats{}
|
||||||
doerID := ctx.Doer.ID
|
doerID := ctx.Doer.ID
|
||||||
|
|
||||||
|
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
|
||||||
|
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
|
||||||
|
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
|
||||||
|
// because the doer may create issues or be mentioned in any public repo.
|
||||||
|
// So we need search issues in all public repos.
|
||||||
|
o.AllPublic = doerID == ctxUser.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
// Open/Closed are for the tabs of the issue list
|
||||||
{
|
{
|
||||||
openClosedOpts := opts.Copy()
|
openClosedOpts := opts.Copy()
|
||||||
switch filterMode {
|
switch filterMode {
|
||||||
@@ -811,6 +812,15 @@ func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Below stats are for the left sidebar
|
||||||
|
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
|
||||||
|
o.AssigneeID = nil
|
||||||
|
o.PosterID = nil
|
||||||
|
o.MentionID = nil
|
||||||
|
o.ReviewRequestedID = nil
|
||||||
|
o.ReviewedID = nil
|
||||||
|
})
|
||||||
|
|
||||||
ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AllPublic = false }))
|
ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AllPublic = false }))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("/activate", auth.Activate)
|
m.Get("/activate", auth.Activate)
|
||||||
m.Post("/activate", auth.ActivatePost)
|
m.Post("/activate", auth.ActivatePost)
|
||||||
m.Any("/activate_email", auth.ActivateEmail)
|
m.Any("/activate_email", auth.ActivateEmail)
|
||||||
m.Get("/avatar/{username}/{size}", user.AvatarByUserName)
|
m.Get("/avatar/{username}/{size}", user.AvatarByUsernameSize)
|
||||||
m.Get("/recover_account", auth.ResetPasswd)
|
m.Get("/recover_account", auth.ResetPasswd)
|
||||||
m.Post("/recover_account", auth.ResetPasswdPost)
|
m.Post("/recover_account", auth.ResetPasswdPost)
|
||||||
m.Get("/forgot_password", auth.ForgotPasswd)
|
m.Get("/forgot_password", auth.ForgotPasswd)
|
||||||
@@ -1335,8 +1335,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
|
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
|
||||||
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
||||||
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
|
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
|
||||||
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
|
m.Post("/tags/delete", reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.DeleteTag)
|
||||||
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
|
|
||||||
}, optSignIn, context.RepoAssignment, reqRepoCodeReader)
|
}, optSignIn, context.RepoAssignment, reqRepoCodeReader)
|
||||||
// end "/{username}/{reponame}": repo tags
|
// end "/{username}/{reponame}": repo tags
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func (input *notifyInput) Notify(ctx context.Context) {
|
|||||||
|
|
||||||
func notify(ctx context.Context, input *notifyInput) error {
|
func notify(ctx context.Context, input *notifyInput) error {
|
||||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||||
if input.Doer.IsActions() {
|
if input.Doer.IsGiteaActions() {
|
||||||
// avoiding triggering cyclically, for example:
|
// avoiding triggering cyclically, for example:
|
||||||
// a comment of an issue will trigger the runner to add a new comment as reply,
|
// a comment of an issue will trigger the runner to add a new comment as reply,
|
||||||
// and the new comment will trigger the runner again.
|
// and the new comment will trigger the runner again.
|
||||||
|
|||||||
@@ -305,8 +305,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTICE: the "ref" here for internal usage only (e.g. woodpecker)
|
refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.PathParam("*"), ctx.FormTrim("ref"))
|
||||||
refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.FormTrim("ref"))
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||||
|
|||||||
@@ -769,35 +769,30 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
|
func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (string, RepoRefType) {
|
||||||
extraRef := util.OptionalArg(optionalExtraRef)
|
reqRefPath := path.Join(extraRef, reqPath)
|
||||||
reqPath := ctx.PathParam("*")
|
reqRefPathParts := strings.Split(reqRefPath, "/")
|
||||||
reqPath = path.Join(extraRef, reqPath)
|
if refName := getRefName(ctx, repo, reqRefPath, RepoRefBranch); refName != "" {
|
||||||
|
|
||||||
if refName := getRefName(ctx, repo, RepoRefBranch); refName != "" {
|
|
||||||
return refName, RepoRefBranch
|
return refName, RepoRefBranch
|
||||||
}
|
}
|
||||||
if refName := getRefName(ctx, repo, RepoRefTag); refName != "" {
|
if refName := getRefName(ctx, repo, reqRefPath, RepoRefTag); refName != "" {
|
||||||
return refName, RepoRefTag
|
return refName, RepoRefTag
|
||||||
}
|
}
|
||||||
|
if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) {
|
||||||
// For legacy support only full commit sha
|
|
||||||
parts := strings.Split(reqPath, "/")
|
|
||||||
if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
|
|
||||||
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
||||||
repo.TreePath = strings.Join(parts[1:], "/")
|
repo.TreePath = strings.Join(reqRefPathParts[1:], "/")
|
||||||
return parts[0], RepoRefCommit
|
return reqRefPathParts[0], RepoRefCommit
|
||||||
}
|
}
|
||||||
|
if refName := getRefName(ctx, repo, reqPath, RepoRefBlob); refName != "" {
|
||||||
if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
|
|
||||||
return refName, RepoRefBlob
|
return refName, RepoRefBlob
|
||||||
}
|
}
|
||||||
|
// FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case:
|
||||||
|
// "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404
|
||||||
repo.TreePath = reqPath
|
repo.TreePath = reqPath
|
||||||
return repo.Repository.DefaultBranch, RepoRefBranch
|
return repo.Repository.DefaultBranch, RepoRefBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
func getRefName(ctx *Base, repo *Repository, path string, pathType RepoRefType) string {
|
||||||
path := ctx.PathParam("*")
|
|
||||||
switch pathType {
|
switch pathType {
|
||||||
case RepoRefBranch:
|
case RepoRefBranch:
|
||||||
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
|
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
|
||||||
@@ -900,7 +895,8 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get default branch.
|
// Get default branch.
|
||||||
if len(ctx.PathParam("*")) == 0 {
|
reqPath := ctx.PathParam("*")
|
||||||
|
if reqPath == "" {
|
||||||
refName = ctx.Repo.Repository.DefaultBranch
|
refName = ctx.Repo.Repository.DefaultBranch
|
||||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||||
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
|
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
|
||||||
@@ -925,12 +921,12 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
|||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
ctx.Repo.IsViewBranch = true
|
ctx.Repo.IsViewBranch = true
|
||||||
} else {
|
} else { // there is a path in request
|
||||||
guessLegacyPath := refType == RepoRefUnknown
|
guessLegacyPath := refType == RepoRefUnknown
|
||||||
if guessLegacyPath {
|
if guessLegacyPath {
|
||||||
refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo)
|
refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "")
|
||||||
} else {
|
} else {
|
||||||
refName = getRefName(ctx.Base, ctx.Repo, refType)
|
refName = getRefName(ctx.Base, ctx.Repo, reqPath, refType)
|
||||||
}
|
}
|
||||||
ctx.Repo.RefName = refName
|
ctx.Repo.RefName = refName
|
||||||
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
|
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
|
func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
|
||||||
@@ -186,7 +187,7 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
|
|||||||
result = append(result, api.StopWatch{
|
result = append(result, api.StopWatch{
|
||||||
Created: sw.CreatedUnix.AsTime(),
|
Created: sw.CreatedUnix.AsTime(),
|
||||||
Seconds: sw.Seconds(),
|
Seconds: sw.Seconds(),
|
||||||
Duration: sw.Duration(),
|
Duration: util.SecToHours(sw.Seconds()),
|
||||||
IssueIndex: issue.Index,
|
IssueIndex: issue.Index,
|
||||||
IssueTitle: issue.Title,
|
IssueTitle: issue.Title,
|
||||||
RepoOwnerName: repo.OwnerName,
|
RepoOwnerName: repo.OwnerName,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
|
|||||||
c.Content[0] == '|' {
|
c.Content[0] == '|' {
|
||||||
// TimeTracking Comments from v1.21 on store the seconds instead of an formatted string
|
// TimeTracking Comments from v1.21 on store the seconds instead of an formatted string
|
||||||
// so we check for the "|" delimiter and convert new to legacy format on demand
|
// so we check for the "|" delimiter and convert new to legacy format on demand
|
||||||
c.Content = util.SecToTime(c.Content[1:])
|
c.Content = util.SecToHours(c.Content[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Type == issues_model.CommentTypeChangeTimeEstimate {
|
if c.Type == issues_model.CommentTypeChangeTimeEstimate {
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ type WebhookForm struct {
|
|||||||
Wiki bool
|
Wiki bool
|
||||||
Repository bool
|
Repository bool
|
||||||
Package bool
|
Package bool
|
||||||
|
Status bool
|
||||||
Active bool
|
Active bool
|
||||||
BranchFilter string `binding:"GlobPattern"`
|
BranchFilter string `binding:"GlobPattern"`
|
||||||
AuthorizationHeader string
|
AuthorizationHeader string
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ func DownloadHandler(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentLength := toByte + 1 - fromByte
|
contentLength := toByte + 1 - fromByte
|
||||||
ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
contentLengthStr := strconv.FormatInt(contentLength, 10)
|
||||||
|
ctx.Resp.Header().Set("Content-Length", contentLengthStr)
|
||||||
|
ctx.Resp.Header().Set("X-Gitea-LFS-Content-Length", contentLengthStr) // we need this header to make sure it won't be affected by reverse proxy or compression
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
filename := ctx.PathParam("filename")
|
filename := ctx.PathParam("filename")
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ type mirrorSyncResult struct {
|
|||||||
/*
|
/*
|
||||||
// * [new tag] v0.1.8 -> v0.1.8
|
// * [new tag] v0.1.8 -> v0.1.8
|
||||||
// * [new branch] master -> origin/master
|
// * [new branch] master -> origin/master
|
||||||
|
// * [new ref] refs/pull/2/head -> refs/pull/2/head"
|
||||||
// - [deleted] (none) -> origin/test // delete a branch
|
// - [deleted] (none) -> origin/test // delete a branch
|
||||||
// - [deleted] (none) -> 1 // delete a tag
|
// - [deleted] (none) -> 1 // delete a tag
|
||||||
// 957a993..a87ba5f test -> origin/test
|
// 957a993..a87ba5f test -> origin/test
|
||||||
@@ -127,6 +128,11 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
|
|||||||
refName: git.RefNameFromBranch(refName),
|
refName: git.RefNameFromBranch(refName),
|
||||||
oldCommitID: gitShortEmptySha,
|
oldCommitID: gitShortEmptySha,
|
||||||
})
|
})
|
||||||
|
case strings.HasPrefix(lines[i], " * [new ref]"): // new reference
|
||||||
|
results = append(results, &mirrorSyncResult{
|
||||||
|
refName: git.RefName(refName),
|
||||||
|
oldCommitID: gitShortEmptySha,
|
||||||
|
})
|
||||||
case strings.HasPrefix(lines[i], " - "): // Delete reference
|
case strings.HasPrefix(lines[i], " - "): // Delete reference
|
||||||
isTag := !strings.HasPrefix(refName, remoteName+"/")
|
isTag := !strings.HasPrefix(refName, remoteName+"/")
|
||||||
var refFullName git.RefName
|
var refFullName git.RefName
|
||||||
@@ -169,8 +175,15 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
|
|||||||
log.Error("Expect two SHAs but not what found: %q", lines[i])
|
log.Error("Expect two SHAs but not what found: %q", lines[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
var refFullName git.RefName
|
||||||
|
if strings.HasPrefix(refName, "refs/") {
|
||||||
|
refFullName = git.RefName(refName)
|
||||||
|
} else {
|
||||||
|
refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/"))
|
||||||
|
}
|
||||||
|
|
||||||
results = append(results, &mirrorSyncResult{
|
results = append(results, &mirrorSyncResult{
|
||||||
refName: git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")),
|
refName: refFullName,
|
||||||
oldCommitID: shas[0],
|
oldCommitID: shas[0],
|
||||||
newCommitID: shas[1],
|
newCommitID: shas[1],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"github.com/keybase/go-crypto/openpgp/clearsign"
|
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"github.com/ulikunitz/xz"
|
"github.com/ulikunitz/xz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -55,22 +55,29 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
|
projectColumnID, err := curIssue.ProjectColumnID(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add timeline to issue
|
if projectColumnID != column.ID {
|
||||||
if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
|
// add timeline to issue
|
||||||
Type: issues_model.CommentTypeProjectColumn,
|
if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
|
||||||
Doer: doer,
|
Type: issues_model.CommentTypeProjectColumn,
|
||||||
Repo: curIssue.Repo,
|
Doer: doer,
|
||||||
Issue: curIssue,
|
Repo: curIssue.Repo,
|
||||||
ProjectID: column.ProjectID,
|
Issue: curIssue,
|
||||||
ProjectTitle: project.Title,
|
ProjectID: column.ProjectID,
|
||||||
ProjectColumnID: column.ID,
|
ProjectTitle: project.Title,
|
||||||
ProjectColumnTitle: column.Title,
|
ProjectColumnID: column.ID,
|
||||||
}); err != nil {
|
ProjectColumnTitle: column.Title,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -636,3 +636,74 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BranchDivergingInfo contains the information about the divergence of a head branch to the base branch.
|
||||||
|
type BranchDivergingInfo struct {
|
||||||
|
// whether the base branch contains new commits which are not in the head branch
|
||||||
|
BaseHasNewCommits bool
|
||||||
|
|
||||||
|
// behind/after are number of commits that the head branch is behind/after the base branch, it's 0 if it's unable to calculate.
|
||||||
|
// there could be a case that BaseHasNewCommits=true while the behind/after are both 0 (unable to calculate).
|
||||||
|
HeadCommitsBehind int
|
||||||
|
HeadCommitsAhead int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBranchDivergingInfo returns the information about the divergence of a patch branch to the base branch.
|
||||||
|
func GetBranchDivergingInfo(ctx context.Context, baseRepo *repo_model.Repository, baseBranch string, headRepo *repo_model.Repository, headBranch string) (*BranchDivergingInfo, error) {
|
||||||
|
headGitBranch, err := git_model.GetBranch(ctx, headRepo.ID, headBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if headGitBranch.IsDeleted {
|
||||||
|
return nil, git_model.ErrBranchNotExist{
|
||||||
|
BranchName: headBranch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
baseGitBranch, err := git_model.GetBranch(ctx, baseRepo.ID, baseBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if baseGitBranch.IsDeleted {
|
||||||
|
return nil, git_model.ErrBranchNotExist{
|
||||||
|
BranchName: baseBranch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &BranchDivergingInfo{}
|
||||||
|
if headGitBranch.CommitID == baseGitBranch.CommitID {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the fork repo has new commits, this call will fail because they are not in the base repo
|
||||||
|
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||||
|
// so at the moment, we first check the update time, then check whether the fork branch has base's head
|
||||||
|
diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
|
||||||
|
if err != nil {
|
||||||
|
info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
|
||||||
|
if headRepo.IsFork && info.BaseHasNewCommits {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
// if the base's update time is before the fork, check whether the base's head is in the fork
|
||||||
|
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, headRepo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
headCommit, err := headGitRepo.GetCommit(headGitBranch.CommitID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
baseCommitID, err := git.NewIDFromString(baseGitBranch.CommitID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hasPreviousCommit, _ := headCommit.HasPreviousCommit(baseCommitID)
|
||||||
|
info.BaseHasNewCommits = !hasPreviousCommit
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info.HeadCommitsBehind, info.HeadCommitsAhead = diff.Behind, diff.Ahead
|
||||||
|
info.BaseHasNewCommits = info.HeadCommitsBehind > 0
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -258,9 +258,11 @@ type findForksOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (opts findForksOptions) ToConds() builder.Cond {
|
func (opts findForksOptions) ToConds() builder.Cond {
|
||||||
return builder.Eq{"fork_id": opts.RepoID}.And(
|
cond := builder.Eq{"fork_id": opts.RepoID}
|
||||||
repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid),
|
if opts.Doer != nil && opts.Doer.IsAdmin {
|
||||||
)
|
return cond
|
||||||
|
}
|
||||||
|
return cond.And(repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindForks returns all the forks of the repository
|
// FindForks returns all the forks of the repository
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
|
||||||
issue_model "code.gitea.io/gitea/models/issues"
|
issue_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -17,12 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/pull"
|
"code.gitea.io/gitea/services/pull"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpstreamDivergingInfo struct {
|
// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
|
||||||
BaseIsNewer bool
|
|
||||||
CommitsBehind int
|
|
||||||
CommitsAhead int
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
|
func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
|
||||||
if err = repo.MustNotBeArchived(); err != nil {
|
if err = repo.MustNotBeArchived(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -30,9 +25,17 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
|
|||||||
if err = repo.GetBaseRepo(ctx); err != nil {
|
if err = repo.GetBaseRepo(ctx); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
divergingInfo, err := GetUpstreamDivergingInfo(ctx, repo, branch)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !divergingInfo.BaseBranchHasNewCommits {
|
||||||
|
return "up-to-date", nil
|
||||||
|
}
|
||||||
|
|
||||||
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
||||||
Remote: repo.RepoPath(),
|
Remote: repo.RepoPath(),
|
||||||
Branch: fmt.Sprintf("%s:%s", branch, branch),
|
Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
|
||||||
Env: repo_module.PushingEnvironment(doer, repo),
|
Env: repo_module.PushingEnvironment(doer, repo),
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -64,7 +67,7 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
|
|||||||
BaseRepoID: repo.BaseRepo.ID,
|
BaseRepoID: repo.BaseRepo.ID,
|
||||||
BaseRepo: repo.BaseRepo,
|
BaseRepo: repo.BaseRepo,
|
||||||
HeadBranch: branch, // maybe HeadCommitID is not needed
|
HeadBranch: branch, // maybe HeadCommitID is not needed
|
||||||
BaseBranch: branch,
|
BaseBranch: divergingInfo.BaseBranchName,
|
||||||
}
|
}
|
||||||
fakeIssue.PullRequest = fakePR
|
fakeIssue.PullRequest = fakePR
|
||||||
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
|
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
|
||||||
@@ -74,42 +77,47 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
|
|||||||
return "merge", nil
|
return "merge", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
|
// UpstreamDivergingInfo is also used in templates, so it needs to search for all references before changing it.
|
||||||
if !repo.IsFork {
|
type UpstreamDivergingInfo struct {
|
||||||
|
BaseBranchName string
|
||||||
|
BaseBranchHasNewCommits bool
|
||||||
|
HeadBranchCommitsBehind int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpstreamDivergingInfo returns the information about the divergence between the fork repository's branch and the base repository's default branch.
|
||||||
|
func GetUpstreamDivergingInfo(ctx context.Context, forkRepo *repo_model.Repository, forkBranch string) (*UpstreamDivergingInfo, error) {
|
||||||
|
if !forkRepo.IsFork {
|
||||||
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
|
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.IsArchived {
|
if forkRepo.IsArchived {
|
||||||
return nil, util.NewInvalidArgumentErrorf("repo is archived")
|
return nil, util.NewInvalidArgumentErrorf("repo is archived")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.GetBaseRepo(ctx); err != nil {
|
if err := forkRepo.GetBaseRepo(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
|
// Do the best to follow the GitHub's behavior, suppose there is a `branch-a` in fork repo:
|
||||||
if err != nil {
|
// * if `branch-a` exists in base repo: try to sync `base:branch-a` to `fork:branch-a`
|
||||||
return nil, err
|
// * if `branch-a` doesn't exist in base repo: try to sync `base:main` to `fork:branch-a`
|
||||||
|
info, err := GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkBranch, forkRepo, forkBranch)
|
||||||
|
if err == nil {
|
||||||
|
return &UpstreamDivergingInfo{
|
||||||
|
BaseBranchName: forkBranch,
|
||||||
|
BaseBranchHasNewCommits: info.BaseHasNewCommits,
|
||||||
|
HeadBranchCommitsBehind: info.HeadCommitsBehind,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
|
info, err = GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkRepo.BaseRepo.DefaultBranch, forkRepo, forkBranch)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return nil, err
|
return &UpstreamDivergingInfo{
|
||||||
|
BaseBranchName: forkRepo.BaseRepo.DefaultBranch,
|
||||||
|
BaseBranchHasNewCommits: info.BaseHasNewCommits,
|
||||||
|
HeadBranchCommitsBehind: info.HeadCommitsBehind,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil, err
|
||||||
info := &UpstreamDivergingInfo{}
|
|
||||||
if forkBranch.CommitID == baseBranch.CommitID {
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: if the fork repo has new commits, this call will fail:
|
|
||||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
|
||||||
// so at the moment, we are not able to handle this case, should be improved in the future
|
|
||||||
diff, err := git.GetDivergingCommits(ctx, repo.BaseRepo.RepoPath(), baseBranch.CommitID, forkBranch.CommitID)
|
|
||||||
if err != nil {
|
|
||||||
info.BaseIsNewer = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
info.CommitsBehind, info.CommitsAhead = diff.Behind, diff.Ahead
|
|
||||||
return info, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, err
|
|||||||
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
|
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc dingtalkConvertor) Status(p *api.CommitStatusPayload) (DingtalkPayload, error) {
|
||||||
|
text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return createDingtalkPayload(text, text, "Status Changed", p.TargetURL), nil
|
||||||
|
}
|
||||||
|
|
||||||
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
|
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
|
||||||
return DingtalkPayload{
|
return DingtalkPayload{
|
||||||
MsgType: "actionCard",
|
MsgType: "actionCard",
|
||||||
@@ -190,3 +196,7 @@ func newDingtalkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_
|
|||||||
var pc payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
|
var pc payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
|
||||||
return newJSONRequest(pc, w, t, true)
|
return newJSONRequest(pc, w, t, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterWebhookRequester(webhook_module.DINGTALK, newDingtalkRequest)
|
||||||
|
}
|
||||||
|
|||||||
@@ -265,6 +265,12 @@ func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error)
|
|||||||
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
|
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d discordConvertor) Status(p *api.CommitStatusPayload) (DiscordPayload, error) {
|
||||||
|
text, color := getStatusPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
return d.createPayload(p.Sender, text, "", p.TargetURL, color), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
meta := &DiscordMeta{}
|
meta := &DiscordMeta{}
|
||||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||||
@@ -277,6 +283,10 @@ func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_m
|
|||||||
return newJSONRequest(pc, w, t, true)
|
return newJSONRequest(pc, w, t, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterWebhookRequester(webhook_module.DISCORD, newDiscordRequest)
|
||||||
|
}
|
||||||
|
|
||||||
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {
|
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {
|
||||||
switch event {
|
switch event {
|
||||||
case webhook_module.HookEventPullRequestReviewApproved:
|
case webhook_module.HookEventPullRequestReviewApproved:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user