mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-05 18:32:41 +09:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7758df4264 | ||
|
|
f994f3cac6 | ||
|
|
f514b2651e | ||
|
|
347101f2a8 | ||
|
|
b5f8c4a510 | ||
|
|
d6cee7c596 | ||
|
|
987219ab3c | ||
|
|
92280637a4 | ||
|
|
5fadcf997e | ||
|
|
be94f7bc07 | ||
|
|
9054a6670c | ||
|
|
fc82204fca | ||
|
|
6f8e62fa9c | ||
|
|
a2c6ecc093 | ||
|
|
523a84e5d0 | ||
|
|
869ee4fc38 | ||
|
|
16a332464d | ||
|
|
d03e7fd65e | ||
|
|
92f2d904f0 | ||
|
|
9c3511f0b1 | ||
|
|
69d35ee911 | ||
|
|
b8a7c20474 | ||
|
|
2cc76009dc | ||
|
|
730742230f | ||
|
|
8939c3845a | ||
|
|
d634e7576f | ||
|
|
7ded86f5af | ||
|
|
27a60fd91b | ||
|
|
e3021fae79 | ||
|
|
81126daf53 | ||
|
|
1c7339e385 | ||
|
|
039924aa2a | ||
|
|
b5007c6154 | ||
|
|
ae595aa913 | ||
|
|
aeeccc9642 | ||
|
|
37e99d9b34 | ||
|
|
9da6d4ea85 | ||
|
|
8844e62cb4 |
7
.github/workflows/pull-db-tests.yml
vendored
7
.github/workflows/pull-db-tests.yml
vendored
@@ -202,12 +202,11 @@ jobs:
|
||||
test-mssql:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
# specifying the version of ubuntu in use as mssql fails on newer kernels
|
||||
# pending resolution from vendor
|
||||
runs-on: ubuntu-20.04
|
||||
# NOTE: mssql-2017 docker image will panic when run on hosts that have Ubuntu newer than 20.04
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||
env:
|
||||
ACCEPT_EULA: Y
|
||||
MSSQL_PID: Standard
|
||||
|
||||
4
.github/workflows/release-tag-version.yml
vendored
4
.github/workflows/release-tag-version.yml
vendored
@@ -88,9 +88,9 @@ jobs:
|
||||
# 1.2
|
||||
# 1.2.3
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -126,9 +126,9 @@ jobs:
|
||||
# 1.2
|
||||
# 1.2.3
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -4,6 +4,49 @@ 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
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
|
||||
|
||||
* SECURITY
|
||||
* Fix LFS URL (#33840) (#33843)
|
||||
* Update jwt and redis packages (#33984) (#33987)
|
||||
* Update golang crypto and net (#33989)
|
||||
* BUGFIXES
|
||||
* Drop timeout for requests made to the internal hook api (#33947) (#33970)
|
||||
* Fix maven panic when no package exists (#33888) (#33889)
|
||||
* Fix markdown render (#33870) (#33875)
|
||||
* Fix auto concurrency cancellation skips commit status updates (#33764) (#33849)
|
||||
* Fix oauth2 auth (#33961) (#33962)
|
||||
* Fix incorrect 1.23 translations (#33932)
|
||||
* Try to figure out attribute checker problem (#33901) (#33902)
|
||||
* Ignore trivial errors when updating push data (#33864) (#33887)
|
||||
* Fix some UI problems for 1.23 (#33856)
|
||||
* Removing unwanted ui container (#33833) (#33835)
|
||||
* Support disable passkey auth (#33348) (#33819)
|
||||
* Do not call "git diff" when listing PRs (#33817)
|
||||
* Try to fix ACME (3rd) (#33807) (#33808)
|
||||
* Fix incorrect code search indexer options (#33992) #33999
|
||||
|
||||
## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-03
|
||||
|
||||
* SECURITY
|
||||
* Bump x/oauth2 & x/crypto (#33704) (#33727)
|
||||
* PERFORMANCE
|
||||
* Optimize user dashboard loading (#33686) (#33708)
|
||||
* BUGFIXES
|
||||
* Fix navbar dropdown item align (#33782)
|
||||
* Fix inconsistent closed issue list icon (#33722) (#33728)
|
||||
* Fix for Maven Package Naming Convention Handling (#33678) (#33679)
|
||||
* Improve Open-with URL encoding (#33666) (#33680)
|
||||
* Deleting repository should unlink all related packages (#33653) (#33673)
|
||||
* Fix omitempty bug (#33663) (#33670)
|
||||
* Upgrade go-crypto from 1.1.4 to 1.1.6 (#33745) (#33754)
|
||||
* Fix OCI image.version annotation for releases to use full semver (#33698) (#33701)
|
||||
* Try to fix ACME path when renew (#33668) (#33693)
|
||||
* Fix mCaptcha bug (#33659) (#33661)
|
||||
* Git graph: don't show detached commits (#33645) (#33650)
|
||||
* Use MatchPhraseQuery for bleve code search (#33628)
|
||||
* Adjust appearence of commit status webhook (#33778) #33789
|
||||
|
||||
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
|
||||
|
||||
* SECURITY
|
||||
|
||||
2
Makefile
2
Makefile
@@ -508,7 +508,7 @@ unit-test-coverage:
|
||||
tidy:
|
||||
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
||||
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
||||
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
||||
$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
||||
|
||||
vendor: go.mod go.sum
|
||||
$(GO) mod vendor
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
@@ -54,10 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
|
||||
altTLSALPNPort = p
|
||||
}
|
||||
|
||||
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||
// 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
|
||||
var certPool *x509.CertPool
|
||||
if setting.AcmeCARoot != "" {
|
||||
@@ -67,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
|
||||
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
||||
}
|
||||
}
|
||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
||||
CA: setting.AcmeURL,
|
||||
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
|
||||
// And one more thing, no idea why we should set the global default variables here
|
||||
// But it seems that the current ACME code needs these global variables to make renew work.
|
||||
// Otherwise, "renew" will use incorrect storage path
|
||||
oldDefaultACME := certmagic.DefaultACME
|
||||
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
certmagic.DefaultACME = certmagic.ACMEIssuer{
|
||||
// try to use the default values provided by DefaultACME
|
||||
CA: util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
|
||||
TestCA: oldDefaultACME.TestCA,
|
||||
Logger: oldDefaultACME.Logger,
|
||||
HTTPProxy: oldDefaultACME.HTTPProxy,
|
||||
|
||||
TrustedRoots: certPool,
|
||||
Email: setting.AcmeEmail,
|
||||
Agreed: setting.AcmeTOS,
|
||||
@@ -77,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
|
||||
ListenHost: setting.HTTPAddr,
|
||||
AltTLSALPNPort: altTLSALPNPort,
|
||||
AltHTTPPort: altHTTPPort,
|
||||
})
|
||||
}
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
|
||||
magic.Issuers = []certmagic.Issuer{myACME}
|
||||
|
||||
// this obtains certificates or renews them if necessary
|
||||
|
||||
@@ -784,10 +784,13 @@ LEVEL = Info
|
||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||
;ENABLE_BASIC_AUTHENTICATION = true
|
||||
;;
|
||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
|
||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
|
||||
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
||||
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
||||
;;
|
||||
;; Allow users to sign-in with a passkey
|
||||
;ENABLE_PASSKEY_AUTHENTICATION = true
|
||||
;;
|
||||
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||
|
||||
22
go.mod
22
go.mod
@@ -1,6 +1,6 @@
|
||||
module code.gitea.io/gitea
|
||||
|
||||
go 1.23
|
||||
go 1.23.6
|
||||
|
||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||
// But some CAs use negative serial number, just relax the check. related:
|
||||
@@ -24,7 +24,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/ProtonMail/go-crypto v1.1.4
|
||||
github.com/ProtonMail/go-crypto v1.1.6
|
||||
github.com/PuerkitoBio/goquery v1.10.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
|
||||
github.com/alecthomas/chroma/v2 v2.15.0
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/go-github/v61 v61.0.0
|
||||
github.com/google/licenseclassifier/v2 v2.0.0
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
|
||||
@@ -100,7 +100,7 @@ require (
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
@@ -118,13 +118,13 @@ require (
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.21.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/net v0.37.0
|
||||
golang.org/x/oauth2 v0.27.0
|
||||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
golang.org/x/tools v0.29.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.35.1
|
||||
@@ -215,7 +215,7 @@ require (
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.15 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||
|
||||
43
go.sum
43
go.sum
@@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
@@ -373,10 +373,11 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
@@ -658,8 +659,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
|
||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
@@ -833,8 +834,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
@@ -869,10 +870,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -884,8 +885,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -919,8 +920,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -932,8 +933,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -944,8 +945,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -194,7 +194,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
||||
|
||||
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
|
||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
|
||||
// Find all runs in the specified repository, reference, and workflow with non-final status
|
||||
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
||||
RepoID: repoID,
|
||||
@@ -204,14 +204,16 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
|
||||
if total == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cancelledJobs := make([]*ActionRunJob, 0, total)
|
||||
|
||||
// Iterate over each found run and cancel its associated jobs.
|
||||
for _, run := range runs {
|
||||
// Find all jobs associated with the current run.
|
||||
@@ -219,7 +221,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||
RunID: run.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return cancelledJobs, err
|
||||
}
|
||||
|
||||
// Iterate over each job and attempt to cancel it.
|
||||
@@ -238,27 +240,29 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||
// Update the job's status and stopped time in the database.
|
||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||
if err != nil {
|
||||
return err
|
||||
return cancelledJobs, err
|
||||
}
|
||||
|
||||
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
|
||||
if n == 0 {
|
||||
return fmt.Errorf("job has changed, try again")
|
||||
return cancelledJobs, fmt.Errorf("job has changed, try again")
|
||||
}
|
||||
|
||||
cancelledJobs = append(cancelledJobs, job)
|
||||
// Continue with the next job.
|
||||
continue
|
||||
}
|
||||
|
||||
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
||||
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
||||
return err
|
||||
return cancelledJobs, err
|
||||
}
|
||||
cancelledJobs = append(cancelledJobs, job)
|
||||
}
|
||||
}
|
||||
|
||||
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||
return nil
|
||||
return cancelledJobs, nil
|
||||
}
|
||||
|
||||
// InsertRun inserts a run
|
||||
|
||||
@@ -120,21 +120,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) {
|
||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||
// There is no other place we can do this because the app.ini will be changed manually
|
||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := CancelPreviousJobs(
|
||||
jobs, err := CancelPreviousJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
repo.DefaultBranch,
|
||||
"",
|
||||
webhook_module.HookEventSchedule,
|
||||
); err != nil {
|
||||
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||
)
|
||||
if err != nil {
|
||||
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||
}
|
||||
return nil
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
@@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
|
||||
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
|
||||
}
|
||||
|
||||
func FeedDateCond(opts GetFeedsOptions) builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.Date == "" {
|
||||
return cond
|
||||
}
|
||||
|
||||
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
|
||||
if err != nil {
|
||||
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
|
||||
} else {
|
||||
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
||||
|
||||
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
|
||||
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
|
||||
cond := builder.NewCond()
|
||||
|
||||
@@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
||||
cond = cond.And(builder.Eq{"is_deleted": false})
|
||||
}
|
||||
|
||||
if opts.Date != "" {
|
||||
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
|
||||
if err != nil {
|
||||
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
|
||||
} else {
|
||||
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
||||
|
||||
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
|
||||
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
|
||||
}
|
||||
}
|
||||
cond = cond.And(FeedDateCond(opts))
|
||||
|
||||
return cond, nil
|
||||
}
|
||||
|
||||
@@ -208,9 +208,31 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
||||
}
|
||||
|
||||
cond, err := ActivityQueryCondition(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
var err error
|
||||
var cond builder.Cond
|
||||
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
|
||||
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
|
||||
cond = builder.Eq{
|
||||
"user_id": opts.RequestedUser.ID,
|
||||
}.And(
|
||||
FeedDateCond(opts),
|
||||
)
|
||||
|
||||
if !opts.IncludeDeleted {
|
||||
cond = cond.And(builder.Eq{"is_deleted": false})
|
||||
}
|
||||
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{"is_private": false})
|
||||
}
|
||||
if opts.OnlyPerformedBy {
|
||||
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||
}
|
||||
} else {
|
||||
cond, err = ActivityQueryCondition(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
actions := make([]*Action, 0, opts.PageSize)
|
||||
|
||||
@@ -44,7 +44,7 @@ func init() {
|
||||
// TranslatableMessage represents JSON struct that can be translated with a Locale
|
||||
type TranslatableMessage struct {
|
||||
Format string
|
||||
Args []any `json:"omitempty"`
|
||||
Args []any `json:",omitempty"`
|
||||
}
|
||||
|
||||
// LoadRepo loads repository of the task
|
||||
|
||||
@@ -46,6 +46,7 @@ type Command struct {
|
||||
desc string
|
||||
globalArgsLength int
|
||||
brokenArgs []string
|
||||
cmd *exec.Cmd // for debug purpose only
|
||||
}
|
||||
|
||||
func (c *Command) String() string {
|
||||
@@ -314,6 +315,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
||||
startTime := time.Now()
|
||||
|
||||
cmd := exec.CommandContext(ctx, c.prog, c.args...)
|
||||
c.cmd = cmd // for debug purpose only
|
||||
if opts.Env == nil {
|
||||
cmd.Env = os.Environ()
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
|
||||
|
||||
stdinReader io.ReadCloser
|
||||
stdinWriter *os.File
|
||||
stdOut attributeWriter
|
||||
stdOut *nulSeparatedAttributeWriter
|
||||
cmd *Command
|
||||
env []string
|
||||
ctx context.Context
|
||||
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run cmd
|
||||
func (c *CheckAttributeReader) Run() error {
|
||||
defer func() {
|
||||
_ = c.stdinReader.Close()
|
||||
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
|
||||
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
|
||||
defer func() {
|
||||
if err != nil && err != c.ctx.Err() {
|
||||
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
|
||||
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reportTimeout := func() error {
|
||||
stdOutClosed := false
|
||||
select {
|
||||
case <-c.stdOut.closed:
|
||||
stdOutClosed = true
|
||||
default:
|
||||
}
|
||||
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
|
||||
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
|
||||
if c.cmd.cmd != nil {
|
||||
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
|
||||
}
|
||||
_ = c.Close()
|
||||
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
|
||||
}
|
||||
|
||||
rs = make(map[string]string)
|
||||
for range c.Attributes {
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
|
||||
// So add a timeout here to mitigate the problem, and output more logs for debug purpose
|
||||
// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
|
||||
// and at the moment the CheckPath result is not so important, so we can just ignore it.
|
||||
return nil, reportTimeout()
|
||||
case attr, ok := <-c.stdOut.ReadAttribute():
|
||||
if !ok {
|
||||
return nil, c.ctx.Err()
|
||||
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// Close close pip after use
|
||||
func (c *CheckAttributeReader) Close() error {
|
||||
c.cancel()
|
||||
err := c.stdinWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
type attributeWriter interface {
|
||||
io.WriteCloser
|
||||
ReadAttribute() <-chan attributeTriple
|
||||
}
|
||||
|
||||
type attributeTriple struct {
|
||||
Filename string
|
||||
Attribute string
|
||||
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a check attribute reader for the current repository and provided commit ID
|
||||
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
|
||||
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
|
||||
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
|
||||
if err != nil {
|
||||
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
|
||||
}
|
||||
ctx, cancel := context.WithCancel(repo.Ctx)
|
||||
if err := checker.Init(ctx); err != nil {
|
||||
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
|
||||
log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
|
||||
} else {
|
||||
go func() {
|
||||
err := checker.Run()
|
||||
if err != nil && err != ctx.Err() {
|
||||
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
|
||||
if err != nil && !IsErrCanceledOrKilled(err) {
|
||||
log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
}
|
||||
deferable := func() {
|
||||
deferrable := func() {
|
||||
_ = checker.Close()
|
||||
cancel()
|
||||
deleteTemporaryFile()
|
||||
}
|
||||
|
||||
return checker, deferable
|
||||
return checker, deferrable
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
mathRand "math/rand/v2"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
||||
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
||||
Value: "unspecified",
|
||||
}, attr)
|
||||
}
|
||||
|
||||
func TestAttributeReader(t *testing.T) {
|
||||
t.Skip() // for debug purpose only, do not run in CI
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
timeout := 1 * time.Second
|
||||
repoPath := filepath.Join(testReposDir, "language_stats_repo")
|
||||
commitRef := "HEAD"
|
||||
|
||||
oneRound := func(t *testing.T, roundIdx int) {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
_ = cancel
|
||||
gitRepo, err := OpenRepository(ctx, repoPath)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, err := gitRepo.GetCommit(commitRef)
|
||||
require.NoError(t, err)
|
||||
|
||||
files, err := gitRepo.LsFiles()
|
||||
require.NoError(t, err)
|
||||
|
||||
randomFiles := slices.Clone(files)
|
||||
randomFiles = append(randomFiles, "any-file-1", "any-file-2")
|
||||
|
||||
t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
|
||||
|
||||
attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
|
||||
defer deferrable()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
file := randomFiles[mathRand.IntN(len(randomFiles))]
|
||||
_, err := attrReader.CheckPath(file)
|
||||
if err != nil {
|
||||
for i := 0; i < 10; i++ {
|
||||
_, _ = attrReader.CheckPath(file)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
oneRound(t, i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
}
|
||||
|
||||
if len(branches) == 0 {
|
||||
graphCmd.AddArguments("--all")
|
||||
graphCmd.AddArguments("--tags", "--branches")
|
||||
}
|
||||
|
||||
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -101,6 +102,9 @@ func (r *Request) Param(key, value string) *Request {
|
||||
|
||||
// Body adds request raw body. It supports string, []byte and io.Reader as body.
|
||||
func (r *Request) Body(data any) *Request {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
switch t := data.(type) {
|
||||
case nil: // do nothing
|
||||
case string:
|
||||
@@ -193,6 +197,9 @@ func (r *Request) getResponse() (*http.Response, error) {
|
||||
// Response executes request client gets response manually.
|
||||
// Caller MUST close the response body if no error occurs
|
||||
func (r *Request) Response() (*http.Response, error) {
|
||||
if r == nil {
|
||||
return nil, errors.New("invalid request")
|
||||
}
|
||||
return r.getResponse()
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
analyzer_custom "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||
analyzer_keyword "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
|
||||
@@ -70,7 +69,7 @@ const (
|
||||
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
||||
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
||||
repoIndexerDocType = "repoIndexerDocType"
|
||||
repoIndexerLatestVersion = 8
|
||||
repoIndexerLatestVersion = 9
|
||||
)
|
||||
|
||||
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
||||
@@ -107,7 +106,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
||||
"type": analyzer_custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": letter.Name,
|
||||
"token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
|
||||
"token_filters": []string{unicodeNormalizeName, lowercase.Name},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -266,7 +265,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
pathQuery.FieldVal = "Filename"
|
||||
pathQuery.SetBoost(10)
|
||||
|
||||
contentQuery := bleve.NewMatchQuery(opts.Keyword)
|
||||
contentQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
|
||||
contentQuery.FieldVal = "Content"
|
||||
|
||||
if opts.IsKeywordFuzzy {
|
||||
|
||||
@@ -165,35 +165,6 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// Search for matches on the contents of files within the repo '62'.
|
||||
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
|
||||
{
|
||||
RepoIDs: []int64{62},
|
||||
Keyword: "This is not cheese",
|
||||
Langs: 1,
|
||||
Results: []codeSearchResult{
|
||||
{
|
||||
Filename: "potato/ham.md",
|
||||
Content: "This is not cheese",
|
||||
},
|
||||
{
|
||||
Filename: "ham.md",
|
||||
Content: "This is also not cheese",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Search for matches on the contents of files regardless of case.
|
||||
{
|
||||
RepoIDs: nil,
|
||||
Keyword: "dESCRIPTION",
|
||||
Langs: 1,
|
||||
Results: []codeSearchResult{
|
||||
{
|
||||
Filename: "README.md",
|
||||
Content: "# repo1\n\nDescription for repo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Search for an exact match on the filename within the repo '62' (case insenstive).
|
||||
// This scenario yields a single result (the file avocado.md on the repo '62')
|
||||
{
|
||||
@@ -233,6 +204,47 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
||||
},
|
||||
}
|
||||
|
||||
if name == "elastic_search" {
|
||||
// Additional scenarios for elastic_search only
|
||||
additional := []struct {
|
||||
RepoIDs []int64
|
||||
Keyword string
|
||||
Langs int
|
||||
Results []codeSearchResult
|
||||
}{
|
||||
// Search for matches on the contents of files within the repo '62'.
|
||||
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
|
||||
{
|
||||
RepoIDs: []int64{62},
|
||||
Keyword: "This is not cheese",
|
||||
Langs: 1,
|
||||
Results: []codeSearchResult{
|
||||
{
|
||||
Filename: "potato/ham.md",
|
||||
Content: "This is not cheese",
|
||||
},
|
||||
{
|
||||
Filename: "ham.md",
|
||||
Content: "This is also not cheese",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Search for matches on the contents of files regardless of case.
|
||||
{
|
||||
RepoIDs: nil,
|
||||
Keyword: "dESCRIPTION",
|
||||
Langs: 1,
|
||||
Results: []codeSearchResult{
|
||||
{
|
||||
Filename: "README.md",
|
||||
Content: "# repo1\n\nDescription for repo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
keywords = append(keywords, additional...)
|
||||
}
|
||||
|
||||
for _, kw := range keywords {
|
||||
t.Run(kw.Keyword, func(t *testing.T) {
|
||||
total, res, langs, err := indexer.Search(context.TODO(), &internal.SearchOptions{
|
||||
|
||||
@@ -70,14 +70,13 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
||||
g.logger.Log("json marshal error", err)
|
||||
return nil, err
|
||||
}
|
||||
url := g.server.JoinPath("objects/batch").String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@@ -179,13 +178,12 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
|
||||
g.logger.Log("argument id incorrect")
|
||||
return nil, 0, transfer.ErrCorruptData
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeOctetStream,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to get response: %w", err)
|
||||
@@ -225,7 +223,6 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
||||
g.logger.Log("argument id incorrect")
|
||||
return transfer.ErrCorruptData
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
@@ -233,7 +230,7 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
||||
headerContentLength: strconv.FormatInt(size, 10),
|
||||
}
|
||||
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
|
||||
req.Body(r)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
@@ -274,14 +271,13 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
|
||||
// the server sent no verify action
|
||||
return transfer.SuccessStatus(), nil
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
||||
|
||||
@@ -43,14 +43,13 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
|
||||
g.logger.Log("json marshal error", err)
|
||||
return nil, err
|
||||
}
|
||||
url := g.server.String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@@ -95,14 +94,13 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
|
||||
g.logger.Log("json marshal error", err)
|
||||
return err
|
||||
}
|
||||
url := g.server.JoinPath(lock.ID(), "unlock").String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@@ -176,16 +174,15 @@ func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lo
|
||||
}
|
||||
|
||||
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
|
||||
urlq := g.server.JoinPath() // get a copy
|
||||
urlq.RawQuery = v.Encode()
|
||||
url := urlq.String()
|
||||
serverURLWithQuery := g.server.JoinPath() // get a copy
|
||||
serverURLWithQuery.RawQuery = v.Encode()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
|
||||
@@ -8,9 +8,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
||||
)
|
||||
@@ -57,8 +61,7 @@ const (
|
||||
|
||||
// Operations enum
|
||||
const (
|
||||
opNone = iota
|
||||
opDownload
|
||||
opDownload = iota + 1
|
||||
opUpload
|
||||
)
|
||||
|
||||
@@ -86,8 +89,49 @@ func statusCodeToErr(code int) error {
|
||||
}
|
||||
}
|
||||
|
||||
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
|
||||
req := private.NewInternalRequest(ctx, url, method)
|
||||
func toInternalLFSURL(s string) string {
|
||||
pos1 := strings.Index(s, "://")
|
||||
if pos1 == -1 {
|
||||
return ""
|
||||
}
|
||||
appSubURLWithSlash := setting.AppSubURL + "/"
|
||||
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
|
||||
if pos2 == -1 {
|
||||
return ""
|
||||
}
|
||||
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
|
||||
fields := strings.SplitN(routePath, "/", 3)
|
||||
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||
return ""
|
||||
}
|
||||
return setting.LocalURL + "api/internal/repo/" + routePath
|
||||
}
|
||||
|
||||
func isInternalLFSURL(s string) bool {
|
||||
if !strings.HasPrefix(s, setting.LocalURL) {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
routePath := util.PathJoinRelX(u.Path)
|
||||
subRoutePath, cut := strings.CutPrefix(routePath, "api/internal/repo/")
|
||||
if !cut {
|
||||
return false
|
||||
}
|
||||
fields := strings.SplitN(subRoutePath, "/", 3)
|
||||
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
|
||||
if !isInternalLFSURL(internalURL) {
|
||||
return nil
|
||||
}
|
||||
req := private.NewInternalRequest(ctx, internalURL, method)
|
||||
for k, v := range headers {
|
||||
req.Header(k, v)
|
||||
}
|
||||
|
||||
54
modules/lfstransfer/backend/util_test.go
Normal file
54
modules/lfstransfer/backend/util_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToInternalLFSURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
cases := []struct {
|
||||
url string
|
||||
expected string
|
||||
}{
|
||||
{"http://appurl/any", ""},
|
||||
{"http://appurl/sub/any", ""},
|
||||
{"http://appurl/sub/owner/repo/any", ""},
|
||||
{"http://appurl/sub/owner/repo/info/any", ""},
|
||||
{"http://appurl/sub/owner/repo/info/lfs/any", "http://localurl/api/internal/repo/owner/repo/info/lfs/any"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
assert.Equal(t, c.expected, toInternalLFSURL(c.url), c.url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInternalLFSURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||
defer test.MockVariableValue(&setting.InternalToken, "mock-token")()
|
||||
cases := []struct {
|
||||
url string
|
||||
expected bool
|
||||
}{
|
||||
{"", false},
|
||||
{"http://otherurl/api/internal/repo/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/repo/owner/repo/info/lfs/any", true},
|
||||
{"http://localurl/api/internal/repo/owner/repo/info", false},
|
||||
{"http://localurl/api/internal/misc/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/foo/bar", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
req := newInternalRequestLFS(context.Background(), c.url, "GET", nil, nil)
|
||||
assert.Equal(t, c.expected, req != nil, c.url)
|
||||
assert.Equal(t, c.expected, isInternalLFSURL(c.url), c.url)
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,14 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
||||
limit: setting.UI.MaxDisplayFileSize * 3,
|
||||
}
|
||||
|
||||
// FIXME: Don't read all to memory, but goldmark doesn't support
|
||||
buf, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
log.Error("Unable to ReadAll: %v", err)
|
||||
return err
|
||||
}
|
||||
buf = giteautil.NormalizeEOL(buf)
|
||||
|
||||
// FIXME: should we include a timeout to abort the renderer if it takes too long?
|
||||
defer func() {
|
||||
err := recover()
|
||||
@@ -166,20 +174,12 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
||||
return
|
||||
}
|
||||
|
||||
log.Warn("Unable to render markdown due to panic in goldmark: %v", err)
|
||||
if (!setting.IsProd && !setting.IsInTesting) || log.IsDebug() {
|
||||
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
|
||||
}
|
||||
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
|
||||
escapedHTML := template.HTMLEscapeString(giteautil.UnsafeBytesToString(buf))
|
||||
_, _ = output.Write(giteautil.UnsafeStringToBytes(escapedHTML))
|
||||
}()
|
||||
|
||||
// FIXME: Don't read all to memory, but goldmark doesn't support
|
||||
pc := newParserContext(ctx)
|
||||
buf, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
log.Error("Unable to ReadAll: %v", err)
|
||||
return err
|
||||
}
|
||||
buf = giteautil.NormalizeEOL(buf)
|
||||
|
||||
// Preserve original length.
|
||||
bufWithMetadataLength := len(buf)
|
||||
|
||||
@@ -23,6 +23,11 @@ func TestAttention(t *testing.T) {
|
||||
defer svg.MockIcon("octicon-alert")()
|
||||
defer svg.MockIcon("octicon-stop")()
|
||||
|
||||
test := func(input, expected string) {
|
||||
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||
}
|
||||
renderAttention := func(attention, icon string) string {
|
||||
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
|
||||
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
|
||||
@@ -31,12 +36,6 @@ func TestAttention(t *testing.T) {
|
||||
return tmpl
|
||||
}
|
||||
|
||||
test := func(input, expected string) {
|
||||
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||
}
|
||||
|
||||
test(`
|
||||
> [!NOTE]
|
||||
> text
|
||||
@@ -53,4 +52,7 @@ func TestAttention(t *testing.T) {
|
||||
|
||||
// legacy GitHub style
|
||||
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||
|
||||
// edge case (it used to cause panic)
|
||||
test(">\ntext", "<blockquote>\n</blockquote>\n<p>text</p>")
|
||||
}
|
||||
|
||||
@@ -115,6 +115,9 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read
|
||||
|
||||
// grab these nodes and make sure we adhere to the attention blockquote structure
|
||||
firstParagraph := v.FirstChild()
|
||||
if firstParagraph == nil {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
g.applyElementDir(firstParagraph)
|
||||
|
||||
attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markup_test
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@@ -82,29 +82,32 @@ type HookProcReceiveRefResult struct {
|
||||
HeadBranch string
|
||||
}
|
||||
|
||||
func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, opts HookOptions) *httplib.Request {
|
||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s", hookName, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||
// This "timeout" applies to http.Client's timeout: A Timeout of zero means no timeout.
|
||||
// This "timeout" was previously set to `time.Duration(60+len(opts.OldCommitIDs))` seconds, but it caused unnecessary timeout failures.
|
||||
// It should be good enough to remove the client side timeout, only respect the "ctx" and server side timeout.
|
||||
req.SetReadWriteTimeout(0)
|
||||
return req
|
||||
}
|
||||
|
||||
// HookPreReceive check whether the provided commits are allowed
|
||||
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))
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||
req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, opts)
|
||||
_, extra := requestJSONResp(req, &ResponseText{})
|
||||
return extra
|
||||
}
|
||||
|
||||
// HookPostReceive updates services and users
|
||||
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))
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||
req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, opts)
|
||||
return requestJSONResp(req, &HookPostReceiveResult{})
|
||||
}
|
||||
|
||||
// HookProcReceive proc-receive hook
|
||||
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))
|
||||
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
||||
req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, opts)
|
||||
return requestJSONResp(req, &HookProcReceiveResult{})
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ func NewInternalRequest(ctx context.Context, url, method string) *httplib.Reques
|
||||
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(url, setting.LocalURL) {
|
||||
log.Fatal("Invalid internal request URL: %q", url)
|
||||
}
|
||||
|
||||
req := httplib.NewRequest(url, method).
|
||||
SetContext(ctx).
|
||||
Header("X-Real-IP", getClientIP()).
|
||||
|
||||
@@ -169,20 +169,24 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
||||
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
|
||||
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
|
||||
|
||||
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
||||
// if these are removed, the warning will not be shown
|
||||
if sec.HasKey("ENABLE_ACME") {
|
||||
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
|
||||
} else {
|
||||
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
|
||||
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
||||
}
|
||||
|
||||
Protocol = HTTP
|
||||
protocolCfg := sec.Key("PROTOCOL").String()
|
||||
if protocolCfg != "https" && EnableAcme {
|
||||
log.Fatal("ACME could only be used with HTTPS protocol")
|
||||
}
|
||||
|
||||
switch protocolCfg {
|
||||
case "https":
|
||||
Protocol = HTTPS
|
||||
|
||||
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
||||
// if these are removed, the warning will not be shown
|
||||
if sec.HasKey("ENABLE_ACME") {
|
||||
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
|
||||
} else {
|
||||
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
|
||||
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
||||
}
|
||||
if EnableAcme {
|
||||
AcmeURL = sec.Key("ACME_URL").MustString("")
|
||||
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
|
||||
@@ -210,6 +214,9 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
||||
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
|
||||
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
|
||||
}
|
||||
if AcmeEmail == "" {
|
||||
log.Fatal("ACME Email is not set (ACME_EMAIL).")
|
||||
}
|
||||
} else {
|
||||
CertFile = sec.Key("CERT_FILE").String()
|
||||
KeyFile = sec.Key("KEY_FILE").String()
|
||||
|
||||
@@ -46,6 +46,7 @@ var Service = struct {
|
||||
RequireSignInView bool
|
||||
EnableNotifyMail bool
|
||||
EnableBasicAuth bool
|
||||
EnablePasskeyAuth bool
|
||||
EnableReverseProxyAuth bool
|
||||
EnableReverseProxyAuthAPI bool
|
||||
EnableReverseProxyAutoRegister bool
|
||||
@@ -161,6 +162,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
||||
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
||||
Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
|
||||
Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true)
|
||||
Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true)
|
||||
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
|
||||
Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool()
|
||||
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
|
||||
|
||||
@@ -27,9 +27,10 @@ type PullRequest struct {
|
||||
Comments int `json:"comments"`
|
||||
// number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
ReviewComments int `json:"review_comments"`
|
||||
Additions int `json:"additions"`
|
||||
Deletions int `json:"deletions"`
|
||||
ChangedFiles int `json:"changed_files"`
|
||||
|
||||
Additions *int `json:"additions,omitempty"`
|
||||
Deletions *int `json:"deletions,omitempty"`
|
||||
ChangedFiles *int `json:"changed_files,omitempty"`
|
||||
|
||||
HTMLURL string `json:"html_url"`
|
||||
DiffURL string `json:"diff_url"`
|
||||
|
||||
@@ -1943,8 +1943,8 @@ pulls.delete.title=删除此合并请求?
|
||||
pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
|
||||
|
||||
pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong>
|
||||
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %s %d 个提交
|
||||
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %s %d 个提交
|
||||
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %[2]s %[1]d 个提交
|
||||
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %[2]s %[1]d 个提交
|
||||
pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
|
||||
pulls.upstream_diverging_merge=同步派生
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
maven_module "code.gitea.io/gitea/modules/packages/maven"
|
||||
)
|
||||
|
||||
// MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html
|
||||
@@ -22,7 +21,7 @@ type MetadataResponse struct {
|
||||
}
|
||||
|
||||
// pds is expected to be sorted ascending by CreatedUnix
|
||||
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
|
||||
func createMetadataResponse(pds []*packages_model.PackageDescriptor, groupID, artifactID string) *MetadataResponse {
|
||||
var release *packages_model.PackageDescriptor
|
||||
|
||||
versions := make([]string, 0, len(pds))
|
||||
@@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe
|
||||
|
||||
latest := pds[len(pds)-1]
|
||||
|
||||
metadata := latest.Metadata.(*maven_module.Metadata)
|
||||
|
||||
resp := &MetadataResponse{
|
||||
GroupID: metadata.GroupID,
|
||||
ArtifactID: metadata.ArtifactID,
|
||||
GroupID: groupID,
|
||||
ArtifactID: artifactID,
|
||||
Latest: latest.Version.Version,
|
||||
Version: versions,
|
||||
}
|
||||
|
||||
@@ -84,16 +84,20 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
|
||||
}
|
||||
|
||||
func serveMavenMetadata(ctx *context.Context, params parameters) {
|
||||
// /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
|
||||
|
||||
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
|
||||
}
|
||||
// path pattern: /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
|
||||
// in case there are legacy package names ("GroupID-ArtifactID") we need to check both, new packages always use ":" as separator("GroupID:ArtifactID")
|
||||
pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
pvs = append(pvsLegacy, pvs...)
|
||||
|
||||
if len(pvs) == 0 {
|
||||
apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
|
||||
return
|
||||
@@ -110,7 +114,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
||||
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
|
||||
})
|
||||
|
||||
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
|
||||
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds, params.GroupID, params.ArtifactID))
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
@@ -1050,7 +1049,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
||||
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
||||
return err
|
||||
}
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
@@ -169,6 +169,7 @@ func prepareSignInPageData(ctx *context.Context) {
|
||||
ctx.Data["PageIsLogin"] = true
|
||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
||||
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
||||
|
||||
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
||||
context.SetCaptchaData(ctx)
|
||||
|
||||
@@ -46,6 +46,7 @@ func LinkAccount(ctx *context.Context) {
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
||||
|
||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
|
||||
@@ -145,6 +146,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
||||
|
||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
|
||||
@@ -235,6 +237,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
||||
|
||||
// use this to set the right link into the signIn and signUp templates in the link_account template
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
|
||||
|
||||
@@ -248,7 +248,7 @@ func AuthorizeOAuth(ctx *context.Context) {
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
|
||||
if err := ctx.Session.Set("CodeChallenge", form.CodeChallenge); err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
ErrorDescription: "cannot set code challenge",
|
||||
|
||||
@@ -50,6 +50,11 @@ func WebAuthn(ctx *context.Context) {
|
||||
|
||||
// WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser
|
||||
func WebAuthnPasskeyAssertion(ctx *context.Context) {
|
||||
if !setting.Service.EnablePasskeyAuth {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin()
|
||||
if err != nil {
|
||||
ctx.ServerError("webauthn.BeginDiscoverableLogin", err)
|
||||
@@ -66,6 +71,11 @@ func WebAuthnPasskeyAssertion(ctx *context.Context) {
|
||||
|
||||
// WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey
|
||||
func WebAuthnPasskeyLogin(ctx *context.Context) {
|
||||
if !setting.Service.EnablePasskeyAuth {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
sessionData, okData := ctx.Session.Get("webauthnPasskeyAssertion").(*webauthn.SessionData)
|
||||
if !okData || sessionData == nil {
|
||||
ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session"))
|
||||
|
||||
@@ -903,7 +903,7 @@ func Run(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
// cancel running jobs of the same workflow
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
if err := actions_service.CancelPreviousJobs(
|
||||
ctx,
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
@@ -902,7 +901,7 @@ func SettingsPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time
|
||||
@@ -32,6 +34,24 @@ func StopEndlessTasks(ctx context.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
|
||||
if len(jobs) > 0 {
|
||||
CreateCommitStatus(ctx, jobs...)
|
||||
}
|
||||
}
|
||||
|
||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
||||
jobs, err := actions_model.CancelPreviousJobs(ctx, repoID, ref, workflowID, event)
|
||||
notifyWorkflowJobStatusUpdate(ctx, jobs)
|
||||
return err
|
||||
}
|
||||
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
|
||||
jobs, err := actions_model.CleanRepoScheduleTasks(ctx, repo)
|
||||
notifyWorkflowJobStatusUpdate(ctx, jobs)
|
||||
return err
|
||||
}
|
||||
|
||||
func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
|
||||
tasks, err := db.Find[actions_model.ActionTask](ctx, opts)
|
||||
if err != nil {
|
||||
@@ -67,7 +87,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
|
||||
remove()
|
||||
}
|
||||
|
||||
CreateCommitStatus(ctx, jobs...)
|
||||
notifyWorkflowJobStatusUpdate(ctx, jobs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
return nil
|
||||
}
|
||||
if unit_model.TypeActions.UnitGlobalDisabled() {
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
}
|
||||
return nil
|
||||
@@ -341,7 +341,7 @@ func handleWorkflows(
|
||||
// cancel running jobs if the event is push or pull_request_sync
|
||||
if run.Event == webhook_module.HookEventPush ||
|
||||
run.Event == webhook_module.HookEventPullRequestSync {
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
if err := CancelPreviousJobs(
|
||||
ctx,
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
@@ -472,7 +472,7 @@ func handleSchedules(
|
||||
log.Error("CountSchedules: %v", err)
|
||||
return err
|
||||
} else if count > 0 {
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func startTasks(ctx context.Context) error {
|
||||
// cancel running jobs if the event is push
|
||||
if row.Schedule.Event == webhook_module.HookEventPush {
|
||||
// cancel running jobs of the same workflow
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
if err := CancelPreviousJobs(
|
||||
ctx,
|
||||
row.RepoID,
|
||||
row.Schedule.Ref,
|
||||
|
||||
@@ -239,9 +239,11 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
// Calculate diff
|
||||
startCommitID = pr.MergeBase
|
||||
|
||||
apiPullRequest.ChangedFiles, apiPullRequest.Additions, apiPullRequest.Deletions, err = gitRepo.GetDiffShortStat(startCommitID, endCommitID)
|
||||
diffChangedFiles, diffAdditions, diffDeletions, err := gitRepo.GetDiffShortStat(startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
log.Error("GetDiffShortStat: %v", err)
|
||||
} else {
|
||||
apiPullRequest.ChangedFiles, apiPullRequest.Additions, apiPullRequest.Deletions = &diffChangedFiles, &diffAdditions, &diffDeletions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,12 +461,6 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Outer scope variables to be used in diff calculation
|
||||
var (
|
||||
startCommitID string
|
||||
endCommitID string
|
||||
)
|
||||
|
||||
if git.IsErrBranchNotExist(err) {
|
||||
headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
@@ -473,7 +469,6 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs
|
||||
}
|
||||
if err == nil {
|
||||
apiPullRequest.Head.Sha = headCommitID
|
||||
endCommitID = headCommitID
|
||||
}
|
||||
} else {
|
||||
commit, err := headBranch.GetCommit()
|
||||
@@ -484,17 +479,8 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs
|
||||
if err == nil {
|
||||
apiPullRequest.Head.Ref = pr.HeadBranch
|
||||
apiPullRequest.Head.Sha = commit.ID.String()
|
||||
endCommitID = commit.ID.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate diff
|
||||
startCommitID = pr.MergeBase
|
||||
|
||||
apiPullRequest.ChangedFiles, apiPullRequest.Additions, apiPullRequest.Deletions, err = gitRepo.GetDiffShortStat(startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
log.Error("GetDiffShortStat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
|
||||
|
||||
@@ -1193,6 +1193,8 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
if language.Has() {
|
||||
diffFile.Language = language.Value()
|
||||
}
|
||||
} else {
|
||||
checker = nil // CheckPath fails, it's not impossible to "check" anymore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1377,10 +1379,8 @@ func GetWhitespaceFlag(whitespaceBehavior string) git.TrustedCmdArgs {
|
||||
"ignore-eol": {"--ignore-space-at-eol"},
|
||||
"show-all": nil,
|
||||
}
|
||||
|
||||
if flag, ok := whitespaceFlags[whitespaceBehavior]; ok {
|
||||
return flag
|
||||
}
|
||||
log.Warn("unknown whitespace behavior: %q, default to 'show-all'", whitespaceBehavior)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
@@ -428,7 +429,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
||||
log.Error("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
if err := actions_service.CancelPreviousJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
from,
|
||||
@@ -609,7 +610,7 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
|
||||
log.Error("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
if err := actions_service.CancelPreviousJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
oldDefaultBranchName,
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@@ -273,6 +274,11 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
|
||||
return err
|
||||
}
|
||||
|
||||
// unlink packages linked to this repository
|
||||
if err = packages_model.UnlinkRepositoryFromAllPackages(ctx, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
@@ -133,23 +134,26 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
||||
} else { // is new tag
|
||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
|
||||
// in case there is dirty data, for example, the "github.com/git/git" repository has tags pointing to non-existing commits
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("Unable to get tag commit: gitRepo.GetCommit(%s) in %s/%s[%d]: %v", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
|
||||
}
|
||||
} else {
|
||||
commits := repo_module.NewPushCommits()
|
||||
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
|
||||
commits.CompareURL = repo.ComposeCompareURL(objectFormat.EmptyObjectID().String(), opts.NewCommitID)
|
||||
|
||||
notify_service.PushCommits(
|
||||
ctx, pusher, repo,
|
||||
&repo_module.PushUpdateOptions{
|
||||
RefFullName: opts.RefFullName,
|
||||
OldCommitID: objectFormat.EmptyObjectID().String(),
|
||||
NewCommitID: opts.NewCommitID,
|
||||
}, commits)
|
||||
|
||||
addTags = append(addTags, tagName)
|
||||
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
|
||||
}
|
||||
|
||||
commits := repo_module.NewPushCommits()
|
||||
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
|
||||
commits.CompareURL = repo.ComposeCompareURL(objectFormat.EmptyObjectID().String(), opts.NewCommitID)
|
||||
|
||||
notify_service.PushCommits(
|
||||
ctx, pusher, repo,
|
||||
&repo_module.PushUpdateOptions{
|
||||
RefFullName: opts.RefFullName,
|
||||
OldCommitID: objectFormat.EmptyObjectID().String(),
|
||||
NewCommitID: opts.NewCommitID,
|
||||
}, commits)
|
||||
|
||||
addTags = append(addTags, tagName)
|
||||
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
|
||||
}
|
||||
} else if opts.RefFullName.IsBranch() {
|
||||
if pusher == nil || pusher.ID != opts.PusherID {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
@@ -63,11 +62,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod
|
||||
notify_service.DeleteRepository(ctx, doer, repo)
|
||||
}
|
||||
|
||||
if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID)
|
||||
return DeleteRepositoryDirectly(ctx, doer, repo.ID)
|
||||
}
|
||||
|
||||
// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
@@ -29,7 +28,7 @@ func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, uni
|
||||
}
|
||||
|
||||
if slices.Contains(deleteUnitTypes, unit.TypeActions) {
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -308,12 +310,16 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w
|
||||
}
|
||||
|
||||
func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
||||
refLink := linkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description)
|
||||
refLink := linkFormatter(p.TargetURL, fmt.Sprintf("%s [%s]", p.Context, base.ShortSha(p.SHA)))
|
||||
|
||||
text = fmt.Sprintf("Commit Status changed: %s", refLink)
|
||||
text = fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description)
|
||||
color = greenColor
|
||||
if withSender {
|
||||
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
|
||||
if user_model.IsGiteaActionsUserName(p.Sender.UserName) {
|
||||
text += fmt.Sprintf(" by %s", p.Sender.FullName)
|
||||
} else {
|
||||
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
|
||||
}
|
||||
}
|
||||
|
||||
return text, color
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"strings"
|
||||
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -245,8 +246,8 @@ func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) {
|
||||
}
|
||||
|
||||
func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, error) {
|
||||
refLink := htmlLinkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description)
|
||||
text := fmt.Sprintf("Commit Status changed: %s", refLink)
|
||||
refLink := htmlLinkFormatter(p.TargetURL, fmt.Sprintf("%s [%s]", p.Context, base.ShortSha(p.SHA)))
|
||||
text := fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description)
|
||||
|
||||
return m.newPayload(text)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{if eq .Num -1}}
|
||||
<a class="disabled item">...</a>
|
||||
{{else}}
|
||||
<a class="{{if .IsCurrent}}active {{end}}item tw-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
<a class="{{if .IsCurrent}}active {{end}}item" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ctx.Locale.PrettyNumber .OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}}
|
||||
</a>
|
||||
<a class="{{if eq .State "closed"}}active {{end}}item flex-text-inline" href="{{if eq .State "closed"}}{{$allStatesLink}}{{else}}{{$closedLink}}{{end}}">
|
||||
{{svg "octicon-check"}}
|
||||
{{svg "octicon-issue-closed"}}
|
||||
{{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -59,11 +59,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if or .EnablePasskeyAuth .ShowRegistrationButton}}
|
||||
<div class="ui container fluid">
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
|
||||
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
|
||||
<a class="signin-passkey">{{ctx.Locale.Tr "auth.signin_passkey"}}</a>
|
||||
{{if .EnablePasskeyAuth}}
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
<a class="signin-passkey">{{ctx.Locale.Tr "auth.signin_passkey"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="field">
|
||||
@@ -73,3 +75,4 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -80,6 +80,7 @@ func TestPackageMaven(t *testing.T) {
|
||||
t.Run("UploadLegacy", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// try to upload a package with legacy package name (will be saved as "GroupID-ArtifactID")
|
||||
legacyRootLink := "/api/packages/user2/maven/com/gitea/legacy-project"
|
||||
req := NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.2/any-file-name?use_legacy_package_name=1", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
@@ -97,6 +98,13 @@ func TestPackageMaven(t *testing.T) {
|
||||
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// legacy package names should also be able to be listed
|
||||
req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
respBody := resp.Body.String()
|
||||
assert.Contains(t, respBody, "<version>1.0.2</version>")
|
||||
|
||||
// then upload a package with correct package name (will be saved as "GroupID:ArtifactID")
|
||||
req = NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.3/any-file-name", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
_, err = packages.GetPackageByName(db.DefaultContext, user.ID, packages.TypeMaven, "com.gitea-legacy-project")
|
||||
@@ -114,6 +122,12 @@ func TestPackageMaven(t *testing.T) {
|
||||
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// now 2 packages should be listed
|
||||
req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
respBody = resp.Body.String()
|
||||
assert.Contains(t, respBody, "<version>1.0.2</version>")
|
||||
assert.Contains(t, respBody, "<version>1.0.3</version>")
|
||||
require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID))
|
||||
})
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ func TestAPIViewPulls(t *testing.T) {
|
||||
assert.Empty(t, pull.RequestedReviewersTeams)
|
||||
assert.EqualValues(t, 5, pull.RequestedReviewers[0].ID)
|
||||
assert.EqualValues(t, 6, pull.RequestedReviewers[1].ID)
|
||||
assert.EqualValues(t, 1, pull.ChangedFiles)
|
||||
|
||||
if assert.EqualValues(t, 5, pull.ID) {
|
||||
resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK)
|
||||
@@ -59,22 +58,23 @@ func TestAPIViewPulls(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "")
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, patch.Files, pull.ChangedFiles) {
|
||||
if assert.Len(t, patch.Files, 1) {
|
||||
assert.Equal(t, "File-WoW", patch.Files[0].Name)
|
||||
// FIXME: The old name should be empty if it's a file add type
|
||||
assert.Equal(t, "File-WoW", patch.Files[0].OldName)
|
||||
assert.EqualValues(t, pull.Additions, patch.Files[0].Addition)
|
||||
assert.EqualValues(t, pull.Deletions, patch.Files[0].Deletion)
|
||||
assert.EqualValues(t, 1, patch.Files[0].Addition)
|
||||
assert.EqualValues(t, 0, patch.Files[0].Deletion)
|
||||
assert.Equal(t, gitdiff.DiffFileAdd, patch.Files[0].Type)
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID),
|
||||
doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) {
|
||||
if assert.Len(t, files, pull.ChangedFiles) {
|
||||
if assert.Len(t, files, 1) {
|
||||
assert.Equal(t, "File-WoW", files[0].Filename)
|
||||
assert.Empty(t, files[0].PreviousFilename)
|
||||
assert.EqualValues(t, pull.Additions, files[0].Additions)
|
||||
assert.EqualValues(t, pull.Deletions, files[0].Deletions)
|
||||
assert.EqualValues(t, 1, files[0].Additions)
|
||||
assert.EqualValues(t, 1, files[0].Changes)
|
||||
assert.EqualValues(t, 0, files[0].Deletions)
|
||||
assert.Equal(t, "added", files[0].Status)
|
||||
}
|
||||
}))
|
||||
@@ -88,7 +88,6 @@ func TestAPIViewPulls(t *testing.T) {
|
||||
assert.EqualValues(t, 4, pull.RequestedReviewers[1].ID)
|
||||
assert.EqualValues(t, 2, pull.RequestedReviewers[2].ID)
|
||||
assert.EqualValues(t, 5, pull.RequestedReviewers[3].ID)
|
||||
assert.EqualValues(t, 1, pull.ChangedFiles)
|
||||
|
||||
if assert.EqualValues(t, 2, pull.ID) {
|
||||
resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK)
|
||||
@@ -96,45 +95,44 @@ func TestAPIViewPulls(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "")
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, patch.Files, pull.ChangedFiles) {
|
||||
if assert.Len(t, patch.Files, 1) {
|
||||
assert.Equal(t, "README.md", patch.Files[0].Name)
|
||||
assert.Equal(t, "README.md", patch.Files[0].OldName)
|
||||
assert.EqualValues(t, pull.Additions, patch.Files[0].Addition)
|
||||
assert.EqualValues(t, pull.Deletions, patch.Files[0].Deletion)
|
||||
assert.EqualValues(t, 4, patch.Files[0].Addition)
|
||||
assert.EqualValues(t, 1, patch.Files[0].Deletion)
|
||||
assert.Equal(t, gitdiff.DiffFileChange, patch.Files[0].Type)
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID),
|
||||
doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) {
|
||||
if assert.Len(t, files, pull.ChangedFiles) {
|
||||
if assert.Len(t, files, 1) {
|
||||
assert.Equal(t, "README.md", files[0].Filename)
|
||||
// FIXME: The PreviousFilename name should be the same as Filename if it's a file change
|
||||
assert.Equal(t, "", files[0].PreviousFilename)
|
||||
assert.EqualValues(t, pull.Additions, files[0].Additions)
|
||||
assert.EqualValues(t, pull.Deletions, files[0].Deletions)
|
||||
assert.EqualValues(t, 4, files[0].Additions)
|
||||
assert.EqualValues(t, 1, files[0].Deletions)
|
||||
assert.Equal(t, "changed", files[0].Status)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pull = pulls[2]
|
||||
pull = pulls[0]
|
||||
assert.EqualValues(t, 1, pull.Poster.ID)
|
||||
assert.Len(t, pull.RequestedReviewers, 1)
|
||||
assert.Len(t, pull.RequestedReviewers, 2)
|
||||
assert.Empty(t, pull.RequestedReviewersTeams)
|
||||
assert.EqualValues(t, 1, pull.RequestedReviewers[0].ID)
|
||||
assert.EqualValues(t, 0, pull.ChangedFiles)
|
||||
assert.EqualValues(t, 5, pull.RequestedReviewers[0].ID)
|
||||
|
||||
if assert.EqualValues(t, 1, pull.ID) {
|
||||
if assert.EqualValues(t, 5, pull.ID) {
|
||||
resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, pull.ChangedFiles, patch.NumFiles)
|
||||
assert.EqualValues(t, 1, patch.NumFiles)
|
||||
|
||||
t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID),
|
||||
doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) {
|
||||
assert.Len(t, files, pull.ChangedFiles)
|
||||
assert.Len(t, files, 1)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,14 @@ func TestGitLFSSSH(t *testing.T) {
|
||||
return strings.Contains(s, "POST /api/internal/repo/user2/repo1.git/info/lfs/objects/batch")
|
||||
})
|
||||
countUpload := slices.ContainsFunc(routerCalls, func(s string) bool {
|
||||
return strings.Contains(s, "PUT /user2/repo1.git/info/lfs/objects/")
|
||||
return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/")
|
||||
})
|
||||
nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool {
|
||||
fields := strings.Fields(s)
|
||||
return !strings.HasPrefix(fields[1], "/api/")
|
||||
})
|
||||
assert.NotZero(t, countBatch)
|
||||
assert.NotZero(t, countUpload)
|
||||
assert.Zero(t, nonAPIRequests)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewWebHookLink(t *testing.T) {
|
||||
@@ -378,12 +379,14 @@ func Test_WebhookPullRequest(t *testing.T) {
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "pull_request", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
require.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
|
||||
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Head.Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName)
|
||||
assert.EqualValues(t, 0, payloads[0].PullRequest.Additions)
|
||||
assert.EqualValues(t, 0, *payloads[0].PullRequest.Additions)
|
||||
assert.EqualValues(t, 0, *payloads[0].PullRequest.ChangedFiles)
|
||||
assert.EqualValues(t, 0, *payloads[0].PullRequest.Deletions)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestSigninWithRememberMe(t *testing.T) {
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestEnablePasswordSignInForm(t *testing.T) {
|
||||
func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
mockLinkAccount := func(ctx *context.Context) {
|
||||
@@ -139,4 +139,22 @@ func TestEnablePasswordSignInForm(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/link_account_signin']", true)
|
||||
})
|
||||
|
||||
t.Run("EnablePasskeyAuth=false", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, false)()
|
||||
|
||||
req := NewRequest(t, "GET", "/user/login")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
NewHTMLParser(t, resp.Body).AssertElement(t, ".signin-passkey", false)
|
||||
})
|
||||
|
||||
t.Run("EnablePasskeyAuth=true", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, true)()
|
||||
|
||||
req := NewRequest(t, "GET", "/user/login")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
NewHTMLParser(t, resp.Body).AssertElement(t, ".signin-passkey", true)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3003
|
||||
ROOT_URL = http://localhost:3003/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3003/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2201
|
||||
|
||||
@@ -47,6 +47,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3001
|
||||
ROOT_URL = http://localhost:3001/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3001/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2201
|
||||
|
||||
@@ -46,6 +46,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3002
|
||||
ROOT_URL = http://localhost:3002/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3002/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2202
|
||||
|
||||
@@ -41,6 +41,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3003
|
||||
ROOT_URL = http://localhost:3003/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3003/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2203
|
||||
|
||||
@@ -182,7 +182,7 @@ textarea:focus,
|
||||
height: 76px !important;
|
||||
}
|
||||
.m-captcha-style {
|
||||
width: 50%;
|
||||
max-width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#navbar .dropdown .item {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
#navbar a.item:hover,
|
||||
#navbar button.item:hover {
|
||||
background: var(--color-nav-hover-bg);
|
||||
@@ -61,6 +57,10 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#navbar .item.active {
|
||||
background: var(--color-active);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
#navbar {
|
||||
align-items: stretch;
|
||||
|
||||
@@ -34,13 +34,18 @@ export async function initCaptcha() {
|
||||
break;
|
||||
}
|
||||
case 'm-captcha': {
|
||||
const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
|
||||
// @ts-expect-error
|
||||
const mCaptcha = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
|
||||
|
||||
// FIXME: the mCaptcha code is not right, it's a miracle that the wrong code could run
|
||||
// * the "vanilla-glue" has some problems with es6 module.
|
||||
// * the INPUT_NAME is a "const", it should not be changed.
|
||||
// * the "mCaptcha.default" is actually the "Widget".
|
||||
|
||||
// @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property.
|
||||
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
||||
const instanceURL = captchaEl.getAttribute('data-instance-url');
|
||||
|
||||
// @ts-expect-error
|
||||
mCaptcha.default({
|
||||
new mCaptcha.default({
|
||||
siteKey: {
|
||||
instanceUrl: new URL(instanceURL),
|
||||
key: siteKey,
|
||||
|
||||
7
web_src/js/features/repo-common.test.ts
Normal file
7
web_src/js/features/repo-common.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {substituteRepoOpenWithUrl} from './repo-common.ts';
|
||||
|
||||
test('substituteRepoOpenWithUrl', () => {
|
||||
// For example: "x-github-client://openRepo/https://github.com/go-gitea/gitea"
|
||||
expect(substituteRepoOpenWithUrl('proto://a/{url}', 'https://gitea')).toEqual('proto://a/https://gitea');
|
||||
expect(substituteRepoOpenWithUrl('proto://a?link={url}', 'https://gitea')).toEqual('proto://a?link=https%3A%2F%2Fgitea');
|
||||
});
|
||||
@@ -42,6 +42,14 @@ export function initRepoActivityTopAuthorsChart() {
|
||||
}
|
||||
}
|
||||
|
||||
export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
|
||||
const pos = tmpl.indexOf('{url}');
|
||||
if (pos === -1) return tmpl;
|
||||
const posQuestionMark = tmpl.indexOf('?');
|
||||
const needEncode = posQuestionMark >= 0 && posQuestionMark < pos;
|
||||
return tmpl.replace('{url}', needEncode ? encodeURIComponent(url) : url);
|
||||
}
|
||||
|
||||
function initCloneSchemeUrlSelection(parent: Element) {
|
||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
||||
|
||||
@@ -70,7 +78,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
||||
}
|
||||
}
|
||||
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
|
||||
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
|
||||
el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template'), link);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -221,7 +221,10 @@ function initRepoDiffShowMore() {
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
$target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children());
|
||||
const $respFileBody = $(resp).find('#diff-file-boxes .diff-file-body .file-body');
|
||||
const respFileBodyChildren = Array.from($respFileBody.children());
|
||||
$target.parent().replaceWith($respFileBody.children());
|
||||
for (const el of respFileBodyChildren) window.htmx.process(el);
|
||||
onShowMoreFiles();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.ts';
|
||||
import {showElem} from '../utils/dom.ts';
|
||||
import {hideElem, showElem} from '../utils/dom.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
@@ -11,6 +11,15 @@ export async function initUserAuthWebAuthn() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.location.protocol === 'http:') {
|
||||
// webauthn is only supported on secure contexts
|
||||
const isLocalhost = ['localhost', '127.0.0.1'].includes(window.location.hostname);
|
||||
if (!isLocalhost) {
|
||||
hideElem(elSignInPasskeyBtn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!detectWebAuthnSupport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
1
web_src/js/globals.d.ts
vendored
1
web_src/js/globals.d.ts
vendored
@@ -63,6 +63,7 @@ interface Window {
|
||||
jQuery: typeof import('@types/jquery'),
|
||||
htmx: Omit<typeof import('htmx.org/dist/htmx.esm.js').default, 'config'> & {
|
||||
config?: Writable<typeof import('htmx.org').default.config>,
|
||||
process?: (elt: Element | string) => void,
|
||||
},
|
||||
ui?: any,
|
||||
_globalHandlerErrors: Array<ErrorEvent & PromiseRejectionEvent> & {
|
||||
|
||||
Reference in New Issue
Block a user