diff --git a/.dockerignore b/.dockerignore index b696e1603c..94aca6b8d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -79,18 +79,6 @@ cpu.out /public/assets/fonts /public/assets/img/avatar /vendor -/web_src/fomantic/node_modules -/web_src/fomantic/build/* -!/web_src/fomantic/build/semantic.js -!/web_src/fomantic/build/semantic.css -!/web_src/fomantic/build/themes -/web_src/fomantic/build/themes/* -!/web_src/fomantic/build/themes/default -/web_src/fomantic/build/themes/default/assets/* -!/web_src/fomantic/build/themes/default/assets/fonts -/web_src/fomantic/build/themes/default/assets/fonts/* -!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2 -!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2 /VERSION /.air /.go-licenses diff --git a/.gitattributes b/.gitattributes index 9fb4a4e83d..52695f70c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,5 @@ /public/assets/img/svg/*.svg linguist-generated /templates/swagger/v1_json.tmpl linguist-generated /vendor/** -text -eol linguist-vendored -/web_src/fomantic/build/** linguist-generated -/web_src/fomantic/_site/globals/site.variables linguist-language=Less /web_src/js/vendor/** -text -eol linguist-vendored Dockerfile.* linguist-language=Dockerfile diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index 33cbc507d9..c34066d318 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -15,7 +15,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - run: make generate-license generate-gitignore + - run: make generate-gitignore timeout-minutes: 40 - name: push translations to repo uses: appleboy/git-push-action@v0.0.3 diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 7e988e0449..64090d6490 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -95,7 +95,7 @@ jobs: go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - - run: make lint-go-windows lint-go-vet + - run: make lint-go-windows lint-go-gitea-vet env: TAGS: bindata sqlite sqlite_unlock_notify GOOS: windows diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 0b23de0a66..6e879053d3 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -202,12 +202,10 @@ 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 + 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 diff --git a/.gitignore b/.gitignore index d215468377..703be8f681 100644 --- a/.gitignore +++ b/.gitignore @@ -84,18 +84,6 @@ cpu.out /public/assets/fonts /public/assets/licenses.txt /vendor -/web_src/fomantic/node_modules -/web_src/fomantic/build/* -!/web_src/fomantic/build/semantic.js -!/web_src/fomantic/build/semantic.css -!/web_src/fomantic/build/themes -/web_src/fomantic/build/themes/* -!/web_src/fomantic/build/themes/default -/web_src/fomantic/build/themes/default/assets/* -!/web_src/fomantic/build/themes/default/assets/fonts -/web_src/fomantic/build/themes/default/assets/fonts/* -!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2 -!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2 /VERSION /.air /.go-licenses diff --git a/CHANGELOG.md b/CHANGELOG.md index c32915c1dc..7541bccb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ 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.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-04 + +* 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 + * Upgrade golang net from 0.35.0 -> 0.36.0 (#33795) #33796 + ## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16 * SECURITY diff --git a/Makefile b/Makefile index d4b416156f..6c8798318c 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ EXTRA_GOFLAGS ?= MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1) MAKE_EVIDENCE_DIR := .make_evidence +GOTESTFLAGS ?= ifeq ($(RACE_ENABLED),true) GOFLAGS += -race GOTESTFLAGS += -race @@ -114,8 +115,6 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) -FOMANTIC_WORK_DIR := web_src/fomantic - WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css @@ -139,7 +138,7 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify -TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) +TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css @@ -311,10 +310,10 @@ lint-frontend: lint-js lint-css ## lint frontend files lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues .PHONY: lint-backend -lint-backend: lint-go lint-go-vet lint-go-gopls lint-editorconfig ## lint backend files +lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files .PHONY: lint-backend-fix -lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig ## lint backend files and fix issues +lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues .PHONY: lint-js lint-js: node_modules ## lint js files @@ -365,9 +364,9 @@ lint-go-windows: @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) golangci-lint run -.PHONY: lint-go-vet -lint-go-vet: ## lint go files with vet - @echo "Running go vet..." +.PHONY: lint-go-gitea-vet +lint-go-gitea-vet: ## lint go files with gitea-vet + @echo "Running gitea-vet..." @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @$(GO) vet -vettool=gitea-vet ./... @@ -846,19 +845,6 @@ update-py: node-check | node_modules ## update py dependencies poetry install @touch .venv -.PHONY: fomantic -fomantic: ## build fomantic files - rm -rf $(FOMANTIC_WORK_DIR)/build - cd $(FOMANTIC_WORK_DIR) && npm install --no-save - cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config - cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/ - $(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js - cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build - # fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event - $(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js - $(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js - rm -f $(FOMANTIC_WORK_DIR)/build/*.min.* - .PHONY: webpack webpack: $(WEBPACK_DEST) ## build webpack files @@ -905,10 +891,6 @@ update-translations: mv ./translations/*.ini ./options/locale/ rmdir ./translations -.PHONY: generate-license -generate-license: ## update license files - $(GO) run build/generate-licenses.go - .PHONY: generate-gitignore generate-gitignore: ## update gitignore files $(GO) run build/generate-gitignores.go diff --git a/build/generate-licenses.go b/build/generate-licenses.go deleted file mode 100644 index 66e1d37755..0000000000 --- a/build/generate-licenses.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build ignore - -package main - -import ( - "archive/tar" - "compress/gzip" - "crypto/md5" - "encoding/hex" - "flag" - "fmt" - "io" - "log" - "net/http" - "os" - "path" - "path/filepath" - "strings" - - "code.gitea.io/gitea/build/license" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" -) - -func main() { - var ( - prefix = "gitea-licenses" - url = "https://api.github.com/repos/spdx/license-list-data/tarball" - githubApiToken = "" - githubUsername = "" - destination = "" - ) - - flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses") - flag.StringVar(&githubUsername, "username", "", "github username") - flag.StringVar(&githubApiToken, "token", "", "github api token") - flag.Parse() - - file, err := os.CreateTemp(os.TempDir(), prefix) - if err != nil { - log.Fatalf("Failed to create temp file. %s", err) - } - - defer util.Remove(file.Name()) - - if err := os.RemoveAll(destination); err != nil { - log.Fatalf("Cannot clean destination folder: %v", err) - } - - if err := os.MkdirAll(destination, 0o755); err != nil { - log.Fatalf("Cannot create destination: %v", err) - } - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Fatalf("Failed to download archive. %s", err) - } - - if len(githubApiToken) > 0 && len(githubUsername) > 0 { - req.SetBasicAuth(githubUsername, githubApiToken) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - log.Fatalf("Failed to download archive. %s", err) - } - - defer resp.Body.Close() - - if _, err := io.Copy(file, resp.Body); err != nil { - log.Fatalf("Failed to copy archive to file. %s", err) - } - - if _, err := file.Seek(0, 0); err != nil { - log.Fatalf("Failed to reset seek on archive. %s", err) - } - - gz, err := gzip.NewReader(file) - if err != nil { - log.Fatalf("Failed to gunzip the archive. %s", err) - } - - tr := tar.NewReader(gz) - aliasesFiles := make(map[string][]string) - for { - hdr, err := tr.Next() - - if err == io.EOF { - break - } - - if err != nil { - log.Fatalf("Failed to iterate archive. %s", err) - } - - if !strings.Contains(hdr.Name, "/text/") { - continue - } - - if filepath.Ext(hdr.Name) != ".txt" { - continue - } - - fileBaseName := filepath.Base(hdr.Name) - licenseName := strings.TrimSuffix(fileBaseName, ".txt") - - if strings.HasPrefix(fileBaseName, "README") { - continue - } - - if strings.HasPrefix(fileBaseName, "deprecated_") { - continue - } - out, err := os.Create(path.Join(destination, licenseName)) - if err != nil { - log.Fatalf("Failed to create new file. %s", err) - } - - defer out.Close() - - // some license files have same content, so we need to detect these files and create a convert map into a json file - // Later we use this convert map to avoid adding same license content with different license name - h := md5.New() - // calculate md5 and write file in the same time - r := io.TeeReader(tr, h) - if _, err := io.Copy(out, r); err != nil { - log.Fatalf("Failed to write new file. %s", err) - } else { - fmt.Printf("Written %s\n", out.Name()) - - md5 := hex.EncodeToString(h.Sum(nil)) - aliasesFiles[md5] = append(aliasesFiles[md5], licenseName) - } - } - - // generate convert license name map - licenseAliases := make(map[string]string) - for _, fileNames := range aliasesFiles { - if len(fileNames) > 1 { - licenseName := license.GetLicenseNameFromAliases(fileNames) - if licenseName == "" { - // license name should not be empty as expected - // if it is empty, we need to rewrite the logic of GetLicenseNameFromAliases - log.Fatalf("GetLicenseNameFromAliases: license name is empty") - } - for _, fileName := range fileNames { - licenseAliases[fileName] = licenseName - } - } - } - // save convert license name map to file - b, err := json.Marshal(licenseAliases) - if err != nil { - log.Fatalf("Failed to create json bytes. %s", err) - } - - licenseAliasesDestination := filepath.Join(destination, "etc", "license-aliases.json") - if err := os.MkdirAll(filepath.Dir(licenseAliasesDestination), 0o755); err != nil { - log.Fatalf("Failed to create directory for license aliases json file. %s", err) - } - - f, err := os.Create(licenseAliasesDestination) - if err != nil { - log.Fatalf("Failed to create license aliases json file. %s", err) - } - defer f.Close() - - if _, err = f.Write(b); err != nil { - log.Fatalf("Failed to write license aliases json file. %s", err) - } - - fmt.Println("Done") -} diff --git a/build/license/aliasgenerator.go b/build/license/aliasgenerator.go deleted file mode 100644 index 7de1e6fbd6..0000000000 --- a/build/license/aliasgenerator.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package license - -import "strings" - -func GetLicenseNameFromAliases(fnl []string) string { - if len(fnl) == 0 { - return "" - } - - shortestItem := func(list []string) string { - s := list[0] - for _, l := range list[1:] { - if len(l) < len(s) { - s = l - } - } - return s - } - allHasPrefix := func(list []string, s string) bool { - for _, l := range list { - if !strings.HasPrefix(l, s) { - return false - } - } - return true - } - - sl := shortestItem(fnl) - slv := strings.Split(sl, "-") - var result string - for i := len(slv); i >= 0; i-- { - result = strings.Join(slv[:i], "-") - if allHasPrefix(fnl, result) { - return result - } - } - return "" -} diff --git a/build/license/aliasgenerator_test.go b/build/license/aliasgenerator_test.go deleted file mode 100644 index 239181b736..0000000000 --- a/build/license/aliasgenerator_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package license - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetLicenseNameFromAliases(t *testing.T) { - tests := []struct { - target string - inputs []string - }{ - { - // real case which you can find in license-aliases.json - target: "AGPL-1.0", - inputs: []string{ - "AGPL-1.0-only", - "AGPL-1.0-or-late", - }, - }, - { - target: "", - inputs: []string{ - "APSL-1.0", - "AGPL-1.0-only", - "AGPL-1.0-or-late", - }, - }, - } - - for _, tt := range tests { - result := GetLicenseNameFromAliases(tt.inputs) - assert.Equal(t, result, tt.target) - } -} diff --git a/cmd/doctor.go b/cmd/doctor.go index e433f4adc5..52699cc4dd 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "fmt" golog "log" "os" @@ -130,8 +131,8 @@ func runRecreateTable(ctx *cli.Context) error { } recreateTables := migrate_base.RecreateTables(beans...) - return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error { - if err := migrations.EnsureUpToDate(x); err != nil { + return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error { + if err := migrations.EnsureUpToDate(ctx, x); err != nil { return err } return recreateTables(x) diff --git a/cmd/hook.go b/cmd/hook.go index 578380ab40..41e3c3ce34 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -316,7 +316,7 @@ func runHookPostReceive(c *cli.Context) error { setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what - if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { + if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { return fmt.Errorf("Failed to call 'git update-server-info': %w", err) } diff --git a/cmd/migrate.go b/cmd/migrate.go index 459805a76d..25d8b50c45 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -7,9 +7,9 @@ import ( "context" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/versioned_migration" "github.com/urfave/cli/v2" ) @@ -36,7 +36,7 @@ func runMigrate(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil { + if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index 2e3aba021d..f9ed140395 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -13,7 +13,6 @@ import ( actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" - "code.gitea.io/gitea/models/migrations" packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -21,6 +20,7 @@ import ( packages_module "code.gitea.io/gitea/modules/packages" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/services/versioned_migration" "github.com/urfave/cli/v2" ) @@ -227,7 +227,7 @@ func runMigrateStorage(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil { + if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err } diff --git a/cmd/web_acme.go b/cmd/web_acme.go index bca4ae0212..172dde913b 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -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" ) @@ -68,9 +69,15 @@ func runACME(listenAddr string, m http.Handler) error { // 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{ - CA: setting.AcmeURL, + // 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, diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 899209874f..0fc49accef 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1294,6 +1294,9 @@ LEVEL = Info ;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css" ;THEMES = ;; +;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future. +;FILE_ICON_THEME = material +;; ;; All available reactions users can choose on issues/prs and comments. ;; Values can be emoji alias (:smile:) or a unicode emoji. ;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png @@ -1767,6 +1770,9 @@ LEVEL = Info ;; ;; convert \r\n to \n for Sendmail ;SENDMAIL_CONVERT_CRLF = true +;; +;; convert links of attached images to inline images. Only for images hosted in this gitea instance. +;EMBED_ATTACHMENT_IMAGES = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/go.mod b/go.mod index 4b7025eb92..0a544a9100 100644 --- a/go.mod +++ b/go.mod @@ -119,7 +119,7 @@ require ( gitlab.com/gitlab-org/api/client-go v0.123.0 golang.org/x/crypto v0.35.0 golang.org/x/image v0.24.0 - golang.org/x/net v0.35.0 + golang.org/x/net v0.36.0 golang.org/x/oauth2 v0.27.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 diff --git a/go.sum b/go.sum index 18a586e404..6e0b0bde07 100644 --- a/go.sum +++ b/go.sum @@ -867,8 +867,8 @@ 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.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 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= diff --git a/models/actions/run.go b/models/actions/run.go index 60fbbcd323..89f7f3e640 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -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 diff --git a/models/actions/runner.go b/models/actions/runner.go index 798a647180..97db0ca7ea 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -337,3 +337,17 @@ func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { } return res.RowsAffected() } + +func CountWrongRepoLevelRunners(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `action_runner` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelRunners(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `action_runner` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/actions/schedule.go b/models/actions/schedule.go index e2cc32eedc..2edf483fe0 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -43,15 +43,12 @@ func init() { // GetSchedulesMapByIDs returns the schedules by given id slice. func GetSchedulesMapByIDs(ctx context.Context, ids []int64) (map[int64]*ActionSchedule, error) { schedules := make(map[int64]*ActionSchedule, len(ids)) + if len(ids) == 0 { + return schedules, nil + } return schedules, db.GetEngine(ctx).In("id", ids).Find(&schedules) } -// GetReposMapByIDs returns the repos by given id slice. -func GetReposMapByIDs(ctx context.Context, ids []int64) (map[int64]*repo_model.Repository, error) { - repos := make(map[int64]*repo_model.Repository, len(ids)) - return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) -} - // CreateScheduleTask creates new schedule task. func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error { // Return early if there are no rows to insert @@ -120,21 +117,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 } diff --git a/models/actions/schedule_spec_list.go b/models/actions/schedule_spec_list.go index f7dac72f8b..e26b2c1120 100644 --- a/models/actions/schedule_spec_list.go +++ b/models/actions/schedule_spec_list.go @@ -32,7 +32,7 @@ func (specs SpecList) LoadSchedules(ctx context.Context) error { } repoIDs := specs.GetRepoIDs() - repos, err := GetReposMapByIDs(ctx, repoIDs) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) if err != nil { return err } diff --git a/models/actions/variable.go b/models/actions/variable.go index 163bb12c93..1929cffbd8 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -147,3 +147,17 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, return variables, nil } + +func CountWrongRepoLevelVariables(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `action_variable` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelVariables(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `action_variable` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/activities/action.go b/models/activities/action.go index 52dffe07fd..c16c49c0ac 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -16,9 +16,7 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -200,15 +198,13 @@ func (a *Action) LoadActUser(ctx context.Context) { } } -func (a *Action) LoadRepo(ctx context.Context) { +func (a *Action) LoadRepo(ctx context.Context) error { if a.Repo != nil { - return + return nil } var err error a.Repo, err = repo_model.GetRepositoryByID(ctx, a.RepoID) - if err != nil { - log.Error("repo_model.GetRepositoryByID(%d): %v", a.RepoID, err) - } + return err } // GetActFullName gets the action's user full name. @@ -250,7 +246,7 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string { // GetRepoUserName returns the name of the action repository owner. func (a *Action) GetRepoUserName(ctx context.Context) string { - a.LoadRepo(ctx) + _ = a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -265,7 +261,7 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string { // GetRepoName returns the name of the action repository. func (a *Action) GetRepoName(ctx context.Context) string { - a.LoadRepo(ctx) + _ = a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -529,8 +525,8 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder. } if opts.RequestedTeam != nil { - env := repo_model.AccessibleTeamReposEnv(ctx, organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam) - teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos) + env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam) + teamRepoIDs, err := env.RepoIDs(ctx, 1, opts.RequestedUser.NumRepos) if err != nil { return nil, fmt.Errorf("GetTeamRepositories: %w", err) } @@ -567,130 +563,6 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) return err } -// NotifyWatchers creates batch of actions for every watcher. -// It could insert duplicate actions for a repository action, like this: -// * Original action: UserID=1 (the real actor), ActUserID=1 -// * Organization action: UserID=100 (the repo's org), ActUserID=1 -// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1 -func NotifyWatchers(ctx context.Context, actions ...*Action) error { - var watchers []*repo_model.Watch - var repo *repo_model.Repository - var err error - var permCode []bool - var permIssue []bool - var permPR []bool - - e := db.GetEngine(ctx) - - for _, act := range actions { - repoChanged := repo == nil || repo.ID != act.RepoID - - if repoChanged { - // Add feeds for user self and all watchers. - watchers, err = repo_model.GetWatchers(ctx, act.RepoID) - if err != nil { - return fmt.Errorf("get watchers: %w", err) - } - } - - // Add feed for actioner. - act.UserID = act.ActUserID - if _, err = e.Insert(act); err != nil { - return fmt.Errorf("insert new actioner: %w", err) - } - - if repoChanged { - act.LoadRepo(ctx) - repo = act.Repo - - // check repo owner exist. - if err := act.Repo.LoadOwner(ctx); err != nil { - return fmt.Errorf("can't get repo owner: %w", err) - } - } else if act.Repo == nil { - act.Repo = repo - } - - // Add feed for organization - if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID { - act.ID = 0 - act.UserID = act.Repo.Owner.ID - if err = db.Insert(ctx, act); err != nil { - return fmt.Errorf("insert new actioner: %w", err) - } - } - - if repoChanged { - permCode = make([]bool, len(watchers)) - permIssue = make([]bool, len(watchers)) - permPR = make([]bool, len(watchers)) - for i, watcher := range watchers { - user, err := user_model.GetUserByID(ctx, watcher.UserID) - if err != nil { - permCode[i] = false - permIssue[i] = false - permPR[i] = false - continue - } - perm, err := access_model.GetUserRepoPermission(ctx, repo, user) - if err != nil { - permCode[i] = false - permIssue[i] = false - permPR[i] = false - continue - } - permCode[i] = perm.CanRead(unit.TypeCode) - permIssue[i] = perm.CanRead(unit.TypeIssues) - permPR[i] = perm.CanRead(unit.TypePullRequests) - } - } - - for i, watcher := range watchers { - if act.ActUserID == watcher.UserID { - continue - } - act.ID = 0 - act.UserID = watcher.UserID - act.Repo.Units = nil - - switch act.OpType { - case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch: - if !permCode[i] { - continue - } - case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: - if !permIssue[i] { - continue - } - case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest, ActionAutoMergePullRequest: - if !permPR[i] { - continue - } - } - - if err = db.Insert(ctx, act); err != nil { - return fmt.Errorf("insert new action: %w", err) - } - } - } - return nil -} - -// NotifyWatchersActions creates batch of actions for every watcher. -func NotifyWatchersActions(ctx context.Context, acts []*Action) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - for _, act := range acts { - if err := NotifyWatchers(ctx, act); err != nil { - return err - } - } - return committer.Commit() -} - // DeleteIssueActions delete all actions related with issueID func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error { // delete actions assigned to this issue diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 9cfe981656..ee2a225a3e 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -82,43 +82,6 @@ func TestActivityReadable(t *testing.T) { } } -func TestNotifyWatchers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - action := &activities_model.Action{ - ActUserID: 8, - RepoID: 1, - OpType: activities_model.ActionStarRepo, - } - assert.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action)) - - // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 8, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 1, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 4, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 11, - RepoID: action.RepoID, - OpType: action.OpType, - }) -} - func TestConsistencyUpdateAction(t *testing.T) { if !setting.Database.Type.IsSQLite3() { t.Skip("Test is only for SQLite database.") diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 2e65d76612..1ed6edd71a 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -25,7 +25,7 @@ func (err ErrKeyUnableVerify) Error() string { } // ErrKeyIsPrivate is returned when the provided key is a private key not a public key -var ErrKeyIsPrivate = util.NewSilentWrapErrorf(util.ErrInvalidArgument, "the provided key is a private key") +var ErrKeyIsPrivate = util.ErrorWrap(util.ErrInvalidArgument, "the provided key is a private key") // ErrKeyNotExist represents a "KeyNotExist" kind of error. type ErrKeyNotExist struct { diff --git a/models/db/context.go b/models/db/context.go index 51627712b1..4b98796ef0 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -289,6 +289,9 @@ func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([ // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one // Timestamps of the entities won't be updated func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error { + if len(ids) == 0 { + return nil + } _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean) return err } diff --git a/models/db/engine_hook.go b/models/db/engine_hook.go index 2c9fc09c99..8709a2c2a1 100644 --- a/models/db/engine_hook.go +++ b/models/db/engine_hook.go @@ -41,7 +41,7 @@ func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error { // 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function // is being displayed (the function that ultimately wants to execute the query in the code) // instead of the function of the slow query hook being called. - h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) + h.Logger.Log(8, &log.Event{Level: log.WARN}, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) } return nil } diff --git a/models/db/engine_init.go b/models/db/engine_init.go index edca697934..7a071fa29b 100644 --- a/models/db/engine_init.go +++ b/models/db/engine_init.go @@ -105,7 +105,7 @@ func UnsetDefaultEngine() { // When called from the "doctor" command, the migration function is a version check // that prevents the doctor from fixing anything in the database if the migration level // is different from the expected value. -func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { +func InitEngineWithMigration(ctx context.Context, migrateFunc func(context.Context, *xorm.Engine) error) (err error) { if err = InitEngine(ctx); err != nil { return err } @@ -122,7 +122,7 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) // Installation should only be being re-run if users want to recover an old database. // However, we should think carefully about should we support re-install on an installed instance, // as there may be other problems due to secret reinitialization. - if err = migrateFunc(xormEngine); err != nil { + if err = migrateFunc(ctx, xormEngine); err != nil { return fmt.Errorf("migrate: %w", err) } diff --git a/models/db/log.go b/models/db/log.go index 307788ea2e..a9df6f541d 100644 --- a/models/db/log.go +++ b/models/db/log.go @@ -29,7 +29,7 @@ const stackLevel = 8 // Log a message with defined skip and at logging level func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...any) { - l.logger.Log(skip+1, level, format, v...) + l.logger.Log(skip+1, &log.Event{Level: level}, format, v...) } // Debug show debug log diff --git a/models/db/name.go b/models/db/name.go index e2165fd76b..0e11c78372 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -77,7 +77,7 @@ func (err ErrNameCharsNotAllowed) Unwrap() error { func IsUsableName(reservedNames, reservedPatterns []string, name string) error { name = strings.TrimSpace(strings.ToLower(name)) if utf8.RuneCountInString(name) == 0 { - return util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument} + return util.NewInvalidArgumentErrorf("name is empty") } for i := range reservedNames { diff --git a/models/issues/issue.go b/models/issues/issue.go index 7e72bb776c..5204f27faf 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -595,6 +595,9 @@ func GetIssueByID(ctx context.Context, id int64) (*Issue, error) { // If keepOrder is true, the order of the returned issues will be the same as the given IDs. func GetIssuesByIDs(ctx context.Context, issueIDs []int64, keepOrder ...bool) (IssueList, error) { issues := make([]*Issue, 0, len(issueIDs)) + if len(issueIDs) == 0 { + return issues, nil + } if err := db.GetEngine(ctx).In("id", issueIDs).Find(&issues); err != nil { return nil, err diff --git a/models/issues/label.go b/models/issues/label.go index b9d24bbe99..8a5d9321cc 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -299,6 +299,9 @@ func GetLabelByID(ctx context.Context, labelID int64) (*Label, error) { // GetLabelsByIDs returns a list of labels by IDs func GetLabelsByIDs(ctx context.Context, labelIDs []int64, cols ...string) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx).Table("label"). In("id", labelIDs). Asc("name"). @@ -375,6 +378,9 @@ func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { // it silently ignores label IDs that do not belong to the repository. func GetLabelsInRepoByIDs(ctx context.Context, repoID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx). Where("repo_id = ?", repoID). In("id", labelIDs). @@ -447,6 +453,9 @@ func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error // it silently ignores label IDs that do not belong to the organization. func GetLabelsInOrgByIDs(ctx context.Context, orgID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx). Where("org_id = ?", orgID). In("id", labelIDs). diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index b105fab97c..b685175f8e 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -61,7 +61,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR } // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged -func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) sess := db.GetEngine(ctx). Join("INNER", "issue", "issue.id = pull_request.issue_id"). @@ -116,7 +116,7 @@ func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged // by given base information (repo and branch). -func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) return prs, db.GetEngine(ctx). Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", diff --git a/models/issues/pull_list_test.go b/models/issues/pull_list_test.go index c7a898ca4e..f5553e7885 100644 --- a/models/issues/pull_list_test.go +++ b/models/issues/pull_list_test.go @@ -16,11 +16,11 @@ import ( func TestPullRequestList_LoadAttributes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext)) + assert.NoError(t, prs.LoadAttributes(db.DefaultContext)) for _, pr := range prs { assert.NotNil(t, pr.Issue) assert.Equal(t, pr.IssueID, pr.Issue.ID) @@ -32,11 +32,11 @@ func TestPullRequestList_LoadAttributes(t *testing.T) { func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext) + reviewComments, err := prs.LoadReviewCommentsCounts(db.DefaultContext) assert.NoError(t, err) assert.Len(t, reviewComments, 2) for _, pr := range prs { @@ -47,11 +47,11 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { func TestPullRequestList_LoadReviews(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext) + reviewList, err := prs.LoadReviews(db.DefaultContext) assert.NoError(t, err) // 1, 7, 8, 9, 10, 22 assert.Len(t, reviewList, 6) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 87d674a440..5f79a873f1 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -374,6 +374,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312) newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge), newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin), + newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables), } return preparedMigrations } @@ -412,7 +413,7 @@ func ExpectedDBVersion() int64 { } // EnsureUpToDate will check if the db is at the correct version -func EnsureUpToDate(x *xorm.Engine) error { +func EnsureUpToDate(ctx context.Context, x *xorm.Engine) error { currentDB, err := GetCurrentDBVersion(x) if err != nil { return err diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go index 44d44a26c5..cba64711d0 100644 --- a/models/migrations/v1_12/v128.go +++ b/models/migrations/v1_12/v128.go @@ -82,17 +82,17 @@ func FixMergeBase(x *xorm.Engine) error { if !pr.HasMerged { var err error - pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = git.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { var err2 error - pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err2 = git.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err2 != nil { log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2) continue } } } else { - parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) + parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -104,9 +104,9 @@ func FixMergeBase(x *xorm.Engine) error { refs := append([]string{}, parents[1:]...) refs = append(refs, gitRefName) - cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...) + cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go index 3d1c82f09e..a918d38757 100644 --- a/models/migrations/v1_12/v134.go +++ b/models/migrations/v1_12/v134.go @@ -79,7 +79,7 @@ func RefixMergeBase(x *xorm.Engine) error { gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) - parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) + parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -92,9 +92,9 @@ func RefixMergeBase(x *xorm.Engine) error { // we should recalculate refs := append([]string{}, parents[1:]...) refs = append(refs, gitRefName) - cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...) + cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/models/migrations/v1_24/v314.go b/models/migrations/v1_24/v314.go new file mode 100644 index 0000000000..e537be13b5 --- /dev/null +++ b/models/migrations/v1_24/v314.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 //nolint + +import ( + "xorm.io/xorm" +) + +func UpdateOwnerIDOfRepoLevelActionsTables(x *xorm.Engine) error { + if _, err := x.Exec("UPDATE `action_runner` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0"); err != nil { + return err + } + if _, err := x.Exec("UPDATE `action_variable` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0"); err != nil { + return err + } + _, err := x.Exec("UPDATE `secret` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + return err +} diff --git a/models/organization/org_test.go b/models/organization/org_test.go index b882a25be3..6638abd8e0 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -320,7 +320,7 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { testSuccess := func(userID, expectedCount int64) { env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) - count, err := env.CountRepos() + count, err := env.CountRepos(db.DefaultContext) assert.NoError(t, err) assert.EqualValues(t, expectedCount, count) } @@ -334,7 +334,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) { testSuccess := func(userID int64, expectedRepoIDs []int64) { env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) - repoIDs, err := env.RepoIDs(1, 100) + repoIDs, err := env.RepoIDs(db.DefaultContext, 1, 100) assert.NoError(t, err) assert.Equal(t, expectedRepoIDs, repoIDs) } @@ -348,7 +348,7 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { testSuccess := func(userID int64, expectedRepoIDs []int64) { env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) - repos, err := env.Repos(1, 100) + repos, err := env.Repos(db.DefaultContext, 1, 100) assert.NoError(t, err) expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs)) for i, repoID := range expectedRepoIDs { @@ -367,7 +367,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { testSuccess := func(userID int64, expectedRepoIDs []int64) { env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) - repos, err := env.MirrorRepos() + repos, err := env.MirrorRepos(db.DefaultContext) assert.NoError(t, err) expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs)) for i, repoID := range expectedRepoIDs { diff --git a/models/organization/team_list.go b/models/organization/team_list.go index 6f2a922e95..0274f9c5ba 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -133,5 +133,8 @@ func GetTeamsByOrgIDs(ctx context.Context, orgIDs []int64) (TeamList, error) { func GetTeamsByIDs(ctx context.Context, teamIDs []int64) (map[int64]*Team, error) { teams := make(map[int64]*Team, len(teamIDs)) + if len(teamIDs) == 0 { + return teams, nil + } return teams, db.GetEngine(ctx).Where(builder.In("`id`", teamIDs)).Find(&teams) } diff --git a/models/project/column.go b/models/project/column.go index 5f581b5880..77ff5ef83e 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -336,6 +336,9 @@ func UpdateColumnSorting(ctx context.Context, cl ColumnList) error { func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { columns := make([]*Column, 0, 5) + if len(columnsIDs) == 0 { + return columns, nil + } if err := db.GetEngine(ctx). Where("project_id =?", projectID). In("id", columnsIDs). diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 5a3eac9f14..d06e94e5ac 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -50,15 +50,15 @@ func (archiver *RepoArchiver) RelativePath() string { func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) { parts := strings.SplitN(relativePath, "/", 3) if len(parts) != 3 { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: must have 3 parts") } repoID, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid repo id") } commitID, archiveType := git.SplitArchiveNameType(parts[2]) if archiveType == git.ArchiveUnknown { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid archive type") } return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil } diff --git a/models/repo/org_repo.go b/models/repo/org_repo.go index 5f0af2d475..fa519d25b1 100644 --- a/models/repo/org_repo.go +++ b/models/repo/org_repo.go @@ -47,10 +47,10 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (Repo // AccessibleReposEnvironment operations involving the repositories that are // accessible to a particular user type AccessibleReposEnvironment interface { - CountRepos() (int64, error) - RepoIDs(page, pageSize int) ([]int64, error) - Repos(page, pageSize int) (RepositoryList, error) - MirrorRepos() (RepositoryList, error) + CountRepos(ctx context.Context) (int64, error) + RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) + Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) + MirrorRepos(ctx context.Context) (RepositoryList, error) AddKeyword(keyword string) SetSort(db.SearchOrderBy) } @@ -60,7 +60,6 @@ type accessibleReposEnv struct { user *user_model.User team *org_model.Team teamIDs []int64 - ctx context.Context keyword string orderBy db.SearchOrderBy } @@ -86,18 +85,16 @@ func AccessibleReposEnv(ctx context.Context, org *org_model.Organization, userID org: org, user: user, teamIDs: teamIDs, - ctx: ctx, orderBy: db.SearchOrderByRecentUpdated, }, nil } // AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` // that are accessible to the specified team. -func AccessibleTeamReposEnv(ctx context.Context, org *org_model.Organization, team *org_model.Team) AccessibleReposEnvironment { +func AccessibleTeamReposEnv(org *org_model.Organization, team *org_model.Team) AccessibleReposEnvironment { return &accessibleReposEnv{ org: org, team: team, - ctx: ctx, orderBy: db.SearchOrderByRecentUpdated, } } @@ -123,8 +120,8 @@ func (env *accessibleReposEnv) cond() builder.Cond { return cond } -func (env *accessibleReposEnv) CountRepos() (int64, error) { - repoCount, err := db.GetEngine(env.ctx). +func (env *accessibleReposEnv) CountRepos(ctx context.Context) (int64, error) { + repoCount, err := db.GetEngine(ctx). Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). Where(env.cond()). Distinct("`repository`.id"). @@ -135,13 +132,13 @@ func (env *accessibleReposEnv) CountRepos() (int64, error) { return repoCount, nil } -func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { +func (env *accessibleReposEnv) RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) { if page <= 0 { page = 1 } repoIDs := make([]int64, 0, pageSize) - return repoIDs, db.GetEngine(env.ctx). + return repoIDs, db.GetEngine(ctx). Table("repository"). Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). Where(env.cond()). @@ -152,8 +149,8 @@ func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { Find(&repoIDs) } -func (env *accessibleReposEnv) Repos(page, pageSize int) (RepositoryList, error) { - repoIDs, err := env.RepoIDs(page, pageSize) +func (env *accessibleReposEnv) Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) { + repoIDs, err := env.RepoIDs(ctx, page, pageSize) if err != nil { return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err) } @@ -163,15 +160,15 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) (RepositoryList, error) return repos, nil } - return repos, db.GetEngine(env.ctx). + return repos, db.GetEngine(ctx). In("`repository`.id", repoIDs). OrderBy(string(env.orderBy)). Find(&repos) } -func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { +func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) { repoIDs := make([]int64, 0, 10) - return repoIDs, db.GetEngine(env.ctx). + return repoIDs, db.GetEngine(ctx). Table("repository"). Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). Where(env.cond()). @@ -181,8 +178,8 @@ func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { Find(&repoIDs) } -func (env *accessibleReposEnv) MirrorRepos() (RepositoryList, error) { - repoIDs, err := env.MirrorRepoIDs() +func (env *accessibleReposEnv) MirrorRepos(ctx context.Context) (RepositoryList, error) { + repoIDs, err := env.MirrorRepoIDs(ctx) if err != nil { return nil, fmt.Errorf("MirrorRepoIDs: %w", err) } @@ -192,7 +189,7 @@ func (env *accessibleReposEnv) MirrorRepos() (RepositoryList, error) { return repos, nil } - return repos, db.GetEngine(env.ctx). + return repos, db.GetEngine(ctx). In("`repository`.id", repoIDs). Find(&repos) } diff --git a/models/repo/repo.go b/models/repo/repo.go index d42792faa2..13473699f3 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -839,6 +839,9 @@ func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) { // GetRepositoriesMapByIDs returns the repositories by given id slice. func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repository, error) { repos := make(map[int64]*Repository, len(ids)) + if len(ids) == 0 { + return repos, nil + } return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 9bed2e9197..02c228e8a0 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -21,11 +21,6 @@ import ( "xorm.io/builder" ) -// FindReposMapByIDs find repos as map -func FindReposMapByIDs(ctx context.Context, repoIDs []int64, res map[int64]*Repository) error { - return db.GetEngine(ctx).In("id", repoIDs).Find(&res) -} - // RepositoryListDefaultPageSize is the default number of repositories // to load in memory when running administrative tasks on all (or almost // all) of them. diff --git a/models/secret/secret.go b/models/secret/secret.go index ce0ad65a79..eab9cf0712 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -165,3 +165,17 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[ return secrets, nil } + +func CountWrongRepoLevelSecrets(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `secret` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelSecrets(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `secret` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/user/must_change_password.go b/models/user/must_change_password.go index 7eab08de89..686847c7d7 100644 --- a/models/user/must_change_password.go +++ b/models/user/must_change_password.go @@ -34,7 +34,7 @@ func SetMustChangePassword(ctx context.Context, all, mustChangePassword bool, in if !all { include = sliceTrimSpaceDropEmpty(include) if len(include) == 0 { - return 0, util.NewSilentWrapErrorf(util.ErrInvalidArgument, "no users to include provided") + return 0, util.ErrorWrap(util.ErrInvalidArgument, "no users to include provided") } cond = cond.And(builder.In("lower_name", include)) diff --git a/models/user/user_list.go b/models/user/user_list.go index c66d59f0d9..4241905058 100644 --- a/models/user/user_list.go +++ b/models/user/user_list.go @@ -11,6 +11,10 @@ import ( func GetUsersMapByIDs(ctx context.Context, userIDs []int64) (map[int64]*User, error) { userMaps := make(map[int64]*User, len(userIDs)) + if len(userIDs) == 0 { + return userMaps, nil + } + left := len(userIDs) for left > 0 { limit := db.DefaultMaxInSize diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index f2a26efbb9..6ff77a380d 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) { "pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone", "pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected", "pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release", - "package", "status", + "package", "status", "workflow_job", }, (&Webhook{ HookEvent: &webhook_module.HookEvent{SendEverything: true}, diff --git a/modules/base/tool.go b/modules/base/tool.go index b6ed8cbf9a..02ca85569e 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -17,7 +17,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -139,24 +138,3 @@ func Int64sToStrings(ints []int64) []string { } return strs } - -// EntryIcon returns the octicon name for displaying files/directories -func EntryIcon(entry *git.TreeEntry) string { - switch { - case entry.IsLink(): - te, err := entry.FollowLink() - if err != nil { - return "file-symlink-file" - } - if te.IsDir() { - return "file-directory-symlink" - } - return "file-symlink-file" - case entry.IsDir(): - return "file-directory-fill" - case entry.IsSubModule(): - return "file-submodule" - } - - return "file" -} diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go new file mode 100644 index 0000000000..040a8e87de --- /dev/null +++ b/modules/fileicon/basic.go @@ -0,0 +1,27 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package fileicon + +import ( + "html/template" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/svg" +) + +func BasicThemeIcon(entry *git.TreeEntry) template.HTML { + svgName := "octicon-file" + switch { + case entry.IsLink(): + svgName = "octicon-file-symlink-file" + if te, err := entry.FollowLink(); err == nil && te.IsDir() { + svgName = "octicon-file-directory-symlink" + } + case entry.IsDir(): + svgName = "octicon-file-directory-fill" + case entry.IsSubModule(): + svgName = "octicon-file-submodule" + } + return svg.RenderHTML(svgName) +} diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go new file mode 100644 index 0000000000..adea625c06 --- /dev/null +++ b/modules/fileicon/material.go @@ -0,0 +1,157 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package fileicon + +import ( + "html/template" + "path" + "strings" + "sync" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/reqctx" + "code.gitea.io/gitea/modules/svg" +) + +type materialIconRulesData struct { + FileNames map[string]string `json:"fileNames"` + FolderNames map[string]string `json:"folderNames"` + FileExtensions map[string]string `json:"fileExtensions"` + LanguageIDs map[string]string `json:"languageIds"` +} + +type MaterialIconProvider struct { + once sync.Once + rules *materialIconRulesData + svgs map[string]string +} + +var materialIconProvider MaterialIconProvider + +func DefaultMaterialIconProvider() *MaterialIconProvider { + materialIconProvider.once.Do(materialIconProvider.loadData) + return &materialIconProvider +} + +func (m *MaterialIconProvider) loadData() { + buf, err := options.AssetFS().ReadFile("fileicon/material-icon-rules.json") + if err != nil { + log.Error("Failed to read material icon rules: %v", err) + return + } + err = json.Unmarshal(buf, &m.rules) + if err != nil { + log.Error("Failed to unmarshal material icon rules: %v", err) + return + } + + buf, err = options.AssetFS().ReadFile("fileicon/material-icon-svgs.json") + if err != nil { + log.Error("Failed to read material icon rules: %v", err) + return + } + err = json.Unmarshal(buf, &m.svgs) + if err != nil { + log.Error("Failed to unmarshal material icon rules: %v", err) + return + } + log.Debug("Loaded material icon rules and SVG images") +} + +func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg string) template.HTML { + data := ctx.GetData() + renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool) + if renderedSVGs == nil { + renderedSVGs = make(map[string]bool) + data["_RenderedSVGs"] = renderedSVGs + } + // This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us. + // Will try to refactor this in the future. + if !strings.HasPrefix(svg, "