diff --git a/.github/workflows/cache-seeder.yml b/.github/workflows/cache-seeder.yml index 0be1d207516..4a597b67863 100644 --- a/.github/workflows/cache-seeder.yml +++ b/.github/workflows/cache-seeder.yml @@ -37,7 +37,7 @@ jobs: - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: TAGS="bindata" make backend - - run: TAGS="bindata gogit" GOEXPERIMENT="" make backend + - run: TAGS="bindata gogit" make backend - name: warm test compile cache (bindata) env: TAGS: bindata @@ -46,13 +46,12 @@ jobs: - name: warm test compile cache (bindata gogit) env: TAGS: bindata gogit - GOEXPERIMENT: GOTEST_FLAGS: -race -list=^$$ -count=1 run: make test-backend - name: warm integration compile cache run: | TAGS="bindata" make test-integration-compile - TAGS="bindata gogit" GOEXPERIMENT="" make test-integration-compile + TAGS="bindata gogit" make test-integration-compile TAGS="bindata gogit" GOTEST_FLAGS="-race" make test-integration-compile lint: diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index cdaa7b026bd..9e9bf9d76e7 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -96,7 +96,6 @@ jobs: - run: make backend env: TAGS: bindata gogit - GOEXPERIMENT: - run: GITEA_TEST_DATABASE=sqlite make test-migration env: TAGS: bindata gogit @@ -107,7 +106,6 @@ jobs: # sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow GOTEST_FLAGS: -timeout=40m TAGS: bindata gogit - GOEXPERIMENT: test-unit: if: needs.files-changed.outputs.backend == 'true' @@ -170,7 +168,6 @@ jobs: env: GOTEST_FLAGS: -race -timeout=20m TAGS: bindata gogit - GOEXPERIMENT: GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} GITEA_TEST_CI_SKIP_EXTERNAL: true - run: make test-check diff --git a/Makefile b/Makefile index 2c2b71e89e8..b30195003d7 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,6 @@ DIST := dist DIST_DIRS := $(DIST)/binaries $(DIST)/release -# By default use go's 1.25 experimental json v2 library when building -# TODO: remove when no longer experimental -export GOEXPERIMENT ?= jsonv2 - GO ?= go SHASUM ?= shasum -a 256 COMMA := , @@ -516,7 +512,7 @@ generate-go: $(TAGS_PREREQ) .PHONY: security-check security-check: - GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... || true + go run $(GOVULNCHECK_PACKAGE) -show color ./... || true $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),) diff --git a/cmd/web_https.go b/cmd/web_https.go index fe285597aa1..907c0ea6777 100644 --- a/cmd/web_https.go +++ b/cmd/web_https.go @@ -36,6 +36,7 @@ var curveStringMap = map[string]tls.CurveID{ "p256": tls.CurveP256, "p384": tls.CurveP384, "p521": tls.CurveP521, + "mlkem1024": tls.MLKEM1024, "x25519mlkem768": tls.X25519MLKEM768, "secp256r1mlkem768": tls.SecP256r1MLKEM768, "secp384r1mlkem1024": tls.SecP384r1MLKEM1024, diff --git a/go.mod b/go.mod index 93d46db4253..22fa93dc349 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module gitea.dev -go 1.27.0 +go 1.27 + +toolchain go1.27.0 require ( connectrpc.com/connect v1.20.0 diff --git a/models/issues/label.go b/models/issues/label.go index 0c3010601ad..24ca9d8c1cb 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -186,11 +186,11 @@ func (l *Label) ExclusiveScope() string { if !l.Exclusive { return "" } - lastIndex := strings.LastIndex(l.Name, "/") - if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 { + scope, name, found := strings.CutLast(l.Name, "/") + if !found || scope == "" || name == "" { return "" } - return l.Name[:lastIndex] + return scope } // CompareLabelForDisplay compares labels for displaying them in dropdowns or lists. diff --git a/modules/avatar/federated.go b/modules/avatar/federated.go index 27d3778cf68..ed8d1cdd863 100644 --- a/modules/avatar/federated.go +++ b/modules/avatar/federated.go @@ -16,11 +16,11 @@ import ( // LookupFederatedHost returns the avatar host from the email domain's SRV record. https://wiki.libravatar.org/api/ func LookupFederatedHost(ctx context.Context, email string, secure bool) string { - at := strings.LastIndexByte(email, '@') - if at < 0 { + _, domain, found := strings.CutLast(email, "@") + if !found { return "" } - domain := strings.ToLower(email[at+1:]) + domain = strings.ToLower(domain) service, defaultPort := "avatars", uint16(80) if secure { diff --git a/modules/avatar/hash_test.go b/modules/avatar/hash_test.go index 089f8b25bf4..a39aad20ad8 100644 --- a/modules/avatar/hash_test.go +++ b/modules/avatar/hash_test.go @@ -4,9 +4,6 @@ package avatar_test import ( - "bytes" - "image" - "image/png" "testing" "gitea.dev/modules/avatar" @@ -15,12 +12,10 @@ import ( ) func Test_HashAvatar(t *testing.T) { - myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) - var buff bytes.Buffer - png.Encode(&buff, myImage) + data := []byte("data") - assert.Equal(t, "9ddb5bac41d57e72aa876321d0c09d71090c05f94bc625303801be2f3240d2cb", avatar.HashAvatar(1, buff.Bytes())) - assert.Equal(t, "9a5d44e5d637b9582a976676e8f3de1dccd877c2fe3e66ca3fab1629f2f47609", avatar.HashAvatar(8, buff.Bytes())) - assert.Equal(t, "ed7399158672088770de6f5211ce15528ebd675e92fc4fc060c025f4b2794ccb", avatar.HashAvatar(1024, buff.Bytes())) + assert.Equal(t, "912b5d144e1f82719c79d4014fd9dc075aea506be410fb782d50c4f299f845f4", avatar.HashAvatar(1, data)) + assert.Equal(t, "55e41725707388f35483f881b2f38405fd9a6883365a862b8a5350ed6737c6fe", avatar.HashAvatar(8, data)) + assert.Equal(t, "473f06c83edcf69a9122db1d32f7649ebaa2680678e20ed0f868d25bd272abe8", avatar.HashAvatar(1024, data)) assert.Equal(t, "161178642c7d59eb25a61dddced5e6b66eae1c70880d5f148b1b497b767e72d9", avatar.HashAvatar(1024, []byte{})) } diff --git a/modules/git/key.go b/modules/git/key.go index 9fd1e09f57c..98ffd1ff158 100644 --- a/modules/git/key.go +++ b/modules/git/key.go @@ -55,12 +55,12 @@ func GetSigningKey(ctx context.Context) (*SigningKey, *Signature) { } return &SigningKey{ - KeyID: strings.TrimSpace(signingKey), - Format: strings.TrimSpace(format), - }, &Signature{ - Name: strings.TrimSpace(signingName), - Email: strings.TrimSpace(signingEmail), - } + KeyID: strings.TrimSpace(signingKey), + Format: strings.TrimSpace(format), + }, &Signature{ + Name: strings.TrimSpace(signingName), + Email: strings.TrimSpace(signingEmail), + } } if setting.Repository.Signing.SigningKey == "" { @@ -68,10 +68,10 @@ func GetSigningKey(ctx context.Context) (*SigningKey, *Signature) { } return &SigningKey{ - KeyID: setting.Repository.Signing.SigningKey, - Format: setting.Repository.Signing.SigningFormat, - }, &Signature{ - Name: setting.Repository.Signing.SigningName, - Email: setting.Repository.Signing.SigningEmail, - } + KeyID: setting.Repository.Signing.SigningKey, + Format: setting.Repository.Signing.SigningFormat, + }, &Signature{ + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } } diff --git a/modules/json/jsonlegacy.go b/modules/json/jsonlegacy.go deleted file mode 100644 index 81d644d4f4e..00000000000 --- a/modules/json/jsonlegacy.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "encoding/json" //nolint:depguard // this package wraps it - "io" -) - -func getDefaultJSONHandler() Interface { - return jsonV1{} -} - -func MarshalKeepOptionalEmpty(v any) ([]byte, error) { - return DefaultJSONHandler.Marshal(v) -} - -func NewDecoderCaseInsensitive(reader io.Reader) Decoder { - return DefaultJSONHandler.NewDecoder(reader) -} - -type Value = json.RawMessage diff --git a/modules/json/jsonv1.go b/modules/json/jsonv1.go deleted file mode 100644 index e7e4be57782..00000000000 --- a/modules/json/jsonv1.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package json - -import ( - "bytes" - "encoding/json" //nolint:depguard // this package wraps it - "io" -) - -type jsonV1 struct{} - -var _ Interface = jsonV1{} - -func (jsonV1) Marshal(v any) ([]byte, error) { - return json.Marshal(v) -} - -func (jsonV1) MarshalWrite(w io.Writer, v any) error { - return json.NewEncoder(w).Encode(v) -} - -func (jsonV1) Unmarshal(data []byte, v any) error { - return json.Unmarshal(data, v) -} - -func (jsonV1) NewEncoder(writer io.Writer) Encoder { - return json.NewEncoder(writer) -} - -func (jsonV1) NewDecoder(reader io.Reader) Decoder { - return json.NewDecoder(reader) -} - -func (jsonV1) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { - return json.Indent(dst, src, prefix, indent) -} diff --git a/modules/json/jsonv2.go b/modules/json/jsonv2.go index 33853279054..18f582f1b05 100644 --- a/modules/json/jsonv2.go +++ b/modules/json/jsonv2.go @@ -1,8 +1,6 @@ // Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build goexperiment.jsonv2 - package json import ( @@ -14,7 +12,6 @@ import ( ) // JSONv2 implements Interface via encoding/json/v2 -// Requires GOEXPERIMENT=jsonv2 to be set at build time type JSONv2 struct { marshalOptions jsonv2.Options marshalKeepOptionalEmptyOptions jsonv2.Options diff --git a/modules/packages/alpine/metadata_test.go b/modules/packages/alpine/metadata_test.go index 2a3c48ffb9a..b69c4698ad0 100644 --- a/modules/packages/alpine/metadata_test.go +++ b/modules/packages/alpine/metadata_test.go @@ -95,7 +95,7 @@ func TestParsePackage(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, p) - assert.Equal(t, "Q1SRYURM5+uQDqfHSwTnNIOIuuDVQ=", p.FileMetadata.Checksum) + assert.Equal(t, "Q1GfcV5dUrPr1xIRekqPYGehdxjL8=", p.FileMetadata.Checksum) }) } diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index 43ad7c16499..a84d08ca97e 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -64,12 +64,12 @@ func IsEmailDomainListed(globs []glob.Glob, email string) bool { return false } - n := strings.LastIndex(email, "@") - if n <= 0 { + localPart, domain, found := strings.CutLast(email, "@") + if !found || localPart == "" { return false } - domain := strings.ToLower(email[n+1:]) + domain = strings.ToLower(domain) for _, g := range globs { if g.Match(domain) { diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 206660ad5c7..c9de6fc0b65 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -83,12 +83,10 @@ func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTr dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot} addItem := func(item *WebDiffFileItem) { var parentPath string - pos := strings.LastIndexByte(item.FullName, '/') - if pos == -1 { - item.DisplayName = item.FullName + if dir, name, found := strings.CutLast(item.FullName, "/"); found { + parentPath, item.DisplayName = dir, name } else { - parentPath = item.FullName[:pos] - item.DisplayName = item.FullName[pos+1:] + item.DisplayName = item.FullName } parentNode, parentExists := dirNodes[parentPath] if !parentExists { diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index cfc42f9e187..5e5e1df9261 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -209,7 +209,7 @@ func TestPackageComposer(t *testing.T) { assert.Len(t, pkgs[0].Authors, 1) assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Equal(t, "f2d7c9059b66874092336aac6b0e96c98b3916c6", pkgs[0].Dist.Checksum) assert.Len(t, pkgs[0].Bin, 1) assert.Equal(t, packageBin, pkgs[0].Bin[0]) @@ -239,7 +239,7 @@ func TestPackageComposer(t *testing.T) { assert.Len(t, pkgs[0].Authors, 1) assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Equal(t, "f2d7c9059b66874092336aac6b0e96c98b3916c6", pkgs[0].Dist.Checksum) assert.Len(t, pkgs[0].Bin, 1) assert.Equal(t, packageBin, pkgs[0].Bin[0]) assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index be5b4463060..62cb455c6dc 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -426,13 +426,13 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(633), pb.Size) + assert.Equal(t, int64(635), pb.Size) case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): assert.False(t, pf.IsLead) pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(616), pb.Size) + assert.Equal(t, int64(617), pb.Size) case packageName + ".nuspec": assert.False(t, pf.IsLead) diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 5b2b5bb0c6c..0ac59d08749 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -238,15 +238,15 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, switch d.Type { case "primary": - assert.EqualValues(t, 722, d.Size) + assert.EqualValues(t, 721, d.Size) assert.EqualValues(t, 1759, d.OpenSize) assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href) case "filelists": - assert.EqualValues(t, 257, d.Size) + assert.EqualValues(t, 254, d.Size) assert.EqualValues(t, 326, d.OpenSize) assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href) case "other": - assert.EqualValues(t, 306, d.Size) + assert.EqualValues(t, 307, d.Size) assert.EqualValues(t, 394, d.OpenSize) assert.Equal(t, "repodata/other.xml.gz", d.Location.Href) } diff --git a/tests/integration/api_packages_rubygems_test.go b/tests/integration/api_packages_rubygems_test.go index 252cdf6b05f..6b914856c45 100644 --- a/tests/integration/api_packages_rubygems_test.go +++ b/tests/integration/api_packages_rubygems_test.go @@ -245,8 +245,8 @@ func TestPackageRubyGems(t *testing.T) { b, _ := base64.StdEncoding.DecodeString(`eJxi4Si1EndPzbWyCi5ITc5My0xOLMnMz2M8zMIRLeGpxGWsZ6RnzGbF5hqSyempxJWeWZKayGbN EBJqJQjWFZZaVJyZnxfN5qnEZahnoGcKkjTwVBJyB6lUKEhMzk5MTwULGngqcRaVJlWCONEMBp5K -DGAWSKc7zFhPJamg0qRK99TcYphehZLU4hKInFhGSUlBsZW+PtgZepn5+iDxECRzDUDGcfh6hoA4 -gAAAAP//MS06Gw==`) +DGAWSKc7zFhPJamg0qRK99TcYphehZLU4hKQSk8lsYySkoJiK319sDP0MvP1QeIhSOYagBzC4esZ +wmbNEAIYADEtOhs=`) assert.Equal(t, b, resp.Body.Bytes()) pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeRubyGems) @@ -266,11 +266,11 @@ gAAAAP//MS06Gw==`) assert.Equal(t, expectedContent, resp.Body.Bytes()) } - b, _ := base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3NwZWNzLjQuOABi4Yhmi+bwVOJKzyxJTWSzYnMNCbUSdE/NtbIKSy0qzszPi2bzVOIy1DPQM2WzZgjxVOIsKk2qBDEBAQAA///xOEYKOwAAAA==`) + b, _ := base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3NwZWNzLjQuOAAAOwDE/wQIWwZbCEkiCmdpdGVhBjoGRVRVOhFHZW06OlZlcnNpb25bBkkiCjEuMC41BjsAVEkiCXJ1YnkGOwBUAwDxOEYKOwAAAA==`) enumeratePackages(t, "specs.4.8.gz", b) - b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/2xhdGVzdF9zcGVjcy40LjgAYuGIZovm8FTiSs8sSU1ks2JzDQm1EnRPzbWyCkstKs7Mz4tm81TiMtQz0DNls2YI8VTiLCpNqgQxAQEAAP//8ThGCjsAAAA=`) + b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/2xhdGVzdF9zcGVjcy40LjgAADsAxP8ECFsGWwhJIgpnaXRlYQY6BkVUVToRR2VtOjpWZXJzaW9uWwZJIgoxLjAuNQY7AFRJIglydWJ5BjsAVAMA8ThGCjsAAAA=`) enumeratePackages(t, "latest_specs.4.8.gz", b) - b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3ByZXJlbGVhc2Vfc3BlY3MuNC44AGLhiGYABAAA//9snXr5BAAAAA==`) + b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3ByZXJlbGVhc2Vfc3BlY3MuNC44AAAEAPv/BAhbAAMAbJ16+QQAAAA=`) enumeratePackages(t, "prerelease_specs.4.8.gz", b) }) @@ -295,8 +295,8 @@ gAAAAP//MS06Gw==`) req := NewRequest(t, "GET", root+"/versions").AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, `--- -gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c -gitea-another 0.99 8b639e4048d282941485368ec42609be +gitea 1.0.5 6dc029f6875c637b6e2236c5056f431d +gitea-another 0.99 3f0251d551c97733836b9c983366c36d `, resp.Body.String()) }) diff --git a/tests/integration/api_packages_vagrant_test.go b/tests/integration/api_packages_vagrant_test.go index ebdfd078c73..5238a927f53 100644 --- a/tests/integration/api_packages_vagrant_test.go +++ b/tests/integration/api_packages_vagrant_test.go @@ -164,6 +164,6 @@ func TestPackageVagrant(t *testing.T) { provider := version.Providers[0] assert.Equal(t, packageProvider, provider.Name) assert.Equal(t, "sha512", provider.ChecksumType) - assert.Equal(t, "259bebd6160acad695016d22a45812e26f187aaf78e71a4c23ee3201528346293f991af3468a8c6c5d2a21d7d9e1bdc1bf79b87110b2fddfcc5a0d45963c7c30", provider.Checksum) + assert.Equal(t, "27c97e5881fe03dd224dff72dba0d5e7e25c9a0114c1f187f5ce228d7018a3a91398c32b8bc1c11d20d71447169751883da5dab996ff4b173f69fbeed069662e", provider.Checksum) }) } diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 640a090bfd6..09d2c5648c9 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -424,13 +424,13 @@ func TestAPIUploadAssetRelease(t *testing.T) { resp := performUpload(t, assetURL, bufImageBytes, http.StatusCreated) attachment := DecodeJSON(t, resp, &api.Attachment{}) assert.Equal(t, filename, attachment.Name) - assert.EqualValues(t, 104, attachment.Size) + assert.EqualValues(t, len(bufImageBytes), attachment.Size) }) t.Run("UploadWithName", func(t *testing.T) { resp := performUpload(t, assetURL+"?name=test-asset", bufImageBytes, http.StatusCreated) attachment := DecodeJSON(t, resp, &api.Attachment{}) assert.Equal(t, "test-asset", attachment.Name) - assert.EqualValues(t, 104, attachment.Size) + assert.EqualValues(t, len(bufImageBytes), attachment.Size) }) }) @@ -449,6 +449,6 @@ func TestAPIUploadAssetRelease(t *testing.T) { attachment := DecodeJSON(t, resp, &api.Attachment{}) assert.Equal(t, "stream.bin", attachment.Name) - assert.EqualValues(t, 104, attachment.Size) + assert.EqualValues(t, len(bufImageBytes), attachment.Size) }) }