diff --git a/.air.toml b/.air.toml index 8854041a25b..6e3c5bdc83b 100644 --- a/.air.toml +++ b/.air.toml @@ -4,7 +4,7 @@ tmp_dir = ".air" [build] pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs cmd = "make --no-print-directory backend" -bin = "gitea" +entrypoint = ["./gitea"] delay = 2000 include_ext = ["go", "tmpl"] include_file = ["main.go"] diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml deleted file mode 100644 index 4f806e93bd8..00000000000 --- a/.github/workflows/pull-e2e-tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: e2e-tests - -on: - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - files-changed: - uses: ./.github/workflows/files-changed.yml - - test-e2e: - # the "test-e2e" won't pass, and it seems that there is no useful test, so skip - # if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' - if: false - needs: files-changed - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - check-latest: true - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v5 - with: - node-version: 24 - - run: make deps-frontend frontend deps-backend - - run: pnpm exec playwright install --with-deps - - run: make test-e2e-sqlite - timeout-minutes: 40 - env: - USE_REPO_TEST_DIR: 1 diff --git a/.golangci.yml b/.golangci.yml index 60482c415fd..2f1587a1e6d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -114,6 +114,10 @@ linters: - stringsbuilder perfsprint: concat-loop: false + govet: + enable: + - nilness + - unusedwrite exclusions: generated: lax presets: diff --git a/Makefile b/Makefile index 20a90a959db..2b9fc60eb31 100644 --- a/Makefile +++ b/Makefile @@ -32,15 +32,14 @@ XGO_VERSION := go-1.25.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.0 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 -GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0 +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.9 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -333,7 +332,7 @@ 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-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files +lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files .PHONY: lint-backend-fix lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues @@ -396,11 +395,6 @@ lint-go-gitea-vet: ## lint go files with gitea-vet @echo "Running gitea-vet..." @$(GO) vet -vettool="$(shell GOOS= GOARCH= go tool -n gitea-vet)" ./... -.PHONY: lint-go-gopls -lint-go-gopls: ## lint go files with gopls - @echo "Running gopls check..." - @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES) - .PHONY: lint-editorconfig lint-editorconfig: @echo "Running editorconfig check..." @@ -844,7 +838,6 @@ deps-tools: ## install tool dependencies $(GO) install $(GO_LICENSES_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \ - $(GO) install $(GOPLS_PACKAGE) & \ wait node_modules: pnpm-lock.yaml diff --git a/eslint.config.ts b/eslint.config.ts index 548e650bc49..c2fddc856cd 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -3,7 +3,6 @@ import comments from '@eslint-community/eslint-plugin-eslint-comments'; import github from 'eslint-plugin-github'; import globals from 'globals'; import importPlugin from 'eslint-plugin-import-x'; -import noUseExtendNative from 'eslint-plugin-no-use-extend-native'; import playwright from 'eslint-plugin-playwright'; import regexp from 'eslint-plugin-regexp'; import sonarjs from 'eslint-plugin-sonarjs'; @@ -58,7 +57,6 @@ export default defineConfig([ 'array-func': arrayFunc, // @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/203 'import-x': importPlugin, - 'no-use-extend-native': noUseExtendNative, regexp, sonarjs, unicorn, @@ -155,7 +153,7 @@ export default defineConfig([ '@typescript-eslint/ban-tslint-comment': [0], '@typescript-eslint/class-literal-property-style': [0], '@typescript-eslint/class-methods-use-this': [0], - '@typescript-eslint/consistent-generic-constructors': [0], + '@typescript-eslint/consistent-generic-constructors': [2, 'constructor'], '@typescript-eslint/consistent-indexed-object-style': [0], '@typescript-eslint/consistent-return': [0], '@typescript-eslint/consistent-type-assertions': [2, {assertionStyle: 'as', objectLiteralTypeAssertions: 'allow'}], @@ -231,6 +229,7 @@ export default defineConfig([ '@typescript-eslint/no-unsafe-return': [0], '@typescript-eslint/no-unsafe-unary-minus': [2], '@typescript-eslint/no-unused-expressions': [0], + '@typescript-eslint/no-unused-private-class-members': [2], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}], '@typescript-eslint/no-useless-constructor': [0], @@ -587,10 +586,9 @@ export default defineConfig([ 'no-unsafe-negation': [2], 'no-unused-expressions': [2], 'no-unused-labels': [2], - 'no-unused-private-class-members': [2], + 'no-unused-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars 'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define - 'no-use-extend-native/no-use-extend-native': [2], 'no-useless-assignment': [2], 'no-useless-backreference': [2], 'no-useless-call': [2], diff --git a/go.mod b/go.mod index aa77a62338c..6806e76ffc5 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module code.gitea.io/gitea go 1.25.0 -toolchain go1.25.4 +toolchain go1.25.5 // 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: @@ -17,7 +17,7 @@ require ( gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/cache v0.2.1 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 - gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15 + gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 github.com/42wim/httpsig v1.2.3 diff --git a/go.sum b/go.sum index 9af066f1e4a..86fe782ae7b 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g= gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk= -gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15 h1:qFYmz05u/s9664o7+XEgrlHXSPQ4uHO8/ccZGUb1uxA= -gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM= +gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e h1:4bugwPyGMLvblEm3pZ8fZProSPVxE4l0UXF2Kv6IJoY= +gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0= gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw= gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY= gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o= diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 15526cb1e6f..d343ae6e35f 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -276,8 +276,14 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito if !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID) || !taskRepo.IsPrivate { // The task repo can access the current repo only if the task repo is private and // the owner of the task repo is a collaborative owner of the current repo. - // FIXME allow public repo read access if tokenless pull is enabled // FIXME should owner's visibility also be considered here? + + // check permission like simple user but limit to read-only + perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser()) + if err != nil { + return perm, err + } + perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead) return perm, nil } accessMode = perm_model.AccessModeRead diff --git a/models/pull/review_state.go b/models/pull/review_state.go index e8b759c0cc6..a0f5548dd44 100644 --- a/models/pull/review_state.go +++ b/models/pull/review_state.go @@ -73,18 +73,18 @@ func GetReviewState(ctx context.Context, userID, pullID int64, commitSHA string) // UpdateReviewState updates the given review inside the database, regardless of whether it existed before or not // The given map of files with their viewed state will be merged with the previous review, if present -func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) error { +func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) (*ReviewState, error) { log.Trace("Updating review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, updatedFiles) review, exists, err := GetReviewState(ctx, userID, pullID, commitSHA) if err != nil { - return err + return nil, err } if exists { review.UpdatedFiles = mergeFiles(review.UpdatedFiles, updatedFiles) } else if previousReview, err := getNewestReviewStateApartFrom(ctx, userID, pullID, commitSHA); err != nil { - return err + return nil, err // Overwrite the viewed files of the previous review if present } else if previousReview != nil { @@ -98,11 +98,11 @@ func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA stri if !exists { log.Trace("Inserting new review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, review.UpdatedFiles) _, err := engine.Insert(review) - return err + return nil, err } log.Trace("Updating already existing review with ID %d (user %d, repo %d, commit %s) with the updated files %v.", review.ID, userID, pullID, commitSHA, review.UpdatedFiles) - _, err = engine.ID(review.ID).Update(&ReviewState{UpdatedFiles: review.UpdatedFiles}) - return err + _, err = engine.ID(review.ID).Cols("updated_files").Update(review) + return review, err } // mergeFiles merges the given maps of files with their viewing state into one map. diff --git a/models/repo/repo.go b/models/repo/repo.go index 819356dfad3..605a9e0f3fa 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -869,16 +869,6 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) } -// IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed. -func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { - has, err := IsRepositoryModelExist(ctx, u, repoName) - if err != nil { - return false, err - } - isDir, err := util.IsDir(RepoPath(u.Name, repoName)) - return has || isDir, err -} - func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { return db.GetEngine(ctx).Get(&Repository{ OwnerID: u.ID, diff --git a/models/repo/update.go b/models/repo/update.go index 3228ae11a4e..bf560cf6950 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -9,8 +9,6 @@ import ( "time" "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" ) @@ -106,35 +104,6 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error { return util.ErrAlreadyExist } -// CheckCreateRepository check if doer could create a repository in new owner -func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error { - if !doer.CanCreateRepoIn(owner) { - return ErrReachLimitOfRepo{owner.MaxRepoCreation} - } - - if err := IsUsableRepoName(name); err != nil { - return err - } - - has, err := IsRepositoryModelOrDirExist(ctx, owner, name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %w", err) - } else if has { - return ErrRepoAlreadyExist{owner.Name, name} - } - - repoPath := RepoPath(owner.Name, name) - isExist, err := util.IsExist(repoPath) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repoPath, err) - return err - } - if !overwriteOrAdopt && isExist { - return ErrRepoFilesAlreadyExist{owner.Name, name} - } - return nil -} - // UpdateRepoSize updates the repository size, calculating it using getDirectorySize func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error { _, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{ diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index cbf5279c651..86f55c6b247 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -22,7 +22,7 @@ var WebAuthn *webauthn.WebAuthn // Init initializes the WebAuthn instance from the config. func Init() { - gob.Register(&webauthn.SessionData{}) + gob.Register(&webauthn.SessionData{}) // TODO: CHI-SESSION-GOB-REGISTER. appURL, _ := protocol.FullyQualifiedOrigin(setting.AppURL) diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index 5361592d8a3..b8ee13cd8cb 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -76,7 +76,7 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, if p.IconSVGs[svgID] == "" { p.IconSVGs[svgID] = svgHTML } - return template.HTML(``) + return template.HTML(``) } func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML { diff --git a/modules/fileicon/render.go b/modules/fileicon/render.go index 8ed86b9ac0e..6b2fcfa81e8 100644 --- a/modules/fileicon/render.go +++ b/modules/fileicon/render.go @@ -25,7 +25,7 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML { return "" } sb := &strings.Builder{} - sb.WriteString(`
`) + sb.WriteString(`
`) for _, icon := range p.IconSVGs { sb.WriteString(string(icon)) } diff --git a/modules/git/attribute/attribute.go b/modules/git/attribute/attribute.go index 9c01cb339e0..8719369df8f 100644 --- a/modules/git/attribute/attribute.go +++ b/modules/git/attribute/attribute.go @@ -96,8 +96,8 @@ func (attrs *Attributes) GetGitlabLanguage() optional.Option[string] { // gitlab-language may have additional parameters after the language // ignore them and just use the main language // https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type - if idx := strings.IndexByte(raw, '?'); idx >= 0 { - return optional.Some(raw[:idx]) + if before, _, ok := strings.Cut(raw, "?"); ok { + return optional.Some(before) } } return attrStr diff --git a/modules/git/commit.go b/modules/git/commit.go index 260b81b5902..af096970182 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -5,17 +5,13 @@ package git import ( - "bufio" - "bytes" "context" "errors" "io" "os/exec" - "strconv" "strings" "code.gitea.io/gitea/modules/git/gitcmd" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" ) @@ -130,65 +126,6 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio return err } -// AllCommitsCount returns count of all commits in repository -func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) { - cmd := gitcmd.NewCommand("rev-list") - if hidePRRefs { - cmd.AddArguments("--exclude=" + PullPrefix + "*") - } - cmd.AddArguments("--all", "--count") - if len(files) > 0 { - cmd.AddDashesAndList(files...) - } - - stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - if err != nil { - return 0, err - } - - return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) -} - -// CommitsCountOptions the options when counting commits -type CommitsCountOptions struct { - RepoPath string - Not string - Revision []string - RelPath []string - Since string - Until string -} - -// CommitsCount returns number of total commits of until given revision. -func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) { - cmd := gitcmd.NewCommand("rev-list", "--count") - - cmd.AddDynamicArguments(opts.Revision...) - - if opts.Not != "" { - cmd.AddOptionValues("--not", opts.Not) - } - - if len(opts.RelPath) > 0 { - cmd.AddDashesAndList(opts.RelPath...) - } - - stdout, _, err := cmd.WithDir(opts.RepoPath).RunStdString(ctx) - if err != nil { - return 0, err - } - - return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) -} - -// CommitsCount returns number of total commits of until current revision. -func (c *Commit) CommitsCount() (int64, error) { - return CommitsCount(c.repo.Ctx, CommitsCountOptions{ - RepoPath: c.repo.Path, - Revision: []string{c.ID.String()}, - }) -} - // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) { return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until) @@ -371,85 +308,6 @@ func (c *Commit) GetBranchName() (string, error) { return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil } -// CommitFileStatus represents status of files in a commit. -type CommitFileStatus struct { - Added []string - Removed []string - Modified []string -} - -// NewCommitFileStatus creates a CommitFileStatus -func NewCommitFileStatus() *CommitFileStatus { - return &CommitFileStatus{ - []string{}, []string{}, []string{}, - } -} - -func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) { - rd := bufio.NewReader(stdout) - peek, err := rd.Peek(1) - if err != nil { - if err != io.EOF { - log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) - } - return - } - if peek[0] == '\n' || peek[0] == '\x00' { - _, _ = rd.Discard(1) - } - for { - modifier, err := rd.ReadString('\x00') - if err != nil { - if err != io.EOF { - log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) - } - return - } - file, err := rd.ReadString('\x00') - if err != nil { - if err != io.EOF { - log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) - } - return - } - file = file[:len(file)-1] - switch modifier[0] { - case 'A': - fileStatus.Added = append(fileStatus.Added, file) - case 'D': - fileStatus.Removed = append(fileStatus.Removed, file) - case 'M': - fileStatus.Modified = append(fileStatus.Modified, file) - } - } -} - -// GetCommitFileStatus returns file status of commit in given repository. -func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*CommitFileStatus, error) { - stdout, w := io.Pipe() - done := make(chan struct{}) - fileStatus := NewCommitFileStatus() - go func() { - parseCommitFileStatus(fileStatus, stdout) - close(done) - }() - - stderr := new(bytes.Buffer) - err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1"). - AddDynamicArguments(commitID). - WithDir(repoPath). - WithStdout(w). - WithStderr(stderr). - Run(ctx) - w.Close() // Close writer to exit parsing goroutine - if err != nil { - return nil, gitcmd.ConcatenateError(err, stderr.String()) - } - - <-done - return fileStatus, nil -} - // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { commitID, _, err := gitcmd.NewCommand("rev-parse"). diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go index 772f5eedb27..0aefb30c95e 100644 --- a/modules/git/commit_sha256_test.go +++ b/modules/git/commit_sha256_test.go @@ -14,33 +14,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestCommitsCountSha256(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256") - - commitsCount, err := CommitsCount(t.Context(), - CommitsCountOptions{ - RepoPath: bareRepo1Path, - Revision: []string{"f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(3), commitsCount) -} - -func TestCommitsCountWithoutBaseSha256(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256") - - commitsCount, err := CommitsCount(t.Context(), - CommitsCountOptions{ - RepoPath: bareRepo1Path, - Not: "main", - Revision: []string{"branch1"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(2), commitsCount) -} - func TestGetFullCommitIDSha256(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256") @@ -157,39 +130,3 @@ func TestHasPreviousCommitSha256(t *testing.T) { assert.NoError(t, err) assert.False(t, selfNot) } - -func TestGetCommitFileStatusMergesSha256(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo6_merge_sha256") - - commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1") - assert.NoError(t, err) - - expected := CommitFileStatus{ - []string{ - "add_file.txt", - }, - []string{}, - []string{ - "to_modify.txt", - }, - } - - assert.Equal(t, expected.Added, commitFileStatus.Added) - assert.Equal(t, expected.Removed, commitFileStatus.Removed) - assert.Equal(t, expected.Modified, commitFileStatus.Modified) - - expected = CommitFileStatus{ - []string{}, - []string{ - "to_remove.txt", - }, - []string{}, - } - - commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo1Path, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172") - assert.NoError(t, err) - - assert.Equal(t, expected.Added, commitFileStatus.Added) - assert.Equal(t, expected.Removed, commitFileStatus.Removed) - assert.Equal(t, expected.Modified, commitFileStatus.Modified) -} diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index 688b4e294f5..de7b7455ebb 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -13,33 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestCommitsCount(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - - commitsCount, err := CommitsCount(t.Context(), - CommitsCountOptions{ - RepoPath: bareRepo1Path, - Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(3), commitsCount) -} - -func TestCommitsCountWithoutBase(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - - commitsCount, err := CommitsCount(t.Context(), - CommitsCountOptions{ - RepoPath: bareRepo1Path, - Not: "master", - Revision: []string{"branch1"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(2), commitsCount) -} - func TestGetFullCommitID(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") @@ -212,134 +185,6 @@ func TestHasPreviousCommit(t *testing.T) { assert.False(t, selfNot) } -func TestParseCommitFileStatus(t *testing.T) { - type testcase struct { - output string - added []string - removed []string - modified []string - } - - kases := []testcase{ - { - // Merge commit - output: "MM\x00options/locale/locale_en-US.ini\x00", - modified: []string{ - "options/locale/locale_en-US.ini", - }, - added: []string{}, - removed: []string{}, - }, - { - // Spaces commit - output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", - removed: []string{ - "b", - "b b/b", - }, - modified: []string{}, - added: []string{ - "b b/b b/b b/b", - "b b/b b/b b/b b/b", - }, - }, - { - // larger commit - output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00", - modified: []string{ - "go.mod", - "go.sum", - "modules/ssh/ssh.go", - "vendor/github.com/gliderlabs/ssh/circle.yml", - "vendor/github.com/gliderlabs/ssh/context.go", - "vendor/github.com/gliderlabs/ssh/server.go", - "vendor/github.com/gliderlabs/ssh/session.go", - "vendor/github.com/gliderlabs/ssh/ssh.go", - "vendor/golang.org/x/sys/unix/mkerrors.sh", - "vendor/golang.org/x/sys/unix/syscall_darwin.go", - "vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go", - "vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go", - "vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go", - "vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go", - "vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go", - "vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go", - "vendor/golang.org/x/sys/unix/zerrors_linux.go", - "vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go", - "vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go", - "vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go", - "vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go", - "vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go", - "vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go", - "vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go", - "vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go", - "vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go", - "vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go", - "vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go", - "vendor/modules.txt", - }, - added: []string{ - "vendor/github.com/gliderlabs/ssh/go.mod", - "vendor/github.com/gliderlabs/ssh/go.sum", - }, - removed: []string{}, - }, - { - // git 1.7.2 adds an unnecessary \x00 on merge commit - output: "\x00MM\x00options/locale/locale_en-US.ini\x00", - modified: []string{ - "options/locale/locale_en-US.ini", - }, - added: []string{}, - removed: []string{}, - }, - { - // git 1.7.2 adds an unnecessary \n on normal commit - output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", - removed: []string{ - "b", - "b b/b", - }, - modified: []string{}, - added: []string{ - "b b/b b/b b/b", - "b b/b b/b b/b b/b", - }, - }, - } - - for _, kase := range kases { - fileStatus := NewCommitFileStatus() - parseCommitFileStatus(fileStatus, strings.NewReader(kase.output)) - - assert.Equal(t, kase.added, fileStatus.Added) - assert.Equal(t, kase.removed, fileStatus.Removed) - assert.Equal(t, kase.modified, fileStatus.Modified) - } -} - -func TestGetCommitFileStatusMerges(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo6_merge") - - commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "022f4ce6214973e018f02bf363bf8a2e3691f699") - assert.NoError(t, err) - - expected := CommitFileStatus{ - []string{ - "add_file.txt", - }, - []string{ - "to_remove.txt", - }, - []string{ - "to_modify.txt", - }, - } - - assert.Equal(t, expected.Added, commitFileStatus.Added) - assert.Equal(t, expected.Removed, commitFileStatus.Removed) - assert.Equal(t, expected.Modified, commitFileStatus.Modified) -} - func Test_GetCommitBranchStart(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") repo, err := OpenRepository(t.Context(), bareRepo1Path) diff --git a/modules/git/foreachref/parser.go b/modules/git/foreachref/parser.go index ebdc7344d0c..fa2ef316c7d 100644 --- a/modules/git/foreachref/parser.go +++ b/modules/git/foreachref/parser.go @@ -113,10 +113,10 @@ func (p *Parser) parseRef(refBlock string) (map[string]string, error) { var fieldKey string var fieldVal string - firstSpace := strings.Index(field, " ") - if firstSpace > 0 { - fieldKey = field[:firstSpace] - fieldVal = field[firstSpace+1:] + before, after, ok := strings.Cut(field, " ") + if ok { + fieldKey = before + fieldVal = after } else { // could be the case if the requested field had no value fieldKey = field diff --git a/modules/git/parse.go b/modules/git/parse.go index a7f5c58e896..d4ff0ecb23e 100644 --- a/modules/git/parse.go +++ b/modules/git/parse.go @@ -27,15 +27,15 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) { // \t var err error - posTab := bytes.IndexByte(line, '\t') - if posTab == -1 { + before, after, ok := bytes.Cut(line, []byte{'\t'}) + if !ok { return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line) } entry := new(LsTreeEntry) - entryAttrs := line[:posTab] - entryName := line[posTab+1:] + entryAttrs := before + entryName := after entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) _ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type diff --git a/modules/git/repo.go b/modules/git/repo.go index 29e70d94c86..88acbd30e60 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -32,11 +32,6 @@ type GPGSettings struct { const prettyLogFormat = `--pretty=format:%H` -// GetAllCommitsCount returns count of all commits in repository -func (repo *Repository) GetAllCommitsCount() (int64, error) { - return AllCommitsCount(repo.Ctx, repo.Path, false) -} - func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) { // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git [...] -- [...]' logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat). @@ -191,18 +186,21 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { // PushOptions options when push to remote type PushOptions struct { - Remote string - Branch string - Force bool - Mirror bool - Env []string - Timeout time.Duration + Remote string + Branch string + Force bool + ForceWithLease string + Mirror bool + Env []string + Timeout time.Duration } // Push pushs local commits to given remote branch. func Push(ctx context.Context, repoPath string, opts PushOptions) error { cmd := gitcmd.NewCommand("push") - if opts.Force { + if opts.ForceWithLease != "" { + cmd.AddOptionFormat("--force-with-lease=%s", opts.ForceWithLease) + } else if opts.Force { cmd.AddArguments("-f") } if opts.Mirror { diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 5f4487ce7e2..4a441429f41 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/setting" ) @@ -216,16 +215,6 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo return len(strings.TrimSpace(string(stdout))) > 0, nil } -// FileCommitsCount return the number of files at a revision -func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) { - return CommitsCount(repo.Ctx, - CommitsCountOptions{ - RepoPath: repo.Path, - Revision: []string{revision}, - RelPath: []string{file}, - }) -} - type CommitsByFileAndRangeOptions struct { Revision string File string @@ -433,25 +422,6 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error return repo.CommitsBetween(lastCommit, beforeCommit) } -// CommitsCountBetween return numbers of commits between two commits -func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) { - count, err := CommitsCount(repo.Ctx, CommitsCountOptions{ - RepoPath: repo.Path, - Revision: []string{start + ".." + end}, - }) - - if err != nil && strings.Contains(err.Error(), "no merge base") { - // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. - // previously it would return the results of git rev-list before last so let's try that... - return CommitsCount(repo.Ctx, CommitsCountOptions{ - RepoPath: repo.Path, - Revision: []string{start, end}, - }) - } - - return count, err -} - // commitsBefore the limit is depth, not total number of returned commits. func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) { cmd := gitcmd.NewCommand("log", prettyLogFormat) @@ -564,23 +534,6 @@ func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err e return len(stdout) > 0, err } -func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error { - if repo.LastCommitCache == nil { - commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) { - commit, err := repo.GetCommit(sha) - if err != nil { - return 0, err - } - return commit.CommitsCount() - }) - if err != nil { - return err - } - repo.LastCommitCache = NewLastCommitCache(commitsCount, fullName, repo, cache.GetCache()) - } - return nil -} - // GetCommitBranchStart returns the commit where the branch diverged func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) { cmd := gitcmd.NewCommand("log", prettyLogFormat) diff --git a/modules/gitrepo/cat_file.go b/modules/gitrepo/cat_file.go new file mode 100644 index 00000000000..c6ac74756f1 --- /dev/null +++ b/modules/gitrepo/cat_file.go @@ -0,0 +1,14 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "context" + + "code.gitea.io/gitea/modules/git" +) + +func NewBatch(ctx context.Context, repo Repository) (*git.Batch, error) { + return git.NewBatch(ctx, repoPath(repo)) +} diff --git a/modules/gitrepo/commit.go b/modules/gitrepo/commit.go new file mode 100644 index 00000000000..e0a87ac10b4 --- /dev/null +++ b/modules/gitrepo/commit.go @@ -0,0 +1,96 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "context" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/gitcmd" +) + +// CommitsCountOptions the options when counting commits +type CommitsCountOptions struct { + Not string + Revision []string + RelPath []string + Since string + Until string +} + +// CommitsCount returns number of total commits of until given revision. +func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions) (int64, error) { + cmd := gitcmd.NewCommand("rev-list", "--count") + + cmd.AddDynamicArguments(opts.Revision...) + + if opts.Not != "" { + cmd.AddOptionValues("--not", opts.Not) + } + + if len(opts.RelPath) > 0 { + cmd.AddDashesAndList(opts.RelPath...) + } + + stdout, _, err := cmd.WithDir(repoPath(repo)).RunStdString(ctx) + if err != nil { + return 0, err + } + + return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) +} + +// CommitsCountBetween return numbers of commits between two commits +func CommitsCountBetween(ctx context.Context, repo Repository, start, end string) (int64, error) { + count, err := CommitsCount(ctx, repo, CommitsCountOptions{ + Revision: []string{start + ".." + end}, + }) + + if err != nil && strings.Contains(err.Error(), "no merge base") { + // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. + // previously it would return the results of git rev-list before last so let's try that... + return CommitsCount(ctx, repo, CommitsCountOptions{ + Revision: []string{start, end}, + }) + } + + return count, err +} + +// FileCommitsCount return the number of files at a revision +func FileCommitsCount(ctx context.Context, repo Repository, revision, file string) (int64, error) { + return CommitsCount(ctx, repo, + CommitsCountOptions{ + Revision: []string{revision}, + RelPath: []string{file}, + }) +} + +// CommitsCountOfCommit returns number of total commits of until current revision. +func CommitsCountOfCommit(ctx context.Context, repo Repository, commitID string) (int64, error) { + return CommitsCount(ctx, repo, CommitsCountOptions{ + Revision: []string{commitID}, + }) +} + +// AllCommitsCount returns count of all commits in repository +func AllCommitsCount(ctx context.Context, repo Repository, hidePRRefs bool, files ...string) (int64, error) { + cmd := gitcmd.NewCommand("rev-list") + if hidePRRefs { + cmd.AddArguments("--exclude=" + git.PullPrefix + "*") + } + cmd.AddArguments("--all", "--count") + if len(files) > 0 { + cmd.AddDashesAndList(files...) + } + + stdout, err := RunCmdString(ctx, repo, cmd) + if err != nil { + return 0, err + } + + return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) +} diff --git a/modules/gitrepo/commit_file.go b/modules/gitrepo/commit_file.go new file mode 100644 index 00000000000..cd4bb340d0e --- /dev/null +++ b/modules/gitrepo/commit_file.go @@ -0,0 +1,93 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "bufio" + "bytes" + "context" + "io" + + "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/log" +) + +// CommitFileStatus represents status of files in a commit. +type CommitFileStatus struct { + Added []string + Removed []string + Modified []string +} + +// NewCommitFileStatus creates a CommitFileStatus +func NewCommitFileStatus() *CommitFileStatus { + return &CommitFileStatus{ + []string{}, []string{}, []string{}, + } +} + +func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) { + rd := bufio.NewReader(stdout) + peek, err := rd.Peek(1) + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + if peek[0] == '\n' || peek[0] == '\x00' { + _, _ = rd.Discard(1) + } + for { + modifier, err := rd.ReadString('\x00') + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + file, err := rd.ReadString('\x00') + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + file = file[:len(file)-1] + switch modifier[0] { + case 'A': + fileStatus.Added = append(fileStatus.Added, file) + case 'D': + fileStatus.Removed = append(fileStatus.Removed, file) + case 'M': + fileStatus.Modified = append(fileStatus.Modified, file) + } + } +} + +// GetCommitFileStatus returns file status of commit in given repository. +func GetCommitFileStatus(ctx context.Context, repo Repository, commitID string) (*CommitFileStatus, error) { + stdout, w := io.Pipe() + done := make(chan struct{}) + fileStatus := NewCommitFileStatus() + go func() { + parseCommitFileStatus(fileStatus, stdout) + close(done) + }() + + stderr := new(bytes.Buffer) + err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1"). + AddDynamicArguments(commitID). + WithDir(repoPath(repo)). + WithStdout(w). + WithStderr(stderr). + Run(ctx) + w.Close() // Close writer to exit parsing goroutine + if err != nil { + return nil, gitcmd.ConcatenateError(err, stderr.String()) + } + + <-done + return fileStatus, nil +} diff --git a/modules/gitrepo/commit_file_test.go b/modules/gitrepo/commit_file_test.go new file mode 100644 index 00000000000..ec1018eebae --- /dev/null +++ b/modules/gitrepo/commit_file_test.go @@ -0,0 +1,175 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseCommitFileStatus(t *testing.T) { + type testcase struct { + output string + added []string + removed []string + modified []string + } + + kases := []testcase{ + { + // Merge commit + output: "MM\x00options/locale/locale_en-US.ini\x00", + modified: []string{ + "options/locale/locale_en-US.ini", + }, + added: []string{}, + removed: []string{}, + }, + { + // Spaces commit + output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", + removed: []string{ + "b", + "b b/b", + }, + modified: []string{}, + added: []string{ + "b b/b b/b b/b", + "b b/b b/b b/b b/b", + }, + }, + { + // larger commit + output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00", + modified: []string{ + "go.mod", + "go.sum", + "modules/ssh/ssh.go", + "vendor/github.com/gliderlabs/ssh/circle.yml", + "vendor/github.com/gliderlabs/ssh/context.go", + "vendor/github.com/gliderlabs/ssh/server.go", + "vendor/github.com/gliderlabs/ssh/session.go", + "vendor/github.com/gliderlabs/ssh/ssh.go", + "vendor/golang.org/x/sys/unix/mkerrors.sh", + "vendor/golang.org/x/sys/unix/syscall_darwin.go", + "vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go", + "vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go", + "vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go", + "vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go", + "vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go", + "vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go", + "vendor/golang.org/x/sys/unix/zerrors_linux.go", + "vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go", + "vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go", + "vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go", + "vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go", + "vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go", + "vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go", + "vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go", + "vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go", + "vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go", + "vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go", + "vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go", + "vendor/modules.txt", + }, + added: []string{ + "vendor/github.com/gliderlabs/ssh/go.mod", + "vendor/github.com/gliderlabs/ssh/go.sum", + }, + removed: []string{}, + }, + { + // git 1.7.2 adds an unnecessary \x00 on merge commit + output: "\x00MM\x00options/locale/locale_en-US.ini\x00", + modified: []string{ + "options/locale/locale_en-US.ini", + }, + added: []string{}, + removed: []string{}, + }, + { + // git 1.7.2 adds an unnecessary \n on normal commit + output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", + removed: []string{ + "b", + "b b/b", + }, + modified: []string{}, + added: []string{ + "b b/b b/b b/b", + "b b/b b/b b/b b/b", + }, + }, + } + + for _, kase := range kases { + fileStatus := NewCommitFileStatus() + parseCommitFileStatus(fileStatus, strings.NewReader(kase.output)) + + assert.Equal(t, kase.added, fileStatus.Added) + assert.Equal(t, kase.removed, fileStatus.Removed) + assert.Equal(t, kase.modified, fileStatus.Modified) + } +} + +func TestGetCommitFileStatusMerges(t *testing.T) { + bareRepo6 := &mockRepository{path: "repo6_merge"} + + commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6, "022f4ce6214973e018f02bf363bf8a2e3691f699") + assert.NoError(t, err) + + expected := CommitFileStatus{ + []string{ + "add_file.txt", + }, + []string{ + "to_remove.txt", + }, + []string{ + "to_modify.txt", + }, + } + + assert.Equal(t, expected.Added, commitFileStatus.Added) + assert.Equal(t, expected.Removed, commitFileStatus.Removed) + assert.Equal(t, expected.Modified, commitFileStatus.Modified) +} + +func TestGetCommitFileStatusMergesSha256(t *testing.T) { + bareRepo6Sha256 := &mockRepository{path: "repo6_merge_sha256"} + + commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1") + assert.NoError(t, err) + + expected := CommitFileStatus{ + []string{ + "add_file.txt", + }, + []string{}, + []string{ + "to_modify.txt", + }, + } + + assert.Equal(t, expected.Added, commitFileStatus.Added) + assert.Equal(t, expected.Removed, commitFileStatus.Removed) + assert.Equal(t, expected.Modified, commitFileStatus.Modified) + + expected = CommitFileStatus{ + []string{}, + []string{ + "to_remove.txt", + }, + []string{}, + } + + commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo6Sha256, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172") + assert.NoError(t, err) + + assert.Equal(t, expected.Added, commitFileStatus.Added) + assert.Equal(t, expected.Removed, commitFileStatus.Removed) + assert.Equal(t, expected.Modified, commitFileStatus.Modified) +} diff --git a/modules/gitrepo/commit_test.go b/modules/gitrepo/commit_test.go new file mode 100644 index 00000000000..93483f3e0de --- /dev/null +++ b/modules/gitrepo/commit_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommitsCount(t *testing.T) { + bareRepo1 := &mockRepository{path: "repo1_bare"} + + commitsCount, err := CommitsCount(t.Context(), bareRepo1, + CommitsCountOptions{ + Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}, + }) + + assert.NoError(t, err) + assert.Equal(t, int64(3), commitsCount) +} + +func TestCommitsCountWithoutBase(t *testing.T) { + bareRepo1 := &mockRepository{path: "repo1_bare"} + + commitsCount, err := CommitsCount(t.Context(), bareRepo1, + CommitsCountOptions{ + Not: "master", + Revision: []string{"branch1"}, + }) + + assert.NoError(t, err) + assert.Equal(t, int64(2), commitsCount) +} diff --git a/modules/gitrepo/push.go b/modules/gitrepo/push.go index 18808cac245..920c317f796 100644 --- a/modules/gitrepo/push.go +++ b/modules/gitrepo/push.go @@ -9,6 +9,19 @@ import ( "code.gitea.io/gitea/modules/git" ) -func Push(ctx context.Context, repo Repository, opts git.PushOptions) error { +// PushToExternal pushes a managed repository to an external remote. +func PushToExternal(ctx context.Context, repo Repository, opts git.PushOptions) error { return git.Push(ctx, repoPath(repo), opts) } + +// Push pushes from one managed repository to another managed repository. +func Push(ctx context.Context, fromRepo, toRepo Repository, opts git.PushOptions) error { + opts.Remote = repoPath(toRepo) + return git.Push(ctx, repoPath(fromRepo), opts) +} + +// PushFromLocal pushes from a local path to a managed repository. +func PushFromLocal(ctx context.Context, fromLocalPath string, toRepo Repository, opts git.PushOptions) error { + opts.Remote = repoPath(toRepo) + return git.Push(ctx, fromLocalPath, opts) +} diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 77f24fa3f38..77e47fdf486 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -77,8 +77,8 @@ func Code(fileName, language, code string) (output template.HTML, lexerName stri if lexer == nil { // Attempt stripping off the '?' - if idx := strings.IndexByte(language, '?'); idx > 0 { - lexer = lexers.Get(language[:idx]) + if before, _, ok := strings.Cut(language, "?"); ok { + lexer = lexers.Get(before) } } } diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index 0e2d0f879a5..bdb477ce6e7 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -218,7 +218,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) if len(changes.Updates) > 0 { - gitBatch, err := git.NewBatch(ctx, repo.RepoPath()) + gitBatch, err := gitrepo.NewBatch(ctx, repo) if err != nil { return err } diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 012c57da291..b2eb301a5dc 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -210,7 +210,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) elasti func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { reqs := make([]elastic.BulkableRequest, 0) if len(changes.Updates) > 0 { - batch, err := git.NewBatch(ctx, repo.RepoPath()) + batch, err := gitrepo.NewBatch(ctx, repo) if err != nil { return err } diff --git a/modules/indexer/code/internal/util.go b/modules/indexer/code/internal/util.go index fa958be473b..5d62a5ccb9d 100644 --- a/modules/indexer/code/internal/util.go +++ b/modules/indexer/code/internal/util.go @@ -17,20 +17,20 @@ func FilenameIndexerID(repoID int64, filename string) string { } func ParseIndexerID(indexerID string) (int64, string) { - index := strings.IndexByte(indexerID, '_') - if index == -1 { + before, after, ok := strings.Cut(indexerID, "_") + if !ok { log.Error("Unexpected ID in repo indexer: %s", indexerID) } - repoID, _ := internal.ParseBase36(indexerID[:index]) - return repoID, indexerID[index+1:] + repoID, _ := internal.ParseBase36(before) + return repoID, after } func FilenameOfIndexerID(indexerID string) string { - index := strings.IndexByte(indexerID, '_') - if index == -1 { + _, after, ok := strings.Cut(indexerID, "_") + if !ok { log.Error("Unexpected ID in repo indexer: %s", indexerID) } - return indexerID[index+1:] + return after } // FilenameMatchIndexPos returns the boundaries of its first seven lines. diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index 43faef16819..7523ebaed06 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -33,7 +33,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { // Of text and link contents sl := strings.SplitSeq(content, "|") for v := range sl { - if equalPos := strings.IndexByte(v, '='); equalPos == -1 { + if found := strings.Contains(v, "="); !found { // There is no equal in this argument; this is a mandatory arg if props["name"] == "" { if IsFullURLString(v) { @@ -55,8 +55,8 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } else { // There is an equal; optional argument. - sep := strings.IndexByte(v, '=') - key, val := v[:sep], html.UnescapeString(v[sep+1:]) + before, after, _ := strings.Cut(v, "=") + key, val := before, html.UnescapeString(after) // When parsing HTML, x/net/html will change all quotes which are // not used for syntax into UTF-8 quotes. So checking val[0] won't diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go index a75f18d36a8..9e368cb689a 100644 --- a/modules/markup/markdown/markdown_math_test.go +++ b/modules/markup/markdown/markdown_math_test.go @@ -30,6 +30,10 @@ func TestMathRender(t *testing.T) { "$ a $", `

a

` + nl, }, + { + "$a$$b$", + `

ab

` + nl, + }, { "$a$ $b$", `

a b

` + nl, @@ -59,7 +63,7 @@ func TestMathRender(t *testing.T) { `

a$b $a a$b b$

` + nl, }, { - "a$x$", + "a$x$", // Pattern: "word$other$" The real world example is: "Price is between US$1 and US$2.", so don't parse this. `

a$x$

` + nl, }, { @@ -70,6 +74,10 @@ func TestMathRender(t *testing.T) { "$a$ ($b$) [$c$] {$d$}", `

a (b) [$c$] {$d$}

` + nl, }, + { + "[$a$](link)", + `

a

` + nl, + }, { "$$a$$", `

a

` + nl, diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go index a711d1e1cd1..564861df90b 100644 --- a/modules/markup/markdown/math/inline_parser.go +++ b/modules/markup/markdown/math/inline_parser.go @@ -54,6 +54,10 @@ func isAlphanumeric(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') } +func isInMarkdownLinkText(block text.Reader, lineAfter []byte) bool { + return block.PrecendingCharacter() == '[' && bytes.HasPrefix(lineAfter, []byte("](")) +} + // Parse parses the current line and returns a result of parsing. func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { line, _ := block.PeekLine() @@ -115,7 +119,9 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. } // check valid ending character isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) || - succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 + succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 || + succeedingCharacter == '$' || + isInMarkdownLinkText(block, line[i+len(stopMark):]) if checkSurrounding && !isValidEndingChar { break } diff --git a/modules/repository/commits.go b/modules/repository/commits.go index 878fdc16039..a3e253e998e 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -72,7 +73,7 @@ func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.U committerUsername = committer.Name } - fileStatus, err := git.GetCommitFileStatus(ctx, repo.RepoPath(), commit.Sha1) + fileStatus, err := gitrepo.GetCommitFileStatus(ctx, repo, commit.Sha1) if err != nil { return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %w", commit.Sha1, err) } diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index 8b204e5c7c9..4758eb72cb5 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -51,10 +51,10 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) { for _, unescapeIdx := range escapeStringIndices { preceding := encoded[last:unescapeIdx[0]] if !inKey { - if splitter := strings.Index(preceding, "__"); splitter > -1 { - section += preceding[:splitter] + if before, after, cutOk := strings.Cut(preceding, "__"); cutOk { + section += before inKey = true - key += preceding[splitter+2:] + key += after } else { section += preceding } @@ -77,9 +77,9 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) { } remaining := encoded[last:] if !inKey { - if splitter := strings.Index(remaining, "__"); splitter > -1 { - section += remaining[:splitter] - key += remaining[splitter+2:] + if before, after, cutOk := strings.Cut(remaining, "__"); cutOk { + section += before + key += after } else { section += remaining } @@ -111,21 +111,21 @@ func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, sect func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { for _, kv := range envs { - idx := strings.IndexByte(kv, '=') - if idx < 0 { + before, after, ok := strings.Cut(kv, "=") + if !ok { continue } // parse the environment variable to config section name and key name - envKey := kv[:idx] - envValue := kv[idx+1:] + envKey := before + envValue := after ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey) if !ok { continue } // use environment value as config value, or read the file content as value if the key indicates a file - keyValue := envValue + keyValue := envValue //nolint:staticcheck // false positive if useFileValue { fileContent, err := os.ReadFile(envValue) if err != nil { diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index b6f9f07f98e..57dc23b17f7 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -337,14 +337,14 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) { func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { if rootCfg.Section(oldSection).HasKey(oldKey) { - LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) + LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) } } // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { if rootCfg.Section(oldSection).HasKey(oldKey) { - LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey) + LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey) } } diff --git a/modules/setting/server.go b/modules/setting/server.go index cedca32da9e..a865e942a62 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -370,6 +370,10 @@ func loadServerFrom(rootCfg ConfigProvider) { } } + // TODO: GOLANG-HTTP-TMPDIR: Some Golang packages (like "http") use os.TempDir() to create temporary files when uploading files. + // So ideally we should set the TMPDIR environment variable to make them use our managed temp directory. + // But there is no clear place to set it currently, for example: when running "install" page, the AppDataPath is not ready yet, then AppDataTempDir won't work + EnableGzip = sec.Key("ENABLE_GZIP").MustBool() EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof")) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index c1c85837fc8..47973a5f6a3 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -292,6 +292,21 @@ type RenameBranchRepoOption struct { Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"` } +// UpdateBranchRepoOption options when updating a branch reference in a repository +// swagger:model +type UpdateBranchRepoOption struct { + // New commit SHA (or any ref) the branch should point to + // + // required: true + NewCommitID string `json:"new_commit_id" binding:"Required"` + + // Expected old commit SHA of the branch; if provided it must match the current tip + OldCommitID string `json:"old_commit_id"` + + // Force update even if the change is not a fast-forward + Force bool `json:"force"` +} + // TransferRepoOption options when transfer a repository's ownership // swagger:model type TransferRepoOption struct { diff --git a/modules/validation/binding.go b/modules/validation/binding.go index 335f248ead1..3ecc5326136 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -215,8 +215,8 @@ func addValidGroupTeamMapRule() { } func portOnly(hostport string) string { - colon := strings.IndexByte(hostport, ':') - if colon == -1 { + _, after, ok := strings.Cut(hostport, ":") + if !ok { return "" } if i := strings.Index(hostport, "]:"); i != -1 { @@ -225,7 +225,7 @@ func portOnly(hostport string) string { if strings.Contains(hostport, "]") { return "" } - return hostport[colon+len(":"):] + return after } func validPort(p string) bool { diff --git a/modules/web/routemock.go b/modules/web/routemock.go index e85b0db7389..68d19475e9f 100644 --- a/modules/web/routemock.go +++ b/modules/web/routemock.go @@ -46,11 +46,15 @@ func RouterMockPoint(pointName string) func(next http.Handler) http.Handler { // // Then the mock function will be executed as a middleware at the mock point. // It only takes effect in testing mode (setting.IsInTesting == true). -func RouteMock(pointName string, h any) { +func RouteMock(pointName string, h any) func() { if _, ok := routeMockPoints[pointName]; !ok { panic("route mock point not found: " + pointName) } + old := routeMockPoints[pointName] routeMockPoints[pointName] = toHandlerProvider(h) + return func() { + routeMockPoints[pointName] = old + } } // RouteMockReset resets all mock points (no mock anymore) diff --git a/modules/web/router.go b/modules/web/router.go index 5812ff69d49..5374f82a23c 100644 --- a/modules/web/router.go +++ b/modules/web/router.go @@ -55,7 +55,7 @@ func NewRouter() *Router { // Use supports two middlewares func (r *Router) Use(middlewares ...any) { for _, m := range middlewares { - if m != nil { + if !isNilOrFuncNil(m) { r.chiRouter.Use(toHandlerProvider(m)) } } diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index d8c71562918..6b17e5be67a 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -3219,6 +3219,22 @@ ".favicons": "folder-favicon", "_favicons": "folder-favicon", "__favicons__": "folder-favicon", + "feature": "folder-features", + ".feature": "folder-features", + "_feature": "folder-features", + "__feature__": "folder-features", + "features": "folder-features", + ".features": "folder-features", + "_features": "folder-features", + "__features__": "folder-features", + "feat": "folder-features", + ".feat": "folder-features", + "_feat": "folder-features", + "__feat__": "folder-features", + "feats": "folder-features", + ".feats": "folder-features", + "_feats": "folder-features", + "__feats__": "folder-features", "lefthook": "folder-lefthook", ".lefthook": "folder-lefthook", "_lefthook": "folder-lefthook", @@ -3474,7 +3490,15 @@ "cues": "folder-cue", ".cues": "folder-cue", "_cues": "folder-cue", - "__cues__": "folder-cue" + "__cues__": "folder-cue", + "license": "folder-license", + ".license": "folder-license", + "_license": "folder-license", + "__license__": "folder-license", + "licenses": "folder-license", + ".licenses": "folder-license", + "_licenses": "folder-license", + "__licenses__": "folder-license" }, "folderNamesExpanded": { "rust": "folder-rust-open", @@ -6696,6 +6720,22 @@ ".favicons": "folder-favicon-open", "_favicons": "folder-favicon-open", "__favicons__": "folder-favicon-open", + "feature": "folder-features-open", + ".feature": "folder-features-open", + "_feature": "folder-features-open", + "__feature__": "folder-features-open", + "features": "folder-features-open", + ".features": "folder-features-open", + "_features": "folder-features-open", + "__features__": "folder-features-open", + "feat": "folder-features-open", + ".feat": "folder-features-open", + "_feat": "folder-features-open", + "__feat__": "folder-features-open", + "feats": "folder-features-open", + ".feats": "folder-features-open", + "_feats": "folder-features-open", + "__feats__": "folder-features-open", "lefthook": "folder-lefthook-open", ".lefthook": "folder-lefthook-open", "_lefthook": "folder-lefthook-open", @@ -6951,7 +6991,15 @@ "cues": "folder-cue-open", ".cues": "folder-cue-open", "_cues": "folder-cue-open", - "__cues__": "folder-cue-open" + "__cues__": "folder-cue-open", + "license": "folder-license-open", + ".license": "folder-license-open", + "_license": "folder-license-open", + "__license__": "folder-license-open", + "licenses": "folder-license-open", + ".licenses": "folder-license-open", + "_licenses": "folder-license-open", + "__licenses__": "folder-license-open" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {}, @@ -7102,6 +7150,8 @@ "srf": "image", "srw": "image", "x3f": "image", + "ktx": "image", + "ktx2": "image", "pal": "palette", "gpl": "palette", "act": "palette", @@ -7287,6 +7337,7 @@ "cp": "cpp", "mii": "cpp", "ii": "cpp", + "cppm": "cpp", "hh": "hpp", "hpp": "hpp", "hxx": "hpp", @@ -7373,6 +7424,11 @@ "fsi": "fsharp", "fsproj": "fsharp", "swift": "swift", + "xcplayground": "swift", + "swiftdeps": "swift", + "swiftdoc": "swift", + "swiftmodule": "swift", + "swiftsourceinfo": "swift", "ino": "arduino", "dockerignore": "docker", "dockerfile": "docker", @@ -8105,10 +8161,10 @@ "css.jsx": "vanilla-extract", "toc": "toc", "cue": "cue", + "lean": "lean", "cljx": "clojure", "clojure": "clojure", "edn": "clojure", - "cppm": "cpp", "ccm": "cpp", "cxxm": "cpp", "c++m": "cpp", @@ -8412,36 +8468,36 @@ "gradlew": "gradle", "gradle-wrapper.properties": "gradle", "gradlew.bat": "gradle", - "copying": "certificate", - "copying.md": "certificate", - "copying.rst": "certificate", - "copying.txt": "certificate", - "copyright": "certificate", - "copyright.md": "certificate", - "copyright.rst": "certificate", - "copyright.txt": "certificate", - "license": "certificate", - "license-agpl": "certificate", - "license-apache": "certificate", - "license-bsd": "certificate", - "license-mit": "certificate", - "license-gpl": "certificate", - "license-lgpl": "certificate", - "license.md": "certificate", - "license.rst": "certificate", - "license.txt": "certificate", - "licence": "certificate", - "licence-agpl": "certificate", - "licence-apache": "certificate", - "licence-bsd": "certificate", - "licence-mit": "certificate", - "licence-gpl": "certificate", - "licence-lgpl": "certificate", - "licence.md": "certificate", - "licence.rst": "certificate", - "licence.txt": "certificate", - "unlicense": "certificate", - "unlicense.txt": "certificate", + "copying": "license", + "copying.md": "license", + "copying.rst": "license", + "copying.txt": "license", + "copyright": "license", + "copyright.md": "license", + "copyright.rst": "license", + "copyright.txt": "license", + "license": "license", + "license-agpl": "license", + "license-apache": "license", + "license-bsd": "license", + "license-mit": "license", + "license-gpl": "license", + "license-lgpl": "license", + "license.md": "license", + "license.rst": "license", + "license.txt": "license", + "licence": "license", + "licence-agpl": "license", + "licence-apache": "license", + "licence-bsd": "license", + "licence-mit": "license", + "licence-gpl": "license", + "licence-lgpl": "license", + "licence.md": "license", + "licence.rst": "license", + "licence.txt": "license", + "unlicense": "unlicense", + "unlicense.txt": "unlicense", ".htpasswd": "key", "sha256sums": "key", ".secrets": "key", @@ -8457,6 +8513,7 @@ ".rspec": "rspec", ".swift-format": "swift", ".swift-version": "swift", + ".swiftformat": "swift", "dockerfile": "docker", "dockerfile.prod": "docker", "dockerfile.production": "docker", @@ -8993,6 +9050,12 @@ "rspress.config.ts": "rstack", "rslint.json": "rstack", "rslint.jsonc": "rstack", + "lynx.config.js": "lynx", + "lynx.config.mjs": "lynx", + "lynx.config.cjs": "lynx", + "lynx.config.ts": "lynx", + "lynx.config.mts": "lynx", + "lynx.config.cts": "lynx", "ionic.config.json": "ionic", ".io-config.json": "ionic", "gulpfile.js": "gulp", @@ -9722,6 +9785,18 @@ "vitest.config.ts": "vitest", "vitest.config.mts": "vitest", "vitest.config.cts": "vitest", + "vitest.unit.config.js": "vitest", + "vitest.unit.config.mjs": "vitest", + "vitest.unit.config.cjs": "vitest", + "vitest.unit.config.ts": "vitest", + "vitest.unit.config.mts": "vitest", + "vitest.unit.config.cts": "vitest", + "vitest.e2e.config.js": "vitest", + "vitest.e2e.config.mjs": "vitest", + "vitest.e2e.config.cjs": "vitest", + "vitest.e2e.config.ts": "vitest", + "vitest.e2e.config.mts": "vitest", + "vitest.e2e.config.cts": "vitest", "velite.config.js": "velite", "velite.config.mjs": "velite", "velite.config.cjs": "velite", @@ -10169,6 +10244,7 @@ ".coderabbit.yml": "coderabbit-ai", ".coderabbit.yaml": "coderabbit-ai", ".aiexclude": "gemini-ai", + "gemini.md": "gemini-ai", "taze.config.js": "taze", "taze.config.mjs": "taze", "taze.config.cjs": "taze", @@ -10273,7 +10349,10 @@ "hadolint.yaml": "hadolint", "hadolint.yml": "hadolint", "tsdoc.json": "tsdoc", - ".oxlintrc.json": "oxlint", + ".oxlintrc.json": "oxc", + ".oxlintrc.jsonc": "oxc", + ".oxfmtrc.json": "oxc", + ".oxfmtrc.jsonc": "oxc", "claude.md": "claude", "claude.local.md": "claude", ".cursorignore": "cursor", @@ -10296,6 +10375,7 @@ "googleservice-info.plist": "google", ".shellcheckrc": "shellcheck", "shellcheckrc": "shellcheck", + "warp.md": "warp", "language-configuration.json": "jsonc", "icon-theme.json": "jsonc", "color-theme.json": "jsonc", @@ -10322,6 +10402,7 @@ "toml": "toml", "diff": "diff", "json": "json", + "jsonl": "json", "jsonc": "json", "json5": "json", "blink": "blink", @@ -10497,7 +10578,8 @@ "gnuplot": "gnuplot", "helm": "helm", "nginx": "nginx", - "cue": "cue" + "cue": "cue", + "lean": "lean" }, "light": { "fileExtensions": { @@ -10705,7 +10787,8 @@ "src/bashly-strings.yaml": "bashly-strings_light", "src/bashly-strings.yml": "bashly-strings_light", ".shellcheckrc": "shellcheck_light", - "shellcheckrc": "shellcheck_light" + "shellcheckrc": "shellcheck_light", + "warp.md": "warp_light" }, "languageIds": { "toml": "toml_light", diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json index 61524074fd6..f5254099ad7 100644 --- a/options/fileicon/material-icon-svgs.json +++ b/options/fileicon/material-icon-svgs.json @@ -92,7 +92,7 @@ "capnp": "", "cbx": "", "cds": "", - "certificate": "", + "certificate": "", "changelog": "", "chess": "", "chess_light": "", @@ -359,6 +359,8 @@ "folder-fastlane": "", "folder-favicon-open": "", "folder-favicon": "", + "folder-features-open": "", + "folder-features": "", "folder-filter-open": "", "folder-filter": "", "folder-firebase-open": "", @@ -459,6 +461,8 @@ "folder-less": "", "folder-lib-open": "", "folder-lib": "", + "folder-license-open": "", + "folder-license": "", "folder-link-open": "", "folder-link": "", "folder-linux-open": "", @@ -829,11 +833,13 @@ "latex.clone": "", "latexmk": "", "lbx": "", + "lean": "", "lefthook": "", "lerna": "", "less": "", "liara": "", "lib": "", + "license": "", "lighthouse": "", "lilypond": "", "lintstaged": "", @@ -846,6 +852,7 @@ "lottie": "", "lua": "", "luau": "", + "lynx": "", "lyric": "", "makefile": "", "markdoc-config": "", @@ -922,7 +929,7 @@ "opentofu": "", "opentofu_light": "", "otne": "", - "oxlint": "", + "oxc": "", "packship": "", "palette": "", "panda": "", @@ -971,7 +978,7 @@ "python": "", "pytorch": "", "qsharp": "", - "quarto": "", + "quarto": "", "quasar": "", "quokka": "", "qwik": "", @@ -1126,6 +1133,7 @@ "uml": "", "uml_light": "", "unity": "", + "unlicense": "", "unocss": "", "url": "", "uv": "", @@ -1159,6 +1167,8 @@ "wakatime_light": "", "wallaby": "", "wally": "", + "warp": "", + "warp_light": "", "watchman": "", "webassembly": "", "webhint": "", diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b5b90b31a53..981d9de2f86 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -215,6 +215,7 @@ more = More buttons.heading.tooltip = Add heading buttons.bold.tooltip = Add bold text buttons.italic.tooltip = Add italic text +buttons.strikethrough.tooltip = Add strikethrough text buttons.quote.tooltip = Quote text buttons.code.tooltip = Add code buttons.link.tooltip = Add a link @@ -1354,8 +1355,11 @@ editor.this_file_locked = File is locked editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. editor.fork_before_edit = You must fork this repository to make or propose changes to this file. editor.delete_this_file = Delete File +editor.delete_this_directory = Delete Directory editor.must_have_write_access = You must have write access to make or propose changes to this file. editor.file_delete_success = File "%s" has been deleted. +editor.directory_delete_success = Directory "%s" has been deleted. +editor.delete_directory = Delete directory '%s' editor.name_your_file = Name your file… editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. editor.or = or diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 221abb5d1f2..886b3955bd3 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -215,6 +215,7 @@ more=Plus buttons.heading.tooltip=Ajouter un en-tête buttons.bold.tooltip=Ajouter du texte en gras buttons.italic.tooltip=Ajouter du texte en italique +buttons.strikethrough.tooltip=Ajouté un texte barré buttons.quote.tooltip=Citer le texte buttons.code.tooltip=Ajouter du code buttons.link.tooltip=Ajouter un lien @@ -1354,8 +1355,11 @@ editor.this_file_locked=Le fichier est verrouillé editor.must_be_on_a_branch=Vous devez être sur une branche pour appliquer ou proposer des modifications à ce fichier. editor.fork_before_edit=Vous devez faire bifurquer ce dépôt pour appliquer ou proposer des modifications à ce fichier. editor.delete_this_file=Supprimer le fichier +editor.delete_this_directory=Supprimer le répertoire editor.must_have_write_access=Vous devez avoir un accès en écriture pour appliquer ou proposer des modifications à ce fichier. editor.file_delete_success=Le fichier "%s" a été supprimé. +editor.directory_delete_success=Le répertoire « %s » a été supprimé. +editor.delete_directory=Supprimer le répertoire « %s » editor.name_your_file=Nommez votre fichier… editor.filename_help=Ajoutez un dossier en entrant son nom suivi d'une barre oblique ('/'). Supprimez un dossier avec un retour arrière au début du champ. editor.or=ou @@ -1482,6 +1486,7 @@ projects.column.new_submit=Créer une colonne projects.column.new=Nouvelle colonne projects.column.set_default=Définir par défaut projects.column.set_default_desc=Les tickets et demandes d’ajout non-catégorisés seront placés dans cette colonne. +projects.column.default_column_hint=Les nouveaux tickets ajoutés à ce projet seront ajoutés dans cette colonne projects.column.delete=Supprimer la colonne projects.column.deletion_desc=La suppression d’une colonne déplace tous ses tickets dans la colonne par défaut. Continuer ? projects.column.color=Couleur diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index abda334ff5b..6f348b1b713 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -215,6 +215,7 @@ more=Níos mó buttons.heading.tooltip=Cuir ceannteideal leis buttons.bold.tooltip=Cuir téacs trom leis buttons.italic.tooltip=Cuir téacs iodálach leis +buttons.strikethrough.tooltip=Cuir téacs trína chéile buttons.quote.tooltip=Téacs luaigh buttons.code.tooltip=Cuir cód leis buttons.link.tooltip=Cuir nasc leis @@ -1354,8 +1355,11 @@ editor.this_file_locked=Tá an comhad faoi ghlas editor.must_be_on_a_branch=Caithfidh tú a bheith ar bhrainse chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. editor.fork_before_edit=Ní mór duit an stór seo a fhorcáil chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. editor.delete_this_file=Scrios Comhad +editor.delete_this_directory=Scrios Eolaire editor.must_have_write_access=Caithfidh rochtain scríofa a bheith agat chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo. editor.file_delete_success=Tá an comhad "%s" scriosta. +editor.directory_delete_success=Scriosadh an eolaire "%s". +editor.delete_directory=Scrios an eolaire '%s' editor.name_your_file=Ainmnigh do chomhad… editor.filename_help=Cuir eolaire leis trína ainm a chlóscríobh ina dhiaidh sin le slash ('/'). Bain eolaire trí backspace a chlóscríobh ag tús an réimse ionchuir. editor.or=nó @@ -1482,6 +1486,7 @@ projects.column.new_submit=Cruthaigh Colún projects.column.new=Colún Nua projects.column.set_default=Socraigh Réamhshocrú projects.column.set_default_desc=Socraigh an colún seo mar réamhshocrú le haghaidh saincheisteanna agus tarraingtí gan chatagóir +projects.column.default_column_hint=Cuirfear saincheisteanna nua a chuirtear leis an tionscadal seo leis an gcolún seo projects.column.delete=Scrios Colún projects.column.deletion_desc=Ag scriosadh colún tionscadail aistríonn gach saincheist ghaolmhar chuig an gcolún. Lean ar aghaidh? projects.column.color=Dath @@ -3038,6 +3043,7 @@ dashboard.update_migration_poster_id=Nuashonraigh ID póstaer imir dashboard.git_gc_repos=Bailitheoir bruscair gach stórais dashboard.resync_all_sshkeys=Nuashonraigh an comhad '.ssh/authorized_keys' le heochracha SSH Gitea dashboard.resync_all_sshprincipals=Nuashonraigh an comhad '.ssh/authorized_principals' le príomhoidí SSH Gitea +dashboard.resync_all_hooks=Athshioncrónaigh crúcaí git na stórtha uile (réamhghlacadh, nuashonrú, iarghlacadh, próiseasghlacadh, ...) dashboard.reinit_missing_repos=Aththosaigh gach stórais Git atá in easnamh a bhfuil taifid ann dóibh dashboard.sync_external_users=Sioncrónaigh sonraí úsáideoirí seachtracha dashboard.cleanup_hook_task_table=Glan suas an tábla hook_task diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 32d7b51ae9c..2e28f5a13c1 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1354,8 +1354,11 @@ editor.this_file_locked=ファイルはロックされています editor.must_be_on_a_branch=このファイルを変更したり変更の提案をするには、ブランチ上にいる必要があります。 editor.fork_before_edit=このファイルを変更したり変更の提案をするには、リポジトリをフォークする必要があります。 editor.delete_this_file=ファイルを削除 +editor.delete_this_directory=ディレクトリを削除 editor.must_have_write_access=このファイルを変更したり変更の提案をするには、書き込み権限が必要です。 editor.file_delete_success=ファイル "%s" を削除しました。 +editor.directory_delete_success=ディレクトリ "%s" を削除しました。 +editor.delete_directory=ディレクトリ'%s'を削除 editor.name_your_file=ファイル名を指定… editor.filename_help=ディレクトリを追加するにはディレクトリ名に続けてスラッシュ('/')を入力します。 ディレクトリを削除するには入力欄の先頭でbackspaceキーを押します。 editor.or=または @@ -1482,6 +1485,7 @@ projects.column.new_submit=列を作成 projects.column.new=新しい列 projects.column.set_default=デフォルトに設定 projects.column.set_default_desc=この列を未分類のイシューやプルリクエストが入るデフォルトの列にします +projects.column.default_column_hint=このプロジェクトに追加された新しいイシューがこの列に追加されます projects.column.delete=列を削除 projects.column.deletion_desc=プロジェクト列を削除すると、関連するすべてのイシューがデフォルトの列に移動します。 続行しますか? projects.column.color=カラー @@ -3038,6 +3042,7 @@ dashboard.update_migration_poster_id=移行する投稿者IDの更新 dashboard.git_gc_repos=すべてのリポジトリでガベージコレクションを実行 dashboard.resync_all_sshkeys='.ssh/authorized_keys' ファイルをGitea上のSSHキーで更新 dashboard.resync_all_sshprincipals='.ssh/authorized_principals' ファイルをGitea上のSSHプリンシパルで更新 +dashboard.resync_all_hooks=すべてのリポジトリのGitフックを再同期する (pre-receive, update, post-receive, proc-receive, ...) dashboard.reinit_missing_repos=レコードが存在するが見当たらないすべてのGitリポジトリを再初期化する dashboard.sync_external_users=外部ユーザーデータの同期 dashboard.cleanup_hook_task_table=hook_taskテーブルのクリーンアップ diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 22e16de93f9..f0a5f2142a6 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -215,6 +215,7 @@ more=Mais buttons.heading.tooltip=Adicionar cabeçalho buttons.bold.tooltip=Adicionar texto em negrito buttons.italic.tooltip=Adicionar texto em itálico +buttons.strikethrough.tooltip=Adicionar texto rasurado buttons.quote.tooltip=Citar texto buttons.code.tooltip=Adicionar código-fonte buttons.link.tooltip=Adicionar uma ligação @@ -1354,8 +1355,11 @@ editor.this_file_locked=Ficheiro bloqueado editor.must_be_on_a_branch=Tem que estar num ramo para fazer ou propor modificações neste ficheiro. editor.fork_before_edit=Tem que fazer uma derivação deste repositório para fazer ou propor modificações neste ficheiro. editor.delete_this_file=Eliminar ficheiro +editor.delete_this_directory=Eliminar pasta editor.must_have_write_access=Tem que ter permissões de escrita para fazer ou propor modificações neste ficheiro. editor.file_delete_success=O ficheiro "%s" foi eliminado. +editor.directory_delete_success=A pasta "%s" foi eliminada. +editor.delete_directory=Eliminar a pasta '%s' editor.name_your_file=Nomeie o seu ficheiro… editor.filename_help=Adicione uma pasta escrevendo o nome dessa pasta seguido de uma barra('/'). Remova uma pasta carregando na tecla de apagar ('←') no início do campo. editor.or=ou diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 6270d353340..73e5b41f4e2 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -426,7 +426,7 @@ need_account=需要一个帐户? sign_up_tip=您正在系统中注册第一个帐户,它拥有管理员权限。请仔细记住您的用户名和密码。 如果您忘记了用户名或密码,请参阅 Gitea 文档以恢复账户。 sign_up_now=立即注册。 sign_up_successful=帐户创建成功。欢迎! -confirmation_mail_sent_prompt_ex=一封新的确认邮件已经发送到 %s。请在下一个 %s 中检查您的收件箱以完成注册流程。 如果您的注册邮箱地址不正确,您可以重新登录并更改它。 +confirmation_mail_sent_prompt_ex=一封新的确认邮件已经发送到 %s。请在 %s 内检查您的收件箱以完成注册流程。 如果您的注册邮箱地址不正确,您可以重新登录并更改它。 must_change_password=更新您的密码 allow_password_change=要求用户更改密码(推荐) reset_password_mail_sent_prompt=确认邮件已被发送到 %s。请您在 %s 内检查您的收件箱 ,完成密码重置流程。 @@ -1483,6 +1483,7 @@ projects.column.new_submit=创建列 projects.column.new=创建列 projects.column.set_default=设为默认 projects.column.set_default_desc=设置此列为未分类问题和合并请求的默认值 +projects.column.default_column_hint=添加到此项目的新议题将被添加到此列 projects.column.delete=删除列 projects.column.deletion_desc=删除项目列会将所有相关问题移至默认列。是否继续? projects.column.color=颜色 @@ -1970,6 +1971,9 @@ pulls.status_checks_requested=必须 pulls.status_checks_details=详情 pulls.status_checks_hide_all=隐藏所有检查 pulls.status_checks_show_all=显示所有检查 +pulls.status_checks_approve_all=批准所有工作流 +pulls.status_checks_need_approvals=%d 个工作流等待批准 +pulls.status_checks_need_approvals_helper=此工作流在仓库维护者批准后才会运行。 pulls.update_branch=通过合并更新分支 pulls.update_branch_rebase=通过变基更新分支 pulls.update_branch_success=分支更新成功 @@ -3036,6 +3040,7 @@ dashboard.update_migration_poster_id=更新迁移的发表者ID dashboard.git_gc_repos=对仓库进行垃圾回收 dashboard.resync_all_sshkeys=使用 Gitea 的 SSH 密钥更新「.ssh/authorized_keys」文件。 dashboard.resync_all_sshprincipals=使用 Gitea 的 SSH 规则更新「.ssh/authorized_principals」文件。 +dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子 dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录 dashboard.sync_external_users=同步外部用户数据 dashboard.cleanup_hook_task_table=清理 hook_task 表 @@ -3070,7 +3075,7 @@ dashboard.total_gc_time=GC 暂停时间总量 dashboard.total_gc_pause=GC 暂停时间总量 dashboard.last_gc_pause=上次 GC 暂停时间 dashboard.gc_times=GC 执行次数 -dashboard.delete_old_actions=从数据库中删除所有旧工作流记录 +dashboard.delete_old_actions=从数据库中删除所有旧操作记录 dashboard.delete_old_actions.started=已开始从数据库中删除所有旧工作流记录。 dashboard.update_checker=更新检查器 dashboard.delete_old_system_notices=从数据库中删除所有旧系统通知 @@ -3890,6 +3895,7 @@ workflow.has_workflow_dispatch=此工作流有一个 workflow_dispatch 事件触 workflow.has_no_workflow_dispatch=工作流「%s」没有 workflow_dispatch 事件触发器。 need_approval_desc=该工作流由派生仓库的合并请求所触发,需要批准方可运行。 +approve_all_success=已成功批准所有工作流运行。 variables=变量 variables.management=变量管理 @@ -3910,6 +3916,14 @@ variables.update.success=变量已编辑。 logs.always_auto_scroll=总是自动滚动日志 logs.always_expand_running=总是展开运行日志 +general=常规 +general.enable_actions=启用工作流 +general.collaborative_owners_management=协作所有者管理 +general.collaborative_owners_management_help=协作所有者是指其私有仓库有权访问此仓库的工作流的用户或组织。 +general.add_collaborative_owner=添加协作所有者 +general.collaborative_owner_not_exist=协作所有者不存在。 +general.remove_collaborative_owner=移除协作所有者 +general.remove_collaborative_owner_desc=移除协作所有者将阻止该所有者的其他仓库访问此仓库中的工作流。是否继续? [projects] deleted.display_name=已删除项目 diff --git a/package.json b/package.json index 2f81034952f..9f848ac3ce0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "type": "module", - "packageManager": "pnpm@10.22.0", + "packageManager": "pnpm@10.24.0", "engines": { "node": ">= 22.6.0", "pnpm": ">= 10.0.0" @@ -12,10 +12,10 @@ "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", "@github/paste-markdown": "1.5.3", - "@github/relative-time-element": "4.5.0", + "@github/relative-time-element": "4.5.1", "@github/text-expander-element": "2.9.2", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.21.0", + "@primer/octicons": "19.21.1", "@resvg/resvg-wasm": "2.6.2", "@silverwind/vue3-calendar-heatmap": "2.0.6", "@techknowlogick/license-checker-webpack-plugin": "0.3.0", @@ -36,9 +36,9 @@ "idiomorph": "0.7.4", "jquery": "3.7.1", "katex": "0.16.25", - "mermaid": "11.12.1", + "mermaid": "11.12.2", "mini-css-extract-plugin": "2.9.4", - "monaco-editor": "0.54.0", + "monaco-editor": "0.55.1", "monaco-editor-webpack-plugin": "7.1.1", "online-3d-viewer": "0.16.0", "pdfobject": "2.3.1", @@ -46,7 +46,7 @@ "postcss": "8.5.6", "postcss-loader": "8.2.0", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.30.2", + "swagger-ui-dist": "5.30.3", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -56,7 +56,7 @@ "typescript": "5.9.3", "uint8-to-base64": "0.2.1", "vanilla-colorful": "0.7.2", - "vue": "3.5.24", + "vue": "3.5.25", "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.3", "vue-loader": "17.4.2", @@ -66,7 +66,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", - "@playwright/test": "1.56.1", + "@playwright/test": "1.57.0", "@stylistic/eslint-plugin": "5.6.1", "@stylistic/stylelint-plugin": "4.0.0", "@types/codemirror": "5.60.17", @@ -79,40 +79,39 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.47.0", + "@typescript-eslint/parser": "8.48.1", "@vitejs/plugin-vue": "6.0.2", - "@vitest/eslint-plugin": "1.4.3", + "@vitest/eslint-plugin": "1.5.1", "eslint": "9.39.1", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.1.0", "eslint-plugin-github": "6.0.0", "eslint-plugin-import-x": "4.16.1", - "eslint-plugin-no-use-extend-native": "0.7.2", - "eslint-plugin-playwright": "2.3.0", + "eslint-plugin-playwright": "2.4.0", "eslint-plugin-regexp": "2.10.0", "eslint-plugin-sonarjs": "3.0.5", "eslint-plugin-unicorn": "62.0.0", - "eslint-plugin-vue": "10.5.1", + "eslint-plugin-vue": "10.6.2", "eslint-plugin-vue-scoped-css": "2.12.0", "eslint-plugin-wc": "3.0.2", "globals": "16.5.0", - "happy-dom": "20.0.10", + "happy-dom": "20.0.11", "markdownlint-cli": "0.46.0", - "material-icon-theme": "5.28.0", + "material-icon-theme": "5.29.0", "nolyfill": "1.0.44", "postcss-html": "1.8.0", "spectral-cli-bundle": "1.0.3", - "stylelint": "16.25.0", + "stylelint": "16.26.1", "stylelint-config-recommended": "17.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "4.0.0", - "typescript-eslint": "8.47.0", - "updates": "16.9.1", + "typescript-eslint": "8.48.1", + "updates": "17.0.4", "vite-string-plugin": "1.4.9", - "vitest": "4.0.10", - "vue-tsc": "3.1.4" + "vitest": "4.0.15", + "vue-tsc": "3.1.5" }, "browserslist": [ "defaults" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 843372d094a..d6c00cd0816 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: 1.5.3 version: 1.5.3 '@github/relative-time-element': - specifier: 4.5.0 - version: 4.5.0 + specifier: 4.5.1 + version: 4.5.1 '@github/text-expander-element': specifier: 2.9.2 version: 2.9.2 @@ -54,14 +54,14 @@ importers: specifier: 0.1.0-alpha-3 version: 0.1.0-alpha-3 '@primer/octicons': - specifier: 19.21.0 - version: 19.21.0 + specifier: 19.21.1 + version: 19.21.1 '@resvg/resvg-wasm': specifier: 2.6.2 version: 2.6.2 '@silverwind/vue3-calendar-heatmap': specifier: 2.0.6 - version: 2.0.6(tippy.js@6.3.7)(vue@3.5.24(typescript@5.9.3)) + version: 2.0.6(tippy.js@6.3.7)(vue@3.5.25(typescript@5.9.3)) '@techknowlogick/license-checker-webpack-plugin': specifier: 0.3.0 version: 0.3.0(webpack@5.103.0) @@ -117,17 +117,17 @@ importers: specifier: 0.16.25 version: 0.16.25 mermaid: - specifier: 11.12.1 - version: 11.12.1 + specifier: 11.12.2 + version: 11.12.2 mini-css-extract-plugin: specifier: 2.9.4 version: 2.9.4(webpack@5.103.0) monaco-editor: - specifier: 0.54.0 - version: 0.54.0 + specifier: 0.55.1 + version: 0.55.1 monaco-editor-webpack-plugin: specifier: 7.1.1 - version: 7.1.1(monaco-editor@0.54.0)(webpack@5.103.0) + version: 7.1.1(monaco-editor@0.55.1)(webpack@5.103.0) online-3d-viewer: specifier: 0.16.0 version: 0.16.0 @@ -147,8 +147,8 @@ importers: specifier: 1.15.6 version: 1.15.6 swagger-ui-dist: - specifier: 5.30.2 - version: 5.30.2 + specifier: 5.30.3 + version: 5.30.3 tailwindcss: specifier: 3.4.17 version: 3.4.17 @@ -177,17 +177,17 @@ importers: specifier: 0.7.2 version: 0.7.2 vue: - specifier: 3.5.24 - version: 3.5.24(typescript@5.9.3) + specifier: 3.5.25 + version: 3.5.25(typescript@5.9.3) vue-bar-graph: specifier: 2.2.0 version: 2.2.0(typescript@5.9.3) vue-chartjs: specifier: 5.3.3 - version: 5.3.3(chart.js@4.5.1)(vue@3.5.24(typescript@5.9.3)) + version: 5.3.3(chart.js@4.5.1)(vue@3.5.25(typescript@5.9.3)) vue-loader: specifier: 17.4.2 - version: 17.4.2(vue@3.5.24(typescript@5.9.3))(webpack@5.103.0) + version: 17.4.2(vue@3.5.25(typescript@5.9.3))(webpack@5.103.0) webpack: specifier: 5.103.0 version: 5.103.0(webpack-cli@6.0.1) @@ -202,14 +202,14 @@ importers: specifier: 4.5.0 version: 4.5.0(eslint@9.39.1(jiti@2.6.1)) '@playwright/test': - specifier: 1.56.1 - version: 1.56.1 + specifier: 1.57.0 + version: 1.57.0 '@stylistic/eslint-plugin': specifier: 5.6.1 version: 5.6.1(eslint@9.39.1(jiti@2.6.1)) '@stylistic/stylelint-plugin': specifier: 4.0.0 - version: 4.0.0(stylelint@16.25.0(typescript@5.9.3)) + version: 4.0.0(stylelint@16.26.1(typescript@5.9.3)) '@types/codemirror': specifier: 5.60.17 version: 5.60.17 @@ -241,20 +241,20 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.47.0 - version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.48.1 + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-vue': specifier: 6.0.2 - version: 6.0.2(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))(vue@3.5.24(typescript@5.9.3)) + version: 6.0.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) '@vitest/eslint-plugin': - specifier: 1.4.3 - version: 1.4.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1)) + specifier: 1.5.1 + version: 1.5.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.15(@types/node@24.10.1)(happy-dom@20.0.11)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2)) eslint: specifier: 9.39.1 version: 9.39.1(jiti@2.6.1) eslint-import-resolver-typescript: specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-array-func: specifier: 5.1.0 version: 5.1.0(eslint@9.39.1(jiti@2.6.1)) @@ -263,13 +263,10 @@ importers: version: 6.0.0(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-import-x: specifier: 4.16.1 - version: 4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-no-use-extend-native: - specifier: 0.7.2 - version: 0.7.2(eslint@9.39.1(jiti@2.6.1)) + version: 4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-playwright: - specifier: 2.3.0 - version: 2.3.0(eslint@9.39.1(jiti@2.6.1)) + specifier: 2.4.0 + version: 2.4.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-regexp: specifier: 2.10.0 version: 2.10.0(eslint@9.39.1(jiti@2.6.1)) @@ -280,8 +277,8 @@ importers: specifier: 62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-vue: - specifier: 10.5.1 - version: 10.5.1(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) + specifier: 10.6.2 + version: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) eslint-plugin-vue-scoped-css: specifier: 2.12.0 version: 2.12.0(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) @@ -292,14 +289,14 @@ importers: specifier: 16.5.0 version: 16.5.0 happy-dom: - specifier: 20.0.10 - version: 20.0.10 + specifier: 20.0.11 + version: 20.0.11 markdownlint-cli: specifier: 0.46.0 version: 0.46.0 material-icon-theme: - specifier: 5.28.0 - version: 5.28.0 + specifier: 5.29.0 + version: 5.29.0 nolyfill: specifier: 1.0.44 version: 1.0.44 @@ -310,38 +307,38 @@ importers: specifier: 1.0.3 version: 1.0.3 stylelint: - specifier: 16.25.0 - version: 16.25.0(typescript@5.9.3) + specifier: 16.26.1 + version: 16.26.1(typescript@5.9.3) stylelint-config-recommended: specifier: 17.0.0 - version: 17.0.0(stylelint@16.25.0(typescript@5.9.3)) + version: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) stylelint-declaration-block-no-ignored-properties: specifier: 2.8.0 - version: 2.8.0(stylelint@16.25.0(typescript@5.9.3)) + version: 2.8.0(stylelint@16.26.1(typescript@5.9.3)) stylelint-declaration-strict-value: specifier: 1.10.11 - version: 1.10.11(stylelint@16.25.0(typescript@5.9.3)) + version: 1.10.11(stylelint@16.26.1(typescript@5.9.3)) stylelint-value-no-unknown-custom-properties: specifier: 6.0.1 - version: 6.0.1(stylelint@16.25.0(typescript@5.9.3)) + version: 6.0.1(stylelint@16.26.1(typescript@5.9.3)) svgo: specifier: 4.0.0 version: 4.0.0 typescript-eslint: - specifier: 8.47.0 - version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.48.1 + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) updates: - specifier: 16.9.1 - version: 16.9.1 + specifier: 17.0.4 + version: 17.0.4 vite-string-plugin: specifier: 1.4.9 version: 1.4.9 vitest: - specifier: 4.0.10 - version: 4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@types/node@24.10.1)(happy-dom@20.0.11)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2) vue-tsc: - specifier: 3.1.4 - version: 3.1.4(typescript@5.9.3) + specifier: 3.1.5 + version: 3.1.5(typescript@5.9.3) packages: @@ -352,9 +349,6 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/utils@9.3.0': - resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -383,11 +377,11 @@ packages: '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} - '@cacheable/memory@2.0.5': - resolution: {integrity: sha512-fkiAxCvssEyJZ5fxX4tcdZFRmW9JehSTGvvqmXn6rTzG5cH6V/3C4ad8yb01vOjp2xBydHkHrgpW0qeGtzt6VQ==} + '@cacheable/memory@2.0.6': + resolution: {integrity: sha512-7e8SScMocHxcAb8YhtkbMhGG+EKLRIficb1F5sjvhSYsWTZGxvg4KIDp8kgxnV2PUJ3ddPe6J9QESjKvBWRDkg==} - '@cacheable/utils@2.3.1': - resolution: {integrity: sha512-38NJXjIr4W1Sghun8ju+uYWD8h2c61B4dKwfnQHVDFpAJ9oS28RpfqZQJ6Dgd3RceGkILDY9YT+72HJR3LoeSQ==} + '@cacheable/utils@2.3.2': + resolution: {integrity: sha512-8kGE2P+HjfY8FglaOiW+y8qxcaQAfAhVML+i66XJR3YX5FtyDqn6Txctr3K2FrbxLKixRRYYBWMbuGciOhYNDg==} '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -458,6 +452,10 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.20': + resolution: {integrity: sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==} + engines: {node: '>=18'} + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} @@ -688,8 +686,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.1': @@ -716,8 +714,8 @@ packages: '@github/paste-markdown@1.5.3': resolution: {integrity: sha512-PzZ1b3PaqBzYqbT4fwKEhiORf38h2OcGp2+JdXNNM7inZ7egaSmfmhyNkQILpqWfS0AYtRS3CDq6z03eZ8yOMQ==} - '@github/relative-time-element@4.5.0': - resolution: {integrity: sha512-zKC/tUHeDDdbODBuZh3CkT5pCy41M8mGuUplzhtBMuiEQ5+qY/l/iu0X1IBY/6QhNeP/xdQIVkLYKh2O5En4dg==} + '@github/relative-time-element@4.5.1': + resolution: {integrity: sha512-uxCxCwe9vdwUDmRmM84tN0UERlj8MosLV44+r/VDj7DZUVUSTP4vyWlE9mRK6vHelOmT8DS3RMlaMrLlg1h1PQ==} '@github/text-expander-element@2.9.2': resolution: {integrity: sha512-XY8EUMqM4GAloNxXNA1Py1ny+engWwYntbgsnpstQN4piaTI9rIlfYldyd0nnPXhxjGCVqHPmP6yg17Q0/n9Vg==} @@ -741,8 +739,8 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.0.2': - resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==} + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} @@ -871,16 +869,16 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.56.1': - resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==} + '@playwright/test@1.57.0': + resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} hasBin: true '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@primer/octicons@19.21.0': - resolution: {integrity: sha512-87buZ9aPlWbbHvTTzPAy9zqqGZpCc/VH+Q6q9OsZou6zCaExjmsINj6rWjP6FxNK5ZWHfF0UFNKQCai72lhaLA==} + '@primer/octicons@19.21.1': + resolution: {integrity: sha512-7tgtBkCNcg75YJnckinzvES+uxysYQCe+CHSEnzr3VYgxttzKRvfmrnVogl3aEuHCQP4xhiE9k2lFDhYwGtTzQ==} '@resvg/resvg-wasm@2.6.2': resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} @@ -1236,63 +1234,63 @@ packages: '@types/whatwg-mimetype@3.0.2': resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} - '@typescript-eslint/eslint-plugin@8.47.0': - resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} + '@typescript-eslint/eslint-plugin@8.48.1': + resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.47.0 + '@typescript-eslint/parser': ^8.48.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.47.0': - resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} + '@typescript-eslint/parser@8.48.1': + resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.47.0': - resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.47.0': - resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.47.0': - resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.47.0': - resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} + '@typescript-eslint/type-utils@8.48.1': + resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.47.0': - resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.47.0': - resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.47.0': - resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.47.0': - resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1397,8 +1395,8 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 vue: ^3.2.25 - '@vitest/eslint-plugin@1.4.3': - resolution: {integrity: sha512-ba+YDKyZdViNAOg8P86a9tIEawPA/O+nLbwhg8g7nkqsLOAVum6GoZhkNxgwX621oqWAbB8N2xb+G5kkpXehcA==} + '@vitest/eslint-plugin@1.5.1': + resolution: {integrity: sha512-t49CNERe/YadnLn90NTTKJLKzs99xBkXElcoUTLodG6j1G0Q7jy3mXqqiHd3N5aryG2KkgOg4UAoGwgwSrZqKQ==} engines: {node: '>=18'} peerDependencies: eslint: '>=8.57.0' @@ -1410,11 +1408,11 @@ packages: vitest: optional: true - '@vitest/expect@4.0.10': - resolution: {integrity: sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==} + '@vitest/expect@4.0.15': + resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} - '@vitest/mocker@4.0.10': - resolution: {integrity: sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==} + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -1424,20 +1422,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.10': - resolution: {integrity: sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==} + '@vitest/pretty-format@4.0.15': + resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} - '@vitest/runner@4.0.10': - resolution: {integrity: sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==} + '@vitest/runner@4.0.15': + resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} - '@vitest/snapshot@4.0.10': - resolution: {integrity: sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==} + '@vitest/snapshot@4.0.15': + resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} - '@vitest/spy@4.0.10': - resolution: {integrity: sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==} + '@vitest/spy@4.0.15': + resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} - '@vitest/utils@4.0.10': - resolution: {integrity: sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==} + '@vitest/utils@4.0.15': + resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} '@volar/language-core@2.4.23': resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} @@ -1448,42 +1446,42 @@ packages: '@volar/typescript@2.4.23': resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - '@vue/compiler-core@3.5.24': - resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} + '@vue/compiler-core@3.5.25': + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} - '@vue/compiler-dom@3.5.24': - resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} + '@vue/compiler-dom@3.5.25': + resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} - '@vue/compiler-sfc@3.5.24': - resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==} + '@vue/compiler-sfc@3.5.25': + resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==} - '@vue/compiler-ssr@3.5.24': - resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==} + '@vue/compiler-ssr@3.5.25': + resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==} - '@vue/language-core@3.1.4': - resolution: {integrity: sha512-n/58wm8SkmoxMWkUNUH/PwoovWe4hmdyPJU2ouldr3EPi1MLoS7iDN46je8CsP95SnVBs2axInzRglPNKvqMcg==} + '@vue/language-core@3.1.5': + resolution: {integrity: sha512-FMcqyzWN+sYBeqRMWPGT2QY0mUasZMVIuHvmb5NT3eeqPrbHBYtCP8JWEUCDCgM+Zr62uuWY/qoeBrPrzfa78w==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.5.24': - resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==} + '@vue/reactivity@3.5.25': + resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==} - '@vue/runtime-core@3.5.24': - resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==} + '@vue/runtime-core@3.5.25': + resolution: {integrity: sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==} - '@vue/runtime-dom@3.5.24': - resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==} + '@vue/runtime-dom@3.5.25': + resolution: {integrity: sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==} - '@vue/server-renderer@3.5.24': - resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==} + '@vue/server-renderer@3.5.25': + resolution: {integrity: sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==} peerDependencies: - vue: 3.5.24 + vue: 3.5.25 - '@vue/shared@3.5.24': - resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} + '@vue/shared@3.5.25': + resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -1605,8 +1603,8 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - alien-signals@3.1.0: - resolution: {integrity: sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg==} + alien-signals@3.1.1: + resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1688,8 +1686,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.29: - resolution: {integrity: sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==} + baseline-browser-mapping@2.9.2: + resolution: {integrity: sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw==} hasBin: true big.js@5.2.2: @@ -1712,8 +1710,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1735,8 +1733,8 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cacheable@2.2.0: - resolution: {integrity: sha512-LEJxRqfeomiiRd2t0uON6hxAtgOoWDfY3fugebbz+J3vDLO+SkdfFChQcOHTZhj9SYa9iwE9MGYNX72dKiOE4w==} + cacheable@2.3.0: + resolution: {integrity: sha512-HHiAvOBmlcR2f3SQ7kdlYD8+AUJG+wlFZ/Ze8tl1Vzvz0MdOh8IYA/EFU4ve8t1/sZ0j4MGi7ST5MoTwHessQA==} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1746,8 +1744,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001756: - resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} + caniuse-lite@1.0.30001759: + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} @@ -1797,8 +1795,8 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chroma-js@3.1.2: - resolution: {integrity: sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==} + chroma-js@3.2.0: + resolution: {integrity: sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==} chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} @@ -1878,9 +1876,6 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - core-js-compat@3.47.0: resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} @@ -2189,8 +2184,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.1.7: - resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} dompurify@3.3.0: resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} @@ -2204,8 +2199,8 @@ packages: easymde@2.20.0: resolution: {integrity: sha512-V1Z5f92TfR42Na852OWnIZMbM7zotWQYTddNaLYZFVKj7APBbyZ3FYJ27gBw2grMW3R6Qdv9J8n5Ij7XRSIgXQ==} - electron-to-chromium@1.5.256: - resolution: {integrity: sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==} + electron-to-chromium@1.5.265: + resolution: {integrity: sha512-B7IkLR1/AE+9jR2LtVF/1/6PFhY5TlnEHnlrKmGk7PvkJibg5jr+mLXLLzq3QYl6PA1T/vLDthQPqIPAlS/PPA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2232,8 +2227,8 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.20.0: - resolution: {integrity: sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==} + envinfo@7.21.0: + resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} engines: {node: '>=4'} hasBin: true @@ -2389,14 +2384,8 @@ packages: resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} - eslint-plugin-no-use-extend-native@0.7.2: - resolution: {integrity: sha512-hUBlwaTXIO1GzTwPT6pAjvYwmSHe4XduDhAiQvur4RUujmBUFjd8Nb2+e7WQdsQ+nGHWGRlogcUWXJRGqizTWw==} - engines: {node: '>=18.18.0'} - peerDependencies: - eslint: ^9.3.0 - - eslint-plugin-playwright@2.3.0: - resolution: {integrity: sha512-7UeUuIb5SZrNkrUGb2F+iwHM97kn33/huajcVtAaQFCSMUYGNFvjzRPil5C0OIppslPfuOV68M/zsisXx+/ZvQ==} + eslint-plugin-playwright@2.4.0: + resolution: {integrity: sha512-MWNXfXlLfwXAjj4Z80PvCCFCXgCYy5OCHan57Z/beGrjkJ3maG1GanuGX8Ck6T6fagplBx2ZdkifxSfByftaTQ==} engines: {node: '>=16.9.0'} peerDependencies: eslint: '>=8.40.0' @@ -2439,8 +2428,8 @@ packages: eslint: '>=5.0.0' vue-eslint-parser: '>=7.1.0' - eslint-plugin-vue@10.5.1: - resolution: {integrity: sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ==} + eslint-plugin-vue@10.6.2: + resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -2529,9 +2518,6 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2573,8 +2559,8 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - file-entry-cache@10.1.4: - resolution: {integrity: sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==} + file-entry-cache@11.1.1: + resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -2630,10 +2616,6 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} - get-set-props@0.2.0: - resolution: {integrity: sha512-YCmOj+4YAeEB5Dd9jfp6ETdejMet4zSxXjNkgaa4npBEKRI9uDOGB5MmAdAgi2OoFGAKshYhCbmLq2DS03CgVA==} - engines: {node: '>=18.0.0'} - get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -2664,10 +2646,6 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} @@ -2692,8 +2670,8 @@ packages: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} - happy-dom@20.0.10: - resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==} + happy-dom@20.0.11: + resolution: {integrity: sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -2703,8 +2681,8 @@ packages: hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hashery@1.2.0: - resolution: {integrity: sha512-43XJKpwle72Ik5Zpam7MuzRWyNdwwdf6XHlh8wCj2PggvWf+v/Dm5B0dxGZOmddidgeO6Ofu9As/o231Ti/9PA==} + hashery@1.3.0: + resolution: {integrity: sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==} engines: {node: '>=20'} hookified@1.13.0: @@ -2820,10 +2798,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-get-set-prop@2.0.0: - resolution: {integrity: sha512-C32bqXfHJfRwa0U5UIMqSGziZhALszXDJZ8n8mz8WZ6c6V7oYGHEWwJvftliBswypY3P3EQqdY5lpDSEKvTS1Q==} - engines: {node: '> 18.0.0'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2831,18 +2805,10 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-js-type@3.0.0: - resolution: {integrity: sha512-IbPf3g3vxm1D902xaBaYp2TUHiXZWwWRu5bM9hgKN9oAQcFaKALV6Gd13PGhXjKE5u2n8s1PhLhdke/E1fchxQ==} - engines: {node: '>=18.0.0'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj-prop@2.0.0: - resolution: {integrity: sha512-2/VFrbzXSZVJIscazpxoB+pOQx2jBOAAL9Gui4cRKxflznUNBpsr8IDvBA4UGol3e40sltLNiY3qnZv/7qSUxA==} - engines: {node: '>=18.0.0'} - is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -2854,10 +2820,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-proto-prop@3.0.1: - resolution: {integrity: sha512-S8xSxNMGJO4eZD86kO46zrq2gLIhA+rN9443lQEvt8Mz/l8cxk72p/AWFmofY6uL9g9ILD6cXW6j8QQj4F3Hcw==} - engines: {node: '>=18.0.0'} - is-valid-element-name@1.0.0: resolution: {integrity: sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==} @@ -2892,10 +2854,6 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-types@4.0.0: - resolution: {integrity: sha512-/c+n06zvqFQGxdz1BbElF7S3nEghjNchLN1TjQnk2j10HYDaUc57rcvl6BbnziTx8NQmrg0JOs/iwRpvcYaxjQ==} - engines: {node: '>=18.20'} - js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -2958,8 +2916,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.5.4: - resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} + keyv@5.5.5: + resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} @@ -2975,9 +2933,6 @@ packages: known-css-properties@0.37.0: resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - langium@3.3.1: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} @@ -3017,10 +2972,6 @@ packages: resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} engines: {node: '>=8.9.0'} - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3056,10 +3007,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lowercase-keys@3.0.0: - resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3091,8 +3038,8 @@ packages: engines: {node: '>= 12'} hasBin: true - material-icon-theme@5.28.0: - resolution: {integrity: sha512-71MxNSngYnNRUQ2YZpcVJg1KPsCFMlvdJV2LtSk434DjSkIjEeh0soHcJjaUp7ohbyYdpU4rukbvzfOylh+lxg==} + material-icon-theme@5.29.0: + resolution: {integrity: sha512-Kr6D+NgLCWYJjsTjGuIOoKUFG/uomUpLREhyV/9g4qWJMNfm7b1BYYMglRIdQg1IiY7WKqyTws8Ufsad6oFLUA==} engines: {vscode: ^1.55.0} mathml-tag-names@2.1.3: @@ -3118,8 +3065,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.12.1: - resolution: {integrity: sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3237,8 +3184,8 @@ packages: monaco-editor: '>= 0.31.0' webpack: ^4.5.0 || 5.x - monaco-editor@0.54.0: - resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==} + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -3304,10 +3251,6 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - obj-props@2.0.0: - resolution: {integrity: sha512-Q/uLAAfjdhrzQWN2czRNh3fDCgXjh7yRIkdHjDgIHTwpFP0BsshxTA3HRNffHR7Iw/XGTH30u8vdMXQ+079urA==} - engines: {node: '>=18.0.0'} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3316,6 +3259,9 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3346,8 +3292,8 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-manager-detector@1.5.0: - resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -3420,16 +3366,13 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - - playwright-core@1.56.1: - resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} hasBin: true - playwright@1.56.1: - resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} + playwright@1.57.0: + resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} engines: {node: '>=18'} hasBin: true @@ -3539,8 +3482,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-styl@0.12.3: @@ -3562,15 +3505,11 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true - prototype-properties@5.0.0: - resolution: {integrity: sha512-uCWE2QqnGlwvvJXTwiHTPTyHE62+zORO5hpFWhAwBGDtEtTmNZZleNLJDoFsqHCL4p/CeAP2Q1uMKFUKALuRGQ==} - engines: {node: '>=18.20'} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -3583,9 +3522,6 @@ packages: resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} engines: {node: '>=20'} - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3869,8 +3805,8 @@ packages: peerDependencies: stylelint: '>=16' - stylelint@16.25.0: - resolution: {integrity: sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==} + stylelint@16.26.1: + resolution: {integrity: sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==} engines: {node: '>=18.12.0'} hasBin: true @@ -3919,8 +3855,8 @@ packages: svgson@5.3.1: resolution: {integrity: sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA==} - swagger-ui-dist@5.30.2: - resolution: {integrity: sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==} + swagger-ui-dist@5.30.3: + resolution: {integrity: sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==} sync-fetch@0.4.5: resolution: {integrity: sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==} @@ -3984,9 +3920,6 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -4038,8 +3971,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.47.0: - resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} + typescript-eslint@8.48.1: + resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -4071,15 +4004,15 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - updates@16.9.1: - resolution: {integrity: sha512-Ct2VXiFLPG6ArPnL0Bd5KIOXjH/V26NYv84cgVZegkVBO96x58x8wG4KqxM2449YaHhgsWiM76jnTNz14pRalQ==} - engines: {node: '>=20'} + updates@17.0.4: + resolution: {integrity: sha512-lNhVFevBQR4QBqkSDFWNcl5a5IAARWaBZ8nZFECK0Ik7O0I4VAKtrFndXQ4ANVh4H1q9Sl4jLr6ps6u7imYsmw==} + engines: {node: '>=22'} hasBin: true uri-js@4.4.1: @@ -4098,8 +4031,8 @@ packages: vite-string-plugin@1.4.9: resolution: {integrity: sha512-mO7PVkMs8+FuTK9ZjBBCRSjabC9cobvUEbN2EjWtGJo6nu35SbW99bYesOh5Ho39ug/KSbT4VwM4GPC26Xk/mQ==} - vite@7.2.2: - resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4138,24 +4071,24 @@ packages: yaml: optional: true - vitest@4.0.10: - resolution: {integrity: sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 + '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.10 - '@vitest/browser-preview': 4.0.10 - '@vitest/browser-webdriverio': 4.0.10 - '@vitest/ui': 4.0.10 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true @@ -4222,14 +4155,14 @@ packages: vue: optional: true - vue-tsc@3.1.4: - resolution: {integrity: sha512-GsRJxttj4WkmXW/zDwYPGMJAN3np/4jTzoDFQTpTsI5Vg/JKMWamBwamlmLihgSVHO66y9P7GX+uoliYxeI4Hw==} + vue-tsc@3.1.5: + resolution: {integrity: sha512-L/G9IUjOWhBU0yun89rv8fKqmKC+T0HfhrFjlIml71WpfBv9eb4E9Bev8FMbyueBIU9vxQqbd+oOsVcDa5amGw==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.24: - resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==} + vue@3.5.25: + resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -4331,8 +4264,8 @@ packages: xml-reader@2.4.3: resolution: {integrity: sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -4346,11 +4279,9 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 1.5.0 + package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@antfu/utils@9.3.0': {} - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -4374,17 +4305,17 @@ snapshots: '@braintree/sanitize-url@7.1.1': {} - '@cacheable/memory@2.0.5': + '@cacheable/memory@2.0.6': dependencies: - '@cacheable/utils': 2.3.1 - '@keyv/bigmap': 1.3.0(keyv@5.5.4) + '@cacheable/utils': 2.3.2 + '@keyv/bigmap': 1.3.0(keyv@5.5.5) hookified: 1.13.0 - keyv: 5.5.4 + keyv: 5.5.5 - '@cacheable/utils@2.3.1': + '@cacheable/utils@2.3.2': dependencies: - hashery: 1.2.0 - keyv: 5.5.4 + hashery: 1.3.0 + keyv: 5.5.5 '@chevrotain/cst-dts-gen@11.0.3': dependencies: @@ -4465,6 +4396,8 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.20': {} + '@csstools/css-tokenizer@3.0.4': {} '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -4472,9 +4405,9 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@discoveryjs/json-ext@0.6.3': {} @@ -4611,7 +4544,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3 @@ -4642,7 +4575,7 @@ snapshots: '@github/paste-markdown@1.5.3': {} - '@github/relative-time-element@4.5.0': {} + '@github/relative-time-element@4.5.1': {} '@github/text-expander-element@2.9.2': dependencies: @@ -4662,18 +4595,11 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.0.2': + '@iconify/utils@3.1.0': dependencies: '@antfu/install-pkg': 1.1.0 - '@antfu/utils': 9.3.0 '@iconify/types': 2.0.0 - debug: 4.4.3 - globals: 15.15.0 - kolorist: 1.8.0 - local-pkg: 1.1.2 mlly: 1.8.0 - transitivePeerDependencies: - - supports-color '@isaacs/balanced-match@4.0.1': {} @@ -4700,11 +4626,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@keyv/bigmap@1.3.0(keyv@5.5.4)': + '@keyv/bigmap@1.3.0(keyv@5.5.5)': dependencies: - hashery: 1.2.0 + hashery: 1.3.0 hookified: 1.13.0 - keyv: 5.5.4 + keyv: 5.5.5 '@keyv/serialize@1.1.1': {} @@ -4793,13 +4719,13 @@ snapshots: '@pkgr/core@0.2.9': {} - '@playwright/test@1.56.1': + '@playwright/test@1.57.0': dependencies: - playwright: 1.56.1 + playwright: 1.57.0 '@popperjs/core@2.11.8': {} - '@primer/octicons@19.21.0': + '@primer/octicons@19.21.1': dependencies: object-assign: 4.1.1 @@ -4877,10 +4803,10 @@ snapshots: '@scarf/scarf@1.4.0': {} - '@silverwind/vue3-calendar-heatmap@2.0.6(tippy.js@6.3.7)(vue@3.5.24(typescript@5.9.3))': + '@silverwind/vue3-calendar-heatmap@2.0.6(tippy.js@6.3.7)(vue@3.5.25(typescript@5.9.3))': dependencies: tippy.js: 6.3.7 - vue: 3.5.24(typescript@5.9.3) + vue: 3.5.25(typescript@5.9.3) '@simonwep/pickr@1.9.0': dependencies: @@ -4905,23 +4831,23 @@ snapshots: '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/types': 8.48.1 eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.3 - '@stylistic/stylelint-plugin@4.0.0(stylelint@16.25.0(typescript@5.9.3))': + '@stylistic/stylelint-plugin@4.0.0(stylelint@16.26.1(typescript@5.9.3))': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 style-search: 0.1.0 - stylelint: 16.25.0(typescript@5.9.3) + stylelint: 16.26.1(typescript@5.9.3) '@swc/helpers@0.2.14': {} @@ -5142,14 +5068,14 @@ snapshots: '@types/whatwg-mimetype@3.0.2': {} - '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -5159,41 +5085,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.47.0': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 - '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -5201,38 +5127,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.47.0': {} + '@typescript-eslint/types@8.48.1': {} - '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.47.0': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -5294,60 +5219,60 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-vue@6.0.2(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))(vue@3.5.24(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.50 - vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) - vue: 3.5.24(typescript@5.9.3) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2) + vue: 3.5.25(typescript@5.9.3) - '@vitest/eslint-plugin@1.4.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))': + '@vitest/eslint-plugin@1.5.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.15(@types/node@24.10.1)(happy-dom@20.0.11)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2))': dependencies: - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) optionalDependencies: typescript: 5.9.3 - vitest: 4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) + vitest: 4.0.15(@types/node@24.10.1)(happy-dom@20.0.11)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.10': + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.10 - '@vitest/utils': 4.0.10 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.10(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.10 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2) - '@vitest/pretty-format@4.0.10': + '@vitest/pretty-format@4.0.15': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.10': + '@vitest/runner@4.0.15': dependencies: - '@vitest/utils': 4.0.10 + '@vitest/utils': 4.0.15 pathe: 2.0.3 - '@vitest/snapshot@4.0.10': + '@vitest/snapshot@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.10 + '@vitest/pretty-format': 4.0.15 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.10': {} + '@vitest/spy@4.0.15': {} - '@vitest/utils@4.0.10': + '@vitest/utils@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.10 + '@vitest/pretty-format': 4.0.15 tinyrainbow: 3.0.3 '@volar/language-core@2.4.23': @@ -5362,71 +5287,71 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.24': + '@vue/compiler-core@3.5.25': dependencies: '@babel/parser': 7.28.5 - '@vue/shared': 3.5.24 + '@vue/shared': 3.5.25 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.24': + '@vue/compiler-dom@3.5.25': dependencies: - '@vue/compiler-core': 3.5.24 - '@vue/shared': 3.5.24 + '@vue/compiler-core': 3.5.25 + '@vue/shared': 3.5.25 - '@vue/compiler-sfc@3.5.24': + '@vue/compiler-sfc@3.5.25': dependencies: '@babel/parser': 7.28.5 - '@vue/compiler-core': 3.5.24 - '@vue/compiler-dom': 3.5.24 - '@vue/compiler-ssr': 3.5.24 - '@vue/shared': 3.5.24 + '@vue/compiler-core': 3.5.25 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.6 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.24': + '@vue/compiler-ssr@3.5.25': dependencies: - '@vue/compiler-dom': 3.5.24 - '@vue/shared': 3.5.24 + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 - '@vue/language-core@3.1.4(typescript@5.9.3)': + '@vue/language-core@3.1.5(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.24 - '@vue/shared': 3.5.24 - alien-signals: 3.1.0 + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + alien-signals: 3.1.1 muggle-string: 0.4.1 path-browserify: 1.0.1 picomatch: 4.0.3 optionalDependencies: typescript: 5.9.3 - '@vue/reactivity@3.5.24': + '@vue/reactivity@3.5.25': dependencies: - '@vue/shared': 3.5.24 + '@vue/shared': 3.5.25 - '@vue/runtime-core@3.5.24': + '@vue/runtime-core@3.5.25': dependencies: - '@vue/reactivity': 3.5.24 - '@vue/shared': 3.5.24 + '@vue/reactivity': 3.5.25 + '@vue/shared': 3.5.25 - '@vue/runtime-dom@3.5.24': + '@vue/runtime-dom@3.5.25': dependencies: - '@vue/reactivity': 3.5.24 - '@vue/runtime-core': 3.5.24 - '@vue/shared': 3.5.24 + '@vue/reactivity': 3.5.25 + '@vue/runtime-core': 3.5.25 + '@vue/shared': 3.5.25 csstype: 3.2.3 - '@vue/server-renderer@3.5.24(vue@3.5.24(typescript@5.9.3))': + '@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.9.3))': dependencies: - '@vue/compiler-ssr': 3.5.24 - '@vue/shared': 3.5.24 - vue: 3.5.24(typescript@5.9.3) + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + vue: 3.5.25(typescript@5.9.3) - '@vue/shared@3.5.24': {} + '@vue/shared@3.5.25': {} '@webassemblyjs/ast@1.14.1': dependencies: @@ -5560,7 +5485,7 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alien-signals@3.1.0: {} + alien-signals@3.1.1: {} ansi-regex@5.0.1: {} @@ -5615,7 +5540,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.29: {} + baseline-browser-mapping@2.9.2: {} big.js@5.2.2: {} @@ -5636,13 +5561,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.0: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.8.29 - caniuse-lite: 1.0.30001756 - electron-to-chromium: 1.5.256 + baseline-browser-mapping: 2.9.2 + caniuse-lite: 1.0.30001759 + electron-to-chromium: 1.5.265 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.28.0) + update-browserslist-db: 1.2.2(browserslist@4.28.1) buffer-from@1.1.2: {} @@ -5657,19 +5582,19 @@ snapshots: bytes@3.1.2: {} - cacheable@2.2.0: + cacheable@2.3.0: dependencies: - '@cacheable/memory': 2.0.5 - '@cacheable/utils': 2.3.1 + '@cacheable/memory': 2.0.6 + '@cacheable/utils': 2.3.2 hookified: 1.13.0 - keyv: 5.5.4 + keyv: 5.5.5 qified: 0.5.2 callsites@3.1.0: {} camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001756: {} + caniuse-lite@1.0.30001759: {} chai@6.2.1: {} @@ -5727,7 +5652,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chroma-js@3.1.2: {} + chroma-js@3.2.0: {} chrome-trace-event@1.0.4: {} @@ -5783,11 +5708,9 @@ snapshots: confbox@0.1.8: {} - confbox@0.2.2: {} - core-js-compat@3.47.0: dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 core-js@3.32.2: {} @@ -6112,7 +6035,9 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.1.7: {} + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 dompurify@3.3.0: optionalDependencies: @@ -6137,7 +6062,7 @@ snapshots: codemirror-spell-checker: 1.1.2 marked: 4.3.0 - electron-to-chromium@1.5.256: {} + electron-to-chromium@1.5.265: {} emoji-regex@10.6.0: {} @@ -6156,7 +6081,7 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.20.0: {} + envinfo@7.21.0: {} error-ex@1.3.4: dependencies: @@ -6231,7 +6156,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) @@ -6242,19 +6167,19 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -6264,7 +6189,7 @@ snapshots: eslint-plugin-escompat@3.11.4(eslint@9.39.1(jiti@2.6.1)): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 eslint: 9.39.1(jiti@2.6.1) eslint-plugin-eslint-comments@3.2.0(eslint@9.39.1(jiti@2.6.1)): @@ -6284,11 +6209,11 @@ snapshots: eslint-plugin-github@6.0.0(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@eslint/compat': 1.4.1(eslint@9.39.1(jiti@2.6.1)) - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@github/browserslist-config': 1.0.0 - '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) aria-query: 5.3.2 eslint: 9.39.1(jiti@2.6.1) eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1)) @@ -6296,17 +6221,17 @@ snapshots: eslint-plugin-eslint-comments: 3.2.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-filenames: 1.3.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-i18n-text: 1.0.1(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2) + eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4) eslint-rule-documentation: 1.0.23 globals: 16.5.0 jsx-ast-utils: 3.3.5 - prettier: 3.6.2 + prettier: 3.7.4 svg-element-attributes: 1.3.1 typescript: 5.9.3 - typescript-eslint: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - '@types/eslint' - eslint-import-resolver-typescript @@ -6317,9 +6242,9 @@ snapshots: dependencies: eslint: 9.39.1(jiti@2.6.1) - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/types': 8.48.1 comment-parser: 1.4.1 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) @@ -6330,12 +6255,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: '@nolyfill/array-includes@1.0.44' @@ -6346,7 +6271,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) hasown: '@nolyfill/hasown@1.0.44' is-core-module: '@nolyfill/is-core-module@1.0.39' is-glob: 4.0.3 @@ -6358,7 +6283,7 @@ snapshots: string.prototype.trimend: '@nolyfill/string.prototype.trimend@1.0.44' tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6385,23 +6310,15 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-no-use-extend-native@0.7.2(eslint@9.39.1(jiti@2.6.1)): - dependencies: - eslint: 9.39.1(jiti@2.6.1) - is-get-set-prop: 2.0.0 - is-js-type: 3.0.0 - is-obj-prop: 2.0.0 - is-proto-prop: 3.0.1 - - eslint-plugin-playwright@2.3.0(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-playwright@2.4.0(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) globals: 16.5.0 - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2): + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4): dependencies: eslint: 9.39.1(jiti@2.6.1) - prettier: 3.6.2 + prettier: 3.7.4 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: @@ -6464,25 +6381,25 @@ snapshots: postcss: 8.5.6 postcss-safe-parser: 6.0.0(postcss@8.5.6) postcss-scss: 4.0.9(postcss@8.5.6) - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-styl: 0.12.3 vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-vue@10.5.1(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))): + eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) eslint: 9.39.1(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.1.1 semver: 7.7.3 vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-wc@3.0.2(eslint@9.39.1(jiti@2.6.1)): dependencies: @@ -6513,7 +6430,7 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -6579,8 +6496,6 @@ snapshots: expect-type@1.2.2: {} - exsolve@1.0.8: {} - fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6617,7 +6532,7 @@ snapshots: fflate@0.8.2: {} - file-entry-cache@10.1.4: + file-entry-cache@11.1.1: dependencies: flat-cache: 6.1.19 @@ -6648,7 +6563,7 @@ snapshots: flat-cache@6.1.19: dependencies: - cacheable: 2.2.0 + cacheable: 2.3.0 flatted: 3.3.3 hookified: 1.13.0 @@ -6668,8 +6583,6 @@ snapshots: get-east-asian-width@1.4.0: {} - get-set-props@0.2.0: {} - get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -6705,8 +6618,6 @@ snapshots: globals@14.0.0: {} - globals@15.15.0: {} - globals@16.5.0: {} globby@11.1.0: @@ -6728,7 +6639,7 @@ snapshots: hammerjs@2.0.8: {} - happy-dom@20.0.10: + happy-dom@20.0.11: dependencies: '@types/node': 20.19.25 '@types/whatwg-mimetype': 3.0.2 @@ -6738,7 +6649,7 @@ snapshots: hash-sum@2.0.0: {} - hashery@1.2.0: + hashery@1.3.0: dependencies: hookified: 1.13.0 @@ -6831,28 +6742,14 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-get-set-prop@2.0.0: - dependencies: - get-set-props: 0.2.0 - lowercase-keys: 3.0.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-hexadecimal@2.0.1: {} - is-js-type@3.0.0: - dependencies: - js-types: 4.0.0 - is-number@7.0.0: {} - is-obj-prop@2.0.0: - dependencies: - lowercase-keys: 3.0.0 - obj-props: 2.0.0 - is-plain-object@2.0.4: dependencies: isobject: 3.0.1 @@ -6861,11 +6758,6 @@ snapshots: is-potential-custom-element-name@1.0.1: {} - is-proto-prop@3.0.1: - dependencies: - lowercase-keys: 3.0.0 - prototype-properties: 5.0.0 - is-valid-element-name@1.0.0: dependencies: is-potential-custom-element-name: 1.0.1 @@ -6892,8 +6784,6 @@ snapshots: js-tokens@9.0.1: {} - js-types@4.0.0: {} - js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -6941,7 +6831,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.5.4: + keyv@5.5.5: dependencies: '@keyv/serialize': 1.1.1 @@ -6955,8 +6845,6 @@ snapshots: known-css-properties@0.37.0: {} - kolorist@1.8.0: {} - langium@3.3.1: dependencies: chevrotain: 11.0.3 @@ -6996,12 +6884,6 @@ snapshots: emojis-list: 3.0.0 json5: 2.2.3 - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 - quansync: 0.2.11 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -7028,8 +6910,6 @@ snapshots: lodash@4.17.21: {} - lowercase-keys@3.0.0: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7079,9 +6959,9 @@ snapshots: marked@4.3.0: {} - material-icon-theme@5.28.0: + material-icon-theme@5.29.0: dependencies: - chroma-js: 3.1.2 + chroma-js: 3.2.0 events: 3.3.0 fast-deep-equal: 3.1.3 svgson: 5.3.1 @@ -7100,10 +6980,10 @@ snapshots: merge2@1.4.1: {} - mermaid@11.12.1: + mermaid@11.12.2: dependencies: '@braintree/sanitize-url': 7.1.1 - '@iconify/utils': 3.0.2 + '@iconify/utils': 3.1.0 '@mermaid-js/parser': 0.6.3 '@types/d3': 7.4.3 cytoscape: 3.33.1 @@ -7122,8 +7002,6 @@ snapshots: stylis: 4.3.6 ts-dedent: 2.2.0 uuid: 11.1.0 - transitivePeerDependencies: - - supports-color micromark-core-commonmark@2.0.3: dependencies: @@ -7335,15 +7213,15 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 - monaco-editor-webpack-plugin@7.1.1(monaco-editor@0.54.0)(webpack@5.103.0): + monaco-editor-webpack-plugin@7.1.1(monaco-editor@0.55.1)(webpack@5.103.0): dependencies: loader-utils: 2.0.4 - monaco-editor: 0.54.0 + monaco-editor: 0.55.1 webpack: 5.103.0(webpack-cli@6.0.1) - monaco-editor@0.54.0: + monaco-editor@0.55.1: dependencies: - dompurify: 3.1.7 + dompurify: 3.2.7 marked: 14.0.0 moo@0.5.2: {} @@ -7386,12 +7264,12 @@ snapshots: dependencies: boolbase: 1.0.0 - obj-props@2.0.0: {} - object-assign@4.1.1: {} object-hash@3.0.0: {} + obug@2.1.1: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -7429,7 +7307,7 @@ snapshots: p-try@2.2.0: {} - package-manager-detector@1.5.0: {} + package-manager-detector@1.6.0: {} parent-module@1.0.1: dependencies: @@ -7492,17 +7370,11 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 - pkg-types@2.3.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.8 - pathe: 2.0.3 + playwright-core@1.57.0: {} - playwright-core@1.56.1: {} - - playwright@1.56.1: + playwright@1.57.0: dependencies: - playwright-core: 1.56.1 + playwright-core: 1.57.0 optionalDependencies: fsevents: 2.3.2 @@ -7537,7 +7409,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.5.6): dependencies: lilconfig: 3.1.3 - yaml: 2.8.1 + yaml: 2.8.2 optionalDependencies: postcss: 8.5.6 @@ -7560,13 +7432,13 @@ snapshots: dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: @@ -7597,7 +7469,7 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.0: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -7626,9 +7498,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.6.2: {} - - prototype-properties@5.0.0: {} + prettier@3.7.4: {} punycode.js@2.3.1: {} @@ -7638,8 +7508,6 @@ snapshots: dependencies: hookified: 1.13.0 - quansync@0.2.11: {} - queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -7899,30 +7767,31 @@ snapshots: style-search@0.1.0: {} - stylelint-config-recommended@17.0.0(stylelint@16.25.0(typescript@5.9.3)): + stylelint-config-recommended@17.0.0(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.25.0(typescript@5.9.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-declaration-block-no-ignored-properties@2.8.0(stylelint@16.25.0(typescript@5.9.3)): + stylelint-declaration-block-no-ignored-properties@2.8.0(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.25.0(typescript@5.9.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-declaration-strict-value@1.10.11(stylelint@16.25.0(typescript@5.9.3)): + stylelint-declaration-strict-value@1.10.11(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.25.0(typescript@5.9.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-value-no-unknown-custom-properties@6.0.1(stylelint@16.25.0(typescript@5.9.3)): + stylelint-value-no-unknown-custom-properties@6.0.1(stylelint@16.26.1(typescript@5.9.3)): dependencies: postcss-value-parser: 4.2.0 resolve: 1.22.11 - stylelint: 16.25.0(typescript@5.9.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint@16.25.0(typescript@5.9.3): + stylelint@16.26.1(typescript@5.9.3): dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-syntax-patches-for-csstree': 1.0.20 '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 colord: 2.9.3 @@ -7932,7 +7801,7 @@ snapshots: debug: 4.4.3 fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 10.1.4 + file-entry-cache: 11.1.1 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 @@ -7949,7 +7818,7 @@ snapshots: postcss: 8.5.6 postcss-resolve-nested-selector: 0.1.6 postcss-safe-parser: 7.0.1(postcss@8.5.6) - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 @@ -8020,7 +7889,7 @@ snapshots: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swagger-ui-dist@5.30.2: + swagger-ui-dist@5.30.3: dependencies: '@scarf/scarf': 1.4.0 @@ -8104,8 +7973,6 @@ snapshots: tinycolor2@1.6.0: {} - tinyexec@0.3.2: {} - tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8151,12 +8018,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -8200,13 +8067,13 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.4(browserslist@4.28.0): + update-browserslist-db@1.2.2(browserslist@4.28.1): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 - updates@16.9.1: {} + updates@17.0.4: {} uri-js@4.4.1: dependencies: @@ -8220,7 +8087,7 @@ snapshots: vite-string-plugin@1.4.9: {} - vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1): + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -8234,34 +8101,33 @@ snapshots: jiti: 2.6.1 stylus: 0.57.0 terser: 5.44.1 - yaml: 2.8.1 + yaml: 2.8.2 - vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1): + vitest@4.0.15(@types/node@24.10.1)(happy-dom@20.0.11)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.10 - '@vitest/mocker': 4.0.10(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.10 - '@vitest/runner': 4.0.10 - '@vitest/snapshot': 4.0.10 - '@vitest/spy': 4.0.10 - '@vitest/utils': 4.0.10 - debug: 4.4.3 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 '@types/node': 24.10.1 - happy-dom: 20.0.10 + happy-dom: 20.0.11 transitivePeerDependencies: - jiti - less @@ -8271,7 +8137,6 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml @@ -8297,14 +8162,14 @@ snapshots: vue-bar-graph@2.2.0(typescript@5.9.3): dependencies: - vue: 3.5.24(typescript@5.9.3) + vue: 3.5.25(typescript@5.9.3) transitivePeerDependencies: - typescript - vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.24(typescript@5.9.3)): + vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.25(typescript@5.9.3)): dependencies: chart.js: 4.5.1 - vue: 3.5.24(typescript@5.9.3) + vue: 3.5.25(typescript@5.9.3) vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1)): dependencies: @@ -8318,28 +8183,28 @@ snapshots: transitivePeerDependencies: - supports-color - vue-loader@17.4.2(vue@3.5.24(typescript@5.9.3))(webpack@5.103.0): + vue-loader@17.4.2(vue@3.5.25(typescript@5.9.3))(webpack@5.103.0): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 watchpack: 2.4.4 webpack: 5.103.0(webpack-cli@6.0.1) optionalDependencies: - vue: 3.5.24(typescript@5.9.3) + vue: 3.5.25(typescript@5.9.3) - vue-tsc@3.1.4(typescript@5.9.3): + vue-tsc@3.1.5(typescript@5.9.3): dependencies: '@volar/typescript': 2.4.23 - '@vue/language-core': 3.1.4(typescript@5.9.3) + '@vue/language-core': 3.1.5(typescript@5.9.3) typescript: 5.9.3 - vue@3.5.24(typescript@5.9.3): + vue@3.5.25(typescript@5.9.3): dependencies: - '@vue/compiler-dom': 3.5.24 - '@vue/compiler-sfc': 3.5.24 - '@vue/runtime-dom': 3.5.24 - '@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@5.9.3)) - '@vue/shared': 3.5.24 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-sfc': 3.5.25 + '@vue/runtime-dom': 3.5.25 + '@vue/server-renderer': 3.5.25(vue@3.5.25(typescript@5.9.3)) + '@vue/shared': 3.5.25 optionalDependencies: typescript: 5.9.3 @@ -8359,7 +8224,7 @@ snapshots: colorette: 2.0.20 commander: 12.1.0 cross-spawn: 7.0.6 - envinfo: 7.20.0 + envinfo: 7.21.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 @@ -8390,7 +8255,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.28.0 + browserslist: 4.28.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -8468,6 +8333,6 @@ snapshots: eventemitter3: 2.0.3 xml-lexer: 0.2.2 - yaml@2.8.1: {} + yaml@2.8.2: {} yocto-queue@0.1.0: {} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index abc4e52877e..6f1e2eb120d 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -216,9 +216,12 @@ func EditUser(ctx *context.APIContext) { } if form.Email != nil { - if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil { + if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil { switch { case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err): + if !user_model.IsEmailDomainAllowed(*form.Email) { + err = fmt.Errorf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email) + } ctx.APIError(http.StatusBadRequest, err) case user_model.IsErrEmailAlreadyUsed(err): ctx.APIError(http.StatusBadRequest, err) @@ -227,10 +230,6 @@ func EditUser(ctx *context.APIContext) { } return } - - if !user_model.IsEmailDomainAllowed(*form.Email) { - ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email)) - } } opts := &user_service.UpdateOptions{ diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8e076857598..9bce98ac025 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1242,6 +1242,7 @@ func Routes() *web.Router { m.Get("/*", repo.GetBranch) m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch) m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch) + m.Put("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch) m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch) }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode)) m.Group("/branch_protections", func() { diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index b9060e9cbd0..4624d7e738c 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -380,6 +380,81 @@ func ListBranches(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiBranches) } +// UpdateBranch moves a branch reference to a new commit. +func UpdateBranch(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch + // --- + // summary: Update a branch reference to a new commit + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: branch + // in: path + // description: name of the branch + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateBranchRepoOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/conflict" + // "422": + // "$ref": "#/responses/validationError" + + opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption) + + branchName := ctx.PathParam("*") + repo := ctx.Repo.Repository + + if repo.IsEmpty { + ctx.APIError(http.StatusNotFound, "Git Repository is empty.") + return + } + + if repo.IsMirror { + ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.") + return + } + + // permission check has been done in api.go + if err := repo_service.UpdateBranch(ctx, repo, ctx.Repo.GitRepo, ctx.Doer, branchName, opt.NewCommitID, opt.OldCommitID, opt.Force); err != nil { + switch { + case git_model.IsErrBranchNotExist(err): + ctx.APIErrorNotFound(err) + case errors.Is(err, util.ErrInvalidArgument): + ctx.APIError(http.StatusUnprocessableEntity, err) + case git.IsErrPushRejected(err): + rej := err.(*git.ErrPushRejected) + ctx.APIError(http.StatusForbidden, rej.Message) + default: + ctx.APIErrorInternal(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + // RenameBranch renames a repository's branch. func RenameBranch(ctx *context.APIContext) { // swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 6a93be624f6..2a7efa0ea6f 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -13,6 +13,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" @@ -222,8 +223,7 @@ func GetAllCommits(ctx *context.APIContext) { } // Total commit count - commitsCountTotal, err = git.CommitsCount(ctx.Repo.GitRepo.Ctx, git.CommitsCountOptions{ - RepoPath: ctx.Repo.GitRepo.Path, + commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository, gitrepo.CommitsCountOptions{ Not: not, Revision: []string{baseCommit.ID.String()}, Since: since, @@ -245,9 +245,8 @@ func GetAllCommits(ctx *context.APIContext) { sha = ctx.Repo.Repository.DefaultBranch } - commitsCountTotal, err = git.CommitsCount(ctx, - git.CommitsCountOptions{ - RepoPath: ctx.Repo.GitRepo.Path, + commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository, + gitrepo.CommitsCountOptions{ Not: not, Revision: []string{sha}, RelPath: []string{path}, diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index ec34d54d224..27a0827a10f 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -610,10 +610,6 @@ func handleChangeRepoFilesError(ctx *context.APIContext, err error) { ctx.APIError(http.StatusUnprocessableEntity, err) return } - if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { - ctx.APIError(http.StatusNotFound, err) - return - } if errors.Is(err, util.ErrNotExist) { ctx.APIError(http.StatusNotFound, err) return diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 68cc5d65a97..073c7842424 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1165,7 +1165,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess) headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess) - log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef) + log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), baseRefToGuess, baseRef, headRefToGuess, headRef) baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName()) headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName()) diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index 8e24ffa465c..baf5e0189fe 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi } // get commit count - wiki revisions - commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) + commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename) // Get last change information. lastCommit, err := wikiRepo.GetCommitByPath(pageFilename) @@ -429,7 +429,7 @@ func ListPageRevisions(ctx *context.APIContext) { } // get commit count - wiki revisions - commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) + commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename) page := max(ctx.FormInt("page"), 1) diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index b80a9c14ba0..310839374b6 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -147,6 +147,8 @@ type swaggerParameterBodies struct { // in:body CreateBranchRepoOption api.CreateBranchRepoOption + // in:body + UpdateBranchRepoOption api.UpdateBranchRepoOption // in:body CreateBranchProtectionOption api.CreateBranchProtectionOption diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 07adee18cec..6bf430d361d 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -5,6 +5,7 @@ package common import ( "fmt" + "log" "net/http" "strings" @@ -71,8 +72,13 @@ func RequestContextHandler() func(h http.Handler) http.Handler { req = req.WithContext(cache.WithCacheContext(ctx)) ds.SetContextValue(httplib.RequestContextKey, req) ds.AddCleanUp(func() { - if req.MultipartForm != nil { - _ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory + // TODO: GOLANG-HTTP-TMPDIR: Golang saves the uploaded files to temp directory (TMPDIR) when parsing multipart-form. + // The "req" might have changed due to the new "req.WithContext" calls + // For example: in NewBaseContext, a new "req" with context is created, and the multipart-form is parsed there. + // So we always use the latest "req" from the data store. + ctxReq := ds.GetContextValue(httplib.RequestContextKey).(*http.Request) + if ctxReq.MultipartForm != nil { + _ = ctxReq.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory } }) next.ServeHTTP(respWriter, req) @@ -107,7 +113,11 @@ func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Han return proxy.ForwardedHeaders(opt) } -func Sessioner() (func(next http.Handler) http.Handler, error) { +func MustInitSessioner() func(next http.Handler) http.Handler { + // TODO: CHI-SESSION-GOB-REGISTER: chi-session has a design problem: it calls gob.Register for "Set" + // But if the server restarts, then the first "Get" will fail to decode the previously stored session data because the structs are not registered yet. + // So each package should make sure their structs are registered correctly during startup for session storage. + middleware, err := session.Sessioner(session.Options{ Provider: setting.SessionConfig.Provider, ProviderConfig: setting.SessionConfig.ProviderConfig, @@ -120,8 +130,7 @@ func Sessioner() (func(next http.Handler) http.Handler, error) { Domain: setting.SessionConfig.Domain, }) if err != nil { - return nil, fmt.Errorf("failed to create session middleware: %w", err) + log.Fatalf("common.Sessioner failed: %v", err) } - - return middleware, nil + return middleware } diff --git a/routers/install/install.go b/routers/install/install.go index 4a9dabac6fe..c5acf968bdd 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -55,8 +55,8 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) { return dbTypeNames } -// Contexter prepare for rendering installation page -func Contexter() func(next http.Handler) http.Handler { +// installContexter prepare for rendering installation page +func installContexter() func(next http.Handler) http.Handler { rnd := templates.HTMLRenderer() dbTypeNames := getSupportedDbTypeNames() envConfigKeys := setting.CollectEnvConfigKeys() diff --git a/routers/install/routes.go b/routers/install/routes.go index d47c1f61eeb..0914c921c0c 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -8,7 +8,6 @@ import ( "html" "net/http" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -25,11 +24,8 @@ func Routes() *web.Router { base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc()) r := web.NewRouter() - if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil { - r.Use(sessionMid, Contexter()) - } else { - log.Fatal("common.Sessioner failed: %v", err) - } + r.Use(common.MustInitSessioner(), installContexter()) + r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall) r.Get("/post-install", InstallDone) diff --git a/routers/private/serv.go b/routers/private/serv.go index 3dfe4d21da7..b752556c238 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -108,21 +108,19 @@ func ServCommand(ctx *context.PrivateContext) { results.RepoName = repoName[:len(repoName)-5] } - // Check if there is a user redirect for the requested owner - redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName) - if err == nil { - owner, err := user_model.GetUserByID(ctx, redirectedUserID) - if err == nil { - log.Info("User %s has been redirected to %s", results.OwnerName, owner.Name) - results.OwnerName = owner.Name - } else { - log.Warn("User %s has a redirect to user with ID %d, but no user with this ID could be found. Trying without redirect...", results.OwnerName, redirectedUserID) - } - } - owner, err := user_model.GetUserByName(ctx, results.OwnerName) if err != nil { - if user_model.IsErrUserNotExist(err) { + if !user_model.IsErrUserNotExist(err) { + log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err) + ctx.JSON(http.StatusForbidden, private.Response{ + UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err), + }) + return + } + + // Check if there is a user redirect for the requested owner + redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName) + if err != nil { // User is fetching/cloning a non-existent repository log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr()) ctx.JSON(http.StatusNotFound, private.Response{ @@ -130,11 +128,20 @@ func ServCommand(ctx *context.PrivateContext) { }) return } - log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err) - ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err), - }) - return + + redirectUser, err := user_model.GetUserByID(ctx, redirectedUserID) + if err != nil { + // User is fetching/cloning a non-existent repository + log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr()) + ctx.JSON(http.StatusNotFound, private.Response{ + UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName), + }) + return + } + + log.Info("User %s has been redirected to %s", results.OwnerName, redirectUser.Name) + results.OwnerName = redirectUser.Name + owner = redirectUser } if !owner.IsOrganization() && !owner.IsActive { ctx.JSON(http.StatusForbidden, private.Response{ @@ -143,24 +150,33 @@ func ServCommand(ctx *context.PrivateContext) { return } - redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName) - if err == nil { - redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID) - if err == nil { - log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name) - results.RepoName = redirectedRepo.Name - results.OwnerName = redirectedRepo.OwnerName - owner.ID = redirectedRepo.OwnerID - } else { - log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID) - } - } - // Now get the Repository and set the results section repoExist := true repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName) if err != nil { - if repo_model.IsErrRepoNotExist(err) { + if !repo_model.IsErrRepoNotExist(err) { + log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err), + }) + return + } + + redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName) + if err == nil { + redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID) + if err == nil { + log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name) + results.RepoName = redirectedRepo.Name + results.OwnerName = redirectedRepo.OwnerName + repo = redirectedRepo + owner.ID = redirectedRepo.OwnerID + } else { + log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID) + } + } + + if repo == nil { repoExist = false if mode == perm.AccessModeRead { // User is fetching/cloning a non-existent repository @@ -170,13 +186,6 @@ func ServCommand(ctx *context.PrivateContext) { }) return } - // else fallthrough (push-to-create may kick in below) - } else { - log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err), - }) - return } } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index a3389361513..ed0eecf90a6 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -409,7 +409,7 @@ func EditUserPost(ctx *context.Context) { } if form.Email != "" { - if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil { + if err := user_service.ReplacePrimaryEmailAddress(ctx, u, form.Email); err != nil { switch { case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f1c155e78f5..f7ce5875ca8 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -277,8 +277,11 @@ type LinkAccountData struct { GothUser goth.User } +func init() { + gob.Register(LinkAccountData{}) // TODO: CHI-SESSION-GOB-REGISTER +} + func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData { - gob.Register(LinkAccountData{}) v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData) if !ok { return nil @@ -287,7 +290,6 @@ func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData { } func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error { - gob.Register(LinkAccountData{}) return updateSession(ctx, nil, map[string]any{ "linkAccountData": linkAccountData, }) diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index e304633f952..0eebff6aa88 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -10,7 +10,6 @@ import ( "net/url" "path" "strconv" - "strings" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -42,8 +41,8 @@ type blameRow struct { // RefBlame render blame page func RefBlame(ctx *context.Context) { - ctx.Data["PageIsViewCode"] = true ctx.Data["IsBlame"] = true + prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL()) // Get current entry user currently looking at. if ctx.Repo.TreePath == "" { @@ -56,17 +55,6 @@ func RefBlame(ctx *context.Context) { return } - treeNames := strings.Split(ctx.Repo.TreePath, "/") - var paths []string - for i := range treeNames { - paths = append(paths, strings.Join(treeNames[:i+1], "/")) - } - - ctx.Data["Paths"] = paths - ctx.Data["TreeNames"] = treeNames - ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - blob := entry.Blob() fileSize := blob.Size() ctx.Data["FileSize"] = fileSize diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index f21f5682318..2b0ba9072d8 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -15,6 +15,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" repo_module "code.gitea.io/gitea/modules/repository" @@ -133,8 +134,7 @@ func RestoreBranchPost(ctx *context.Context) { return } - if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{ - Remote: ctx.Repo.Repository.RepoPath(), + if err := gitrepo.Push(ctx, ctx.Repo.Repository, ctx.Repo.Repository, git.PushOptions{ Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name), Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository), }); err != nil { diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 6bb9a8ae770..f702b2de16e 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -222,7 +222,7 @@ func FileHistory(ctx *context.Context) { return } - commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath) + commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath) if err != nil { ctx.ServerError("FileCommitsCount", err) return diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 8c630cb35f0..983249a6d24 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -41,7 +41,12 @@ const ( editorCommitChoiceNewBranch string = "commit-to-new-branch" ) -func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions { +func prepareEditorPage(ctx *context.Context, editorAction string) *context.CommitFormOptions { + prepareHomeTreeSideBarSwitch(ctx) + return prepareEditorPageFormOptions(ctx, editorAction) +} + +func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions { cleanedTreePath := files_service.CleanGitTreePath(ctx.Repo.TreePath) if cleanedTreePath != ctx.Repo.TreePath { redirectTo := fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, editorAction, util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(cleanedTreePath)) @@ -283,7 +288,7 @@ func EditFile(ctx *context.Context) { // on the "New File" page, we should add an empty path field to make end users could input a new name prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath)) - prepareEditorCommitFormOptions(ctx, editorAction) + prepareEditorPage(ctx, editorAction) if ctx.Written() { return } @@ -376,15 +381,16 @@ func EditFilePost(ctx *context.Context) { // DeleteFile render delete file page func DeleteFile(ctx *context.Context) { - prepareEditorCommitFormOptions(ctx, "_delete") + prepareEditorPage(ctx, "_delete") if ctx.Written() { return } ctx.Data["PageIsDelete"] = true + prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath) ctx.HTML(http.StatusOK, tplDeleteFile) } -// DeleteFilePost response for deleting file +// DeleteFilePost response for deleting file or directory func DeleteFilePost(ctx *context.Context) { parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx) if ctx.Written() { @@ -392,17 +398,37 @@ func DeleteFilePost(ctx *context.Context) { } treePath := ctx.Repo.TreePath - _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ + if treePath == "" { + ctx.JSONError("cannot delete root directory") // it should not happen unless someone is trying to be malicious + return + } + + // Check if the path is a directory + entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath) + if err != nil { + ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err) + return + } + + var commitMessage string + if entry.IsDir() { + commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete_directory", treePath)) + } else { + commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)) + } + + _, err = files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ LastCommitID: parsed.form.LastCommit, OldBranch: parsed.OldBranchName, NewBranch: parsed.NewBranchName, Files: []*files_service.ChangeRepoFile{ { - Operation: "delete", - TreePath: treePath, + Operation: "delete", + TreePath: treePath, + DeleteRecursively: true, }, }, - Message: parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)), + Message: commitMessage, Signoff: parsed.form.Signoff, Author: parsed.GitCommitter, Committer: parsed.GitCommitter, @@ -412,7 +438,11 @@ func DeleteFilePost(ctx *context.Context) { return } - ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath)) + if entry.IsDir() { + ctx.Flash.Success(ctx.Tr("repo.editor.directory_delete_success", treePath)) + } else { + ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath)) + } redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath) redirectForCommitChoice(ctx, parsed, redirectTreePath) } @@ -420,7 +450,7 @@ func DeleteFilePost(ctx *context.Context) { func UploadFile(ctx *context.Context) { ctx.Data["PageIsUpload"] = true prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath) - opts := prepareEditorCommitFormOptions(ctx, "_upload") + opts := prepareEditorPage(ctx, "_upload") if ctx.Written() { return } diff --git a/routers/web/repo/editor_apply_patch.go b/routers/web/repo/editor_apply_patch.go index aad7b4129c7..357c6f3a21a 100644 --- a/routers/web/repo/editor_apply_patch.go +++ b/routers/web/repo/editor_apply_patch.go @@ -14,7 +14,7 @@ import ( ) func NewDiffPatch(ctx *context.Context) { - prepareEditorCommitFormOptions(ctx, "_diffpatch") + prepareEditorPage(ctx, "_diffpatch") if ctx.Written() { return } diff --git a/routers/web/repo/editor_cherry_pick.go b/routers/web/repo/editor_cherry_pick.go index 099814a9fa9..32e3c58e874 100644 --- a/routers/web/repo/editor_cherry_pick.go +++ b/routers/web/repo/editor_cherry_pick.go @@ -16,7 +16,7 @@ import ( ) func CherryPick(ctx *context.Context) { - prepareEditorCommitFormOptions(ctx, "_cherrypick") + prepareEditorPage(ctx, "_cherrypick") if ctx.Written() { return } diff --git a/routers/web/repo/editor_util.go b/routers/web/repo/editor_util.go index f910f0bd407..07bcb474f0a 100644 --- a/routers/web/repo/editor_util.go +++ b/routers/web/repo/editor_util.go @@ -13,6 +13,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -102,8 +103,7 @@ func getUniqueRepositoryName(ctx context.Context, ownerID int64, name string) st } func editorPushBranchToForkedRepository(ctx context.Context, doer *user_model.User, baseRepo *repo_model.Repository, baseBranchName string, targetRepo *repo_model.Repository, targetBranchName string) error { - return git.Push(ctx, baseRepo.RepoPath(), git.PushOptions{ - Remote: targetRepo.RepoPath(), + return gitrepo.Push(ctx, baseRepo, targetRepo, git.PushOptions{ Branch: baseBranchName + ":" + targetBranchName, Env: repo_module.PushingEnvironment(doer, targetRepo), }) diff --git a/routers/web/repo/find.go b/routers/web/repo/find.go deleted file mode 100644 index 3a3a7610e77..00000000000 --- a/routers/web/repo/find.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "net/http" - - "code.gitea.io/gitea/modules/templates" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/services/context" -) - -const ( - tplFindFiles templates.TplName = "repo/find/files" -) - -// FindFiles render the page to find repository files -func FindFiles(ctx *context.Context) { - path := ctx.PathParam("*") - ctx.Data["TreeLink"] = ctx.Repo.RepoLink + "/src/" + util.PathEscapeSegments(path) - ctx.Data["DataLink"] = ctx.Repo.RepoLink + "/tree-list/" + util.PathEscapeSegments(path) - ctx.HTML(http.StatusOK, tplFindFiles) -} diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index edad756b6bd..35124c5c3ef 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/renderhelper" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" @@ -141,8 +142,7 @@ func NewComment(ctx *context.Context) { if prHeadCommitID != headBranchCommitID { // force push to base repo - err := git.Push(ctx, pull.HeadRepo.RepoPath(), git.PushOptions{ - Remote: pull.BaseRepo.RepoPath(), + err := gitrepo.Push(ctx, pull.HeadRepo, pull.BaseRepo, git.PushOptions{ Branch: pull.HeadBranch + ":" + prHeadRef, Force: true, Env: repo_module.InternalPushingEnvironment(pull.Issue.Poster, pull.BaseRepo), diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index ea15e90e5c2..8f4adb2ad22 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/migrations" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/services/task" ) @@ -237,7 +238,7 @@ func MigratePost(ctx *context.Context) { opts.AWSSecretAccessKey = form.AWSSecretAccessKey } - err = repo_model.CheckCreateRepository(ctx, ctx.Doer, ctxUser, opts.RepoName, false) + err = repo_service.CheckCreateRepository(ctx, ctx.Doer, ctxUser, opts.RepoName, false) if err != nil { handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form) return diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index 18e14e9b224..f064058221e 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -331,7 +331,7 @@ func UpdateViewedFiles(ctx *context.Context) { updatedFiles[file] = state } - if err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil { + if _, err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil { ctx.ServerError("UpdateReview", err) } } diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 4ed9e0bdbde..33e0dc58894 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -5,6 +5,7 @@ package repo import ( + stdCtx "context" "errors" "fmt" "net/http" @@ -18,6 +19,7 @@ import ( "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/gitrepo" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -39,7 +41,7 @@ const ( ) // calReleaseNumCommitsBehind calculates given release has how many commits behind release target. -func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error { +func calReleaseNumCommitsBehind(ctx stdCtx.Context, repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error { target := release.Target if target == "" { target = repoCtx.Repository.DefaultBranch @@ -59,7 +61,7 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model return fmt.Errorf("GetBranchCommit(DefaultBranch): %w", err) } } - countCache[target], err = commit.CommitsCount() + countCache[target], err = gitrepo.CommitsCountOfCommit(ctx, repoCtx.Repository, commit.ID.String()) if err != nil { return fmt.Errorf("CommitsCount: %w", err) } @@ -122,7 +124,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) } if !r.IsDraft { - if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { + if err := calReleaseNumCommitsBehind(ctx, ctx.Repo, r, countCache); err != nil { return nil, err } } diff --git a/routers/web/repo/release_test.go b/routers/web/repo/release_test.go index 9f49fc75007..7ba91afb297 100644 --- a/routers/web/repo/release_test.go +++ b/routers/web/repo/release_test.go @@ -158,7 +158,7 @@ func TestCalReleaseNumCommitsBehind(t *testing.T) { countCache := make(map[string]int64) for _, release := range releases { - err := calReleaseNumCommitsBehind(ctx.Repo, release, countCache) + err := calReleaseNumCommitsBehind(ctx, ctx.Repo, release, countCache) assert.NoError(t, err) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 07435a550dc..3a0976ffa06 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -70,7 +70,7 @@ func CommitInfoCache(ctx *context.Context) { ctx.ServerError("GetBranchCommit", err) return } - ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() + ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount(ctx) if err != nil { ctx.ServerError("GetCommitsCount", err) return diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index a558231df12..c7a19062d26 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/git/pipeline" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -112,7 +113,7 @@ func LFSLocks(ctx *context.Context) { } defer cleanup() - if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ + if err := gitrepo.CloneRepoToLocal(ctx, ctx.Repo.Repository, tmpBasePath, git.CloneRepoOptions{ Bare: true, Shared: true, }); err != nil { diff --git a/routers/web/repo/treelist_test.go b/routers/web/repo/treelist_test.go index 94ba60661b1..019fe085d4b 100644 --- a/routers/web/repo/treelist_test.go +++ b/routers/web/repo/treelist_test.go @@ -33,7 +33,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) { }) mockIconForFile := func(id string) template.HTML { - return template.HTML(``) + return template.HTML(``) } assert.Equal(t, WebDiffFileTree{ TreeRoot: WebDiffFileItem{ diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 09ac33cff43..8e85cc32785 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -245,27 +245,17 @@ func LastCommit(ctx *context.Context) { return } + // The "/lastcommit/" endpoint is used to render the embedded HTML content for the directory file listing with latest commit info + // It needs to construct correct links to the file items, but the route only accepts a commit ID, not a full ref name (branch or tag). + // So we need to get the ref name from the query parameter "refSubUrl". + // TODO: LAST-COMMIT-ASYNC-LOADING: it needs more tests to cover this + refSubURL := path.Clean(ctx.FormString("refSubUrl")) + prepareRepoViewContent(ctx, util.IfZero(refSubURL, ctx.Repo.RefTypeNameSubURL())) renderDirectoryFiles(ctx, 0) if ctx.Written() { return } - var treeNames []string - paths := make([]string, 0, 5) - if len(ctx.Repo.TreePath) > 0 { - treeNames = strings.Split(ctx.Repo.TreePath, "/") - for i := range treeNames { - paths = append(paths, strings.Join(treeNames[:i+1], "/")) - } - - ctx.Data["HasParentPath"] = true - if len(paths)-2 >= 0 { - ctx.Data["ParentPath"] = "/" + paths[len(paths)-2] - } - } - branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.Data["BranchLink"] = branchLink - ctx.HTML(http.StatusOK, tplRepoViewList) } @@ -289,7 +279,9 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri return nil } - ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + // TODO: LAST-COMMIT-ASYNC-LOADING: search this keyword to see more details + lastCommitLoaderURL := ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + ctx.Data["LastCommitLoaderURL"] = lastCommitLoaderURL + "?refSubUrl=" + url.QueryEscape(ctx.Repo.RefTypeNameSubURL()) // Get current entry user currently looking at. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) @@ -322,6 +314,21 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri ctx.ServerError("GetCommitsInfo", err) return nil } + + { + if timeout != 0 && !setting.IsProd && !setting.IsInTesting { + log.Debug("first call to get directory file commit info") + clearFilesCommitInfo := func() { + log.Warn("clear directory file commit info to force async loading on frontend") + for i := range files { + files[i].Commit = nil + } + } + _ = clearFilesCommitInfo + // clearFilesCommitInfo() // TODO: LAST-COMMIT-ASYNC-LOADING: debug the frontend async latest commit info loading, uncomment this line, and it needs more tests + } + } + ctx.Data["Files"] = files prepareDirectoryFileIcons(ctx, files) for _, f := range files { @@ -334,16 +341,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri if !loadLatestCommitData(ctx, latestCommit) { return nil } - - branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - treeLink := branchLink - - if len(ctx.Repo.TreePath) > 0 { - treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - } - - ctx.Data["TreeLink"] = treeLink - return allEntries } diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 17043055e5f..00d30bedef5 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -362,6 +362,32 @@ func redirectFollowSymlink(ctx *context.Context, treePathEntry *git.TreeEntry) b return false } +func prepareRepoViewContent(ctx *context.Context, refTypeNameSubURL string) { + // for: home, file list, file view, blame + ctx.Data["PageIsViewCode"] = true + ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show Upload File button or menu item + + // prepare the tree path navigation + var treeNames, paths []string + branchLink := ctx.Repo.RepoLink + "/src/" + refTypeNameSubURL + treeLink := branchLink + if ctx.Repo.TreePath != "" { + treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + treeNames = strings.Split(ctx.Repo.TreePath, "/") + for i := range treeNames { + paths = append(paths, strings.Join(treeNames[:i+1], "/")) + } + ctx.Data["HasParentPath"] = true + if len(paths)-2 >= 0 { + ctx.Data["ParentPath"] = "/" + paths[len(paths)-2] + } + } + ctx.Data["Paths"] = paths + ctx.Data["TreeLink"] = treeLink + ctx.Data["TreeNames"] = treeNames + ctx.Data["BranchLink"] = branchLink +} + // Home render repository home page func Home(ctx *context.Context) { if handleRepoHomeFeed(ctx) { @@ -383,8 +409,7 @@ func Home(ctx *context.Context) { title += ": " + ctx.Repo.Repository.Description } ctx.Data["Title"] = title - ctx.Data["PageIsViewCode"] = true - ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show New File / Upload File buttons + prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL()) if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() { // empty or broken repositories need to be handled differently @@ -405,26 +430,6 @@ func Home(ctx *context.Context) { return } - // prepare the tree path - var treeNames, paths []string - branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - treeLink := branchLink - if ctx.Repo.TreePath != "" { - treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - treeNames = strings.Split(ctx.Repo.TreePath, "/") - for i := range treeNames { - paths = append(paths, strings.Join(treeNames[:i+1], "/")) - } - ctx.Data["HasParentPath"] = true - if len(paths)-2 >= 0 { - ctx.Data["ParentPath"] = "/" + paths[len(paths)-2] - } - } - ctx.Data["Paths"] = paths - ctx.Data["TreeLink"] = treeLink - ctx.Data["TreeNames"] = treeNames - ctx.Data["BranchLink"] = branchLink - // some UI components are only shown when the tree path is root isTreePathRoot := ctx.Repo.TreePath == "" @@ -455,7 +460,7 @@ func Home(ctx *context.Context) { if isViewHomeOnlyContent(ctx) { ctx.HTML(http.StatusOK, tplRepoViewContent) - } else if len(treeNames) != 0 { + } else if ctx.Repo.TreePath != "" { ctx.HTML(http.StatusOK, tplRepoView) } else { ctx.HTML(http.StatusOK, tplRepoHome) diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 542ac9c7316..921e17fb6a4 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -310,7 +310,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { } // get commit count - wiki revisions - commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) + commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename) ctx.Data["CommitCount"] = commitsCount return wikiGitRepo, entry @@ -350,7 +350,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } // get commit count - wiki revisions - commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) + commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename) ctx.Data["CommitCount"] = commitsCount // get page diff --git a/routers/web/web.go b/routers/web/web.go index 8b55e4469ee..68907893215 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -227,6 +227,8 @@ func ctxDataSet(args ...any) func(ctx *context.Context) { } } +const RouterMockPointBeforeWebRoutes = "before-web-routes" + // Routes returns all web routes func Routes() *web.Router { routes := web.NewRouter() @@ -267,11 +269,7 @@ func Routes() *web.Router { routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) - if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil { - mid = append(mid, sessionMid, context.Contexter()) - } else { - log.Fatal("common.Sessioner failed: %v", err) - } + mid = append(mid, common.MustInitSessioner(), context.Contexter()) // Get user from session if logged in. mid = append(mid, webAuth(buildAuthGroup())) @@ -289,7 +287,7 @@ func Routes() *web.Router { webRoutes := web.NewRouter() webRoutes.Use(mid...) - webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS()) + webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS(), web.RouterMockPoint(RouterMockPointBeforeWebRoutes)) routes.Mount("", webRoutes) return routes } @@ -1188,7 +1186,6 @@ func registerWebRoutes(m *web.Router) { m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup) m.Group("/{username}/{reponame}", func() { - m.Get("/find/*", repo.FindFiles) m.Group("/tree-list", func() { m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeList) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeList) diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go index 313f375281b..2a165bac85d 100644 --- a/services/auth/source/oauth2/init.go +++ b/services/auth/source/oauth2/init.go @@ -22,9 +22,6 @@ import ( var gothRWMutex = sync.RWMutex{} -// UsersStoreKey is the key for the store -const UsersStoreKey = "gitea-oauth2-sessions" - // ProviderHeaderKey is the HTTP header key const ProviderHeaderKey = "gitea-oauth2-provider" @@ -33,7 +30,7 @@ func Init(ctx context.Context) error { // Lock our mutex gothRWMutex.Lock() - gob.Register(&sessions.Session{}) + gob.Register(&sessions.Session{}) // TODO: CHI-SESSION-GOB-REGISTER. FIXME: it seems to be an abuse, why the Session struct itself is stored in session store again? gothic.Store = &SessionsStore{ maxLength: int64(setting.OAuth2.MaxTokenLength), diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index db7c6aab96f..fc290aa7714 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -35,9 +35,9 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u // Allow PAM sources with `@` in their name, like from Active Directory username := pamLogin email := pamLogin - idx := strings.Index(pamLogin, "@") - if idx > -1 { - username = pamLogin[:idx] + before, _, ok := strings.Cut(pamLogin, "@") + if ok { + username = before } if user_model.ValidateEmail(email) != nil { if source.EmailDomain != "" { diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index b8e668f5f9d..de39c1d3a61 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -21,10 +21,10 @@ import ( func (source *Source) Authenticate(ctx context.Context, user *user_model.User, userName, password string) (*user_model.User, error) { // Verify allowed domains. if len(source.AllowedDomains) > 0 { - idx := strings.Index(userName, "@") - if idx == -1 { + _, after, ok := strings.Cut(userName, "@") + if !ok { return nil, user_model.ErrUserNotExist{Name: userName} - } else if !util.SliceContainsString(strings.Split(source.AllowedDomains, ","), userName[idx+1:], true) { + } else if !util.SliceContainsString(strings.Split(source.AllowedDomains, ","), after, true) { return nil, user_model.ErrUserNotExist{Name: userName} } } @@ -61,9 +61,9 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u } username := userName - idx := strings.Index(userName, "@") - if idx > -1 { - username = userName[:idx] + before, _, ok := strings.Cut(userName, "@") + if ok { + username = before } user = &user_model.User{ diff --git a/services/context/base.go b/services/context/base.go index de839ede814..8bd66bed092 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -43,8 +43,10 @@ type Base struct { Locale translation.Locale } +var ParseMultipartFormMaxMemory = int64(32 << 20) + func (b *Base) ParseMultipartForm() bool { - err := b.Req.ParseMultipartForm(32 << 20) + err := b.Req.ParseMultipartForm(ParseMultipartFormMaxMemory) if err != nil { // TODO: all errors caused by client side should be ignored (connection closed). if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) { diff --git a/services/context/csrf.go b/services/context/csrf.go index f190465bdb3..aa99f34b030 100644 --- a/services/context/csrf.go +++ b/services/context/csrf.go @@ -118,7 +118,7 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) { if uidChanged { _ = ctx.Session.Set(c.opt.oldSessionKey, c.id) } else if cookieToken != "" { - // If cookie token presents, re-use existing unexpired token, else generate a new one. + // If cookie token present, re-use existing unexpired token, else generate a new one. if issueTime, ok := ParseCsrfToken(cookieToken); ok { dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time. if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval { diff --git a/services/context/repo.go b/services/context/repo.go index cfbfb33ab9e..64b86952364 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -202,14 +202,14 @@ func (r *Repository) CanCreateIssueDependencies(ctx context.Context, user *user_ } // GetCommitsCount returns cached commit count for current view -func (r *Repository) GetCommitsCount() (int64, error) { +func (r *Repository) GetCommitsCount(ctx context.Context) (int64, error) { if r.Commit == nil { return 0, nil } contextName := r.RefFullName.ShortName() isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag() return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) { - return r.Commit.CommitsCount() + return gitrepo.CommitsCountOfCommit(ctx, r.Repository, r.Commit.ID.String()) }) } @@ -219,11 +219,10 @@ func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, return cache.GetInt64(cacheKey, func() (int64, error) { if len(branches) == 0 { - return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...) + return gitrepo.AllCommitsCount(ctx, r.Repository, hidePRRefs, files...) } - return git.CommitsCount(ctx, - git.CommitsCountOptions{ - RepoPath: r.Repository.RepoPath(), + return gitrepo.CommitsCount(ctx, r.Repository, + gitrepo.CommitsCountOptions{ Revision: branches, RelPath: files, }) @@ -821,7 +820,7 @@ func RepoRefByDefaultBranch() func(*Context) { ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch) ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName) - ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount() + ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount(ctx) ctx.Data["RefFullName"] = ctx.Repo.RefFullName ctx.Data["BranchName"] = ctx.Repo.BranchName ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount @@ -858,7 +857,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { if err == nil && len(brs) != 0 { refShortName = brs[0] } else if len(brs) == 0 { - log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) + log.Error("No branches in non-empty repository %s", ctx.Repo.Repository.RelativePath()) } else { log.Error("GetBranches error: %v", err) } @@ -970,7 +969,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef - ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() + ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount(ctx) if err != nil { ctx.ServerError("GetCommitsCount", err) return diff --git a/services/convert/git_commit.go b/services/convert/git_commit.go index d0228c45fb2..bf17024d2d0 100644 --- a/services/convert/git_commit.go +++ b/services/convert/git_commit.go @@ -11,6 +11,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -190,7 +191,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep // Retrieve files affected by the commit if opts.Files { - fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String()) + fileStatus, err := gitrepo.GetCommitFileStatus(ctx, repo, commit.ID.String()) if err != nil { return nil, err } diff --git a/services/doctor/misc.go b/services/doctor/misc.go index ce7eea1dcc8..445ff61ffb1 100644 --- a/services/doctor/misc.go +++ b/services/doctor/misc.go @@ -215,7 +215,7 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro if !isExist { numNeedUpdate++ if autofix { - if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil { + if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil { logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err) return err } diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 4ad06bc04ff..6e15f716095 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1434,15 +1434,17 @@ outer: } } - // Explicitly store files that have changed in the database, if any is present at all. - // This has the benefit that the "Has Changed" attribute will be present as long as the user does not explicitly mark this file as viewed, so it will even survive a page reload after marking another file as viewed. - // On the other hand, this means that even if a commit reverting an unseen change is committed, the file will still be seen as changed. if len(filesChangedSinceLastDiff) > 0 { - err := pull_model.UpdateReviewState(ctx, review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff) + // Explicitly store files that have changed in the database, if any is present at all. + // This has the benefit that the "Has Changed" attribute will be present as long as the user does not explicitly mark this file as viewed, so it will even survive a page reload after marking another file as viewed. + // On the other hand, this means that even if a commit reverting an unseen change is committed, the file will still be seen as changed. + updatedReview, err := pull_model.UpdateReviewState(ctx, review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff) if err != nil { log.Warn("Could not update review for user %d, pull %d, commit %s and the changed files %v: %v", review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff, err) return nil, err } + // Update the local review to reflect the changes immediately + review = updatedReview } return review, nil diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go index eade0cf271b..1efaa845b81 100644 --- a/services/mailer/incoming/incoming.go +++ b/services/mailer/incoming/incoming.go @@ -6,6 +6,7 @@ package incoming import ( "context" "crypto/tls" + "errors" "fmt" net_mail "net/mail" "regexp" @@ -221,7 +222,7 @@ loop: err := func() error { r := msg.GetBody(section) if r == nil { - return fmt.Errorf("could not get body from message: %w", err) + return errors.New("could not get body from message") } env, err := enmime.ReadEnvelope(r) diff --git a/services/mailer/sender/sendmail.go b/services/mailer/sender/sendmail.go index 64c7f8f0816..7064c60f976 100644 --- a/services/mailer/sender/sendmail.go +++ b/services/mailer/sender/sendmail.go @@ -33,7 +33,13 @@ func (s *SendmailSender) Send(from string, to []string, msg io.WriterTo) error { args := []string{"-f", envelopeFrom, "-i"} args = append(args, setting.MailService.SendmailArgs...) - args = append(args, to...) + for _, recipient := range to { + smtpTo, err := sanitizeEmailAddress(recipient) + if err != nil { + return fmt.Errorf("invalid recipient address %q: %w", recipient, err) + } + args = append(args, smtpTo) + } log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args) diff --git a/services/mailer/sender/smtp.go b/services/mailer/sender/smtp.go index 3207eee32fc..fa07803359c 100644 --- a/services/mailer/sender/smtp.go +++ b/services/mailer/sender/smtp.go @@ -9,13 +9,13 @@ import ( "fmt" "io" "net" + "net/mail" + "net/smtp" "os" "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - - "github.com/wneessen/go-mail/smtp" ) // SMTPSender Sender SMTP mail sender @@ -108,7 +108,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { if strings.Contains(options, "CRAM-MD5") { auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd) } else if strings.Contains(options, "PLAIN") { - auth = smtp.PlainAuth("", opts.User, opts.Passwd, host, false) + auth = smtp.PlainAuth("", opts.User, opts.Passwd, host) } else if strings.Contains(options, "LOGIN") { // Patch for AUTH LOGIN auth = LoginAuth(opts.User, opts.Passwd) @@ -123,18 +123,24 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { } } - if opts.OverrideEnvelopeFrom { - if err = client.Mail(opts.EnvelopeFrom); err != nil { - return fmt.Errorf("failed to issue MAIL command: %w", err) - } - } else { - if err = client.Mail(fmt.Sprintf("<%s>", from)); err != nil { - return fmt.Errorf("failed to issue MAIL command: %w", err) - } + fromAddr := from + if opts.OverrideEnvelopeFrom && opts.EnvelopeFrom != "" { + fromAddr = opts.EnvelopeFrom + } + smtpFrom, err := sanitizeEmailAddress(fromAddr) + if err != nil { + return fmt.Errorf("invalid envelope from address: %w", err) + } + if err = client.Mail(smtpFrom); err != nil { + return fmt.Errorf("failed to issue MAIL command: %w", err) } for _, rec := range to { - if err = client.Rcpt(rec); err != nil { + smtpTo, err := sanitizeEmailAddress(rec) + if err != nil { + return fmt.Errorf("invalid recipient address %q: %w", rec, err) + } + if err = client.Rcpt(smtpTo); err != nil { return fmt.Errorf("failed to issue RCPT command: %w", err) } } @@ -155,3 +161,11 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { return nil } + +func sanitizeEmailAddress(raw string) (string, error) { + addr, err := mail.ParseAddress(strings.TrimSpace(strings.Trim(raw, "<>"))) + if err != nil { + return "", err + } + return addr.Address, nil +} diff --git a/services/mailer/sender/smtp_auth.go b/services/mailer/sender/smtp_auth.go index c60e0dbfbbe..66ea24e8960 100644 --- a/services/mailer/sender/smtp_auth.go +++ b/services/mailer/sender/smtp_auth.go @@ -6,9 +6,9 @@ package sender import ( "errors" "fmt" + "net/smtp" "github.com/Azure/go-ntlmssp" - "github.com/wneessen/go-mail/smtp" ) type loginAuth struct { diff --git a/services/mailer/sender/smtp_test.go b/services/mailer/sender/smtp_test.go new file mode 100644 index 00000000000..1e944583a6a --- /dev/null +++ b/services/mailer/sender/smtp_test.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package sender + +import "testing" + +func TestSanitizeEmailAddress(t *testing.T) { + tests := []struct { + input string + expected string + hasError bool + }{ + {"abc@gitea.com", "abc@gitea.com", false}, + {"", "abc@gitea.com", false}, + {"ssss.com", "", true}, + {"", "", true}, + } + + for _, tt := range tests { + result, err := sanitizeEmailAddress(tt.input) + if (err != nil) != tt.hasError { + t.Errorf("sanitizeEmailAddress(%q) unexpected error status: got %v, want error: %v", tt.input, err != nil, tt.hasError) + continue + } + if result != tt.expected { + t.Errorf("sanitizeEmailAddress(%q) = %q; want %q", tt.input, result, tt.expected) + } + } +} diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 4d23060661d..5c2d86550b2 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -303,7 +303,7 @@ func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*ba return fmt.Errorf("GetTagCommit[%v]: %w", rel.TagName, err) } rel.Sha1 = commit.ID.String() - rel.NumCommits, err = commit.CommitsCount() + rel.NumCommits, err = gitrepo.CommitsCountOfCommit(ctx, g.repo, commit.ID.String()) if err != nil { return fmt.Errorf("CommitsCount: %w", err) } diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index eba9c79df57..bd7e52cc3d0 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -16,6 +16,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/log" @@ -327,6 +328,9 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba messenger("repo.migrate.migrating_issues") issueBatchSize := uploader.MaxBatchInsertSize("issue") + // because when the migrating is running, some issues maybe removed, so after the next page + // some of issue maybe duplicated, so we need to record the inserted issue indexes + mapInsertedIssueIndexes := container.Set[int64]{} for i := 1; ; i++ { issues, isEnd, err := downloader.GetIssues(ctx, i, issueBatchSize) if err != nil { @@ -336,6 +340,14 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba log.Warn("migrating issues is not supported, ignored") break } + for i := 0; i < len(issues); i++ { + if mapInsertedIssueIndexes.Contains(issues[i].Number) { + issues = append(issues[:i], issues[i+1:]...) + i-- + continue + } + mapInsertedIssueIndexes.Add(issues[i].Number) + } if err := uploader.CreateIssues(ctx, issues...); err != nil { return err @@ -381,6 +393,7 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba log.Trace("migrating pull requests and comments") messenger("repo.migrate.migrating_pulls") prBatchSize := uploader.MaxBatchInsertSize("pullrequest") + mapInsertedPRIndexes := container.Set[int64]{} for i := 1; ; i++ { prs, isEnd, err := downloader.GetPullRequests(ctx, i, prBatchSize) if err != nil { @@ -390,6 +403,14 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba log.Warn("migrating pull requests is not supported, ignored") break } + for i := 0; i < len(prs); i++ { + if mapInsertedPRIndexes.Contains(prs[i].Number) { + prs = append(prs[:i], prs[i+1:]...) + i-- + continue + } + mapInsertedPRIndexes.Add(prs[i].Number) + } if err := uploader.CreatePullRequests(ctx, prs...); err != nil { return err diff --git a/services/migrations/onedev_test.go b/services/migrations/onedev_test.go index a05d6cac6e9..3319e198510 100644 --- a/services/migrations/onedev_test.go +++ b/services/migrations/onedev_test.go @@ -23,9 +23,6 @@ func TestOneDevDownloadRepo(t *testing.T) { u, _ := url.Parse("https://code.onedev.io") ctx := t.Context() downloader := NewOneDevDownloader(ctx, u, "", "", "go-gitea-test_repo") - if err != nil { - t.Fatalf("NewOneDevDownloader is nil: %v", err) - } repo, err := downloader.GetRepoInfo(ctx) assert.NoError(t, err) assertRepositoryEqual(t, &base.Repository{ diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index b61345e830d..bae189ba879 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -153,7 +153,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { log.Trace("Pushing %s mirror[%d] remote %s", storageRepo.RelativePath(), m.ID, m.RemoteName) envs := proxy.EnvWithProxy(remoteURL.URL) - if err := gitrepo.Push(ctx, storageRepo, git.PushOptions{ + if err := gitrepo.PushToExternal(ctx, storageRepo, git.PushOptions{ Remote: m.RemoteName, Force: true, Mirror: true, diff --git a/services/pull/pull.go b/services/pull/pull.go index 6f0318ea49c..ecc0b2c7ceb 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -427,10 +427,16 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) { for _, pr := range headBranchPRs { objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) if opts.NewCommitID != "" && opts.NewCommitID != objectFormat.EmptyObjectID().String() { - changed, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID) + changed, newMergeBase, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID) if err != nil { log.Error("checkIfPRContentChanged: %v", err) } + if newMergeBase != "" && pr.MergeBase != newMergeBase { + pr.MergeBase = newMergeBase + if _, err := pr.UpdateColsIfNotMerged(ctx, "merge_base"); err != nil { + log.Error("Update merge base for %-v: %v", pr, err) + } + } if changed { // Mark old reviews as stale if diff to mergebase has changed if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil { @@ -496,30 +502,30 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) { // checkIfPRContentChanged checks if diff to target branch has changed by push // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged -func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) { +func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, mergeBase string, err error) { prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) // FIXME: why it still needs to create a temp repo, since the alongside calls like GetDiverging doesn't do so anymore if err != nil { log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) - return false, err + return false, "", err } defer cancel() tmpRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath) if err != nil { - return false, fmt.Errorf("OpenRepository: %w", err) + return false, "", fmt.Errorf("OpenRepository: %w", err) } defer tmpRepo.Close() // Find the merge-base - _, base, err := tmpRepo.GetMergeBase("", "base", "tracking") + mergeBase, _, err = tmpRepo.GetMergeBase("", "base", "tracking") if err != nil { - return false, fmt.Errorf("GetMergeBase: %w", err) + return false, "", fmt.Errorf("GetMergeBase: %w", err) } - cmd := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) + cmd := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, mergeBase) stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { - return false, fmt.Errorf("unable to open pipe for to run diff: %w", err) + return false, mergeBase, fmt.Errorf("unable to open pipe for to run diff: %w", err) } stderr := new(bytes.Buffer) @@ -535,19 +541,19 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, }). Run(ctx); err != nil { if err == util.ErrNotEmpty { - return true, nil + return true, mergeBase, nil } err = gitcmd.ConcatenateError(err, stderr.String()) log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: Error: %v", - newCommitID, oldCommitID, base, + newCommitID, oldCommitID, mergeBase, pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch, err) - return false, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, base, err) + return false, mergeBase, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, mergeBase, err) } - return false, nil + return false, mergeBase, nil } // PushToBaseRepo pushes commits from branches of head repository to @@ -564,13 +570,11 @@ func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, pre log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err) return err } - headRepoPath := pr.HeadRepo.RepoPath() if err := pr.LoadBaseRepo(ctx); err != nil { log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) return err } - baseRepoPath := pr.BaseRepo.RepoPath() if err = pr.LoadIssue(ctx); err != nil { return fmt.Errorf("unable to load issue %d for pr %d: %w", pr.IssueID, pr.ID, err) @@ -581,8 +585,7 @@ func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, pre gitRefName := pr.GetGitHeadRefName() - if err := git.Push(ctx, headRepoPath, git.PushOptions{ - Remote: baseRepoPath, + if err := gitrepo.Push(ctx, pr.HeadRepo, pr.BaseRepo, git.PushOptions{ Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName, Force: true, // Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/... diff --git a/services/pull/review.go b/services/pull/review.go index 3977e351ca7..9aeeb4c31d6 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -333,7 +333,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos if headCommitID == commitID { stale = false } else { - stale, err = checkIfPRContentChanged(ctx, pr, commitID, headCommitID) + stale, _, err = checkIfPRContentChanged(ctx, pr, commitID, headCommitID) if err != nil { return nil, nil, err } diff --git a/services/release/release.go b/services/release/release.go index 4f121bf6d39..a0d3736b445 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -148,7 +148,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel } rel.Sha1 = commit.ID.String() - rel.NumCommits, err = commit.CommitsCount() + rel.NumCommits, err = gitrepo.CommitsCountOfCommit(ctx, rel.Repo, commit.ID.String()) if err != nil { return false, fmt.Errorf("CommitsCount: %w", err) } diff --git a/services/repository/branch.go b/services/repository/branch.go index 0a2fd30620d..fd1e7d04149 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -385,8 +385,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo return err } - if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ - Remote: repo.RepoPath(), + if err := gitrepo.Push(ctx, repo, repo, git.PushOptions{ Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), Env: repo_module.PushingEnvironment(doer, repo), }); err != nil { @@ -483,6 +482,64 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m return "", nil } +// UpdateBranch moves a branch reference to the provided commit. permission check should be done before calling this function. +func UpdateBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User, branchName, newCommitID, expectedOldCommitID string, force bool) error { + branch, err := git_model.GetBranch(ctx, repo.ID, branchName) + if err != nil { + return err + } + if branch.IsDeleted { + return git_model.ErrBranchNotExist{ + BranchName: branchName, + } + } + + if expectedOldCommitID != "" { + expectedID, err := gitRepo.ConvertToGitID(expectedOldCommitID) + if err != nil { + return fmt.Errorf("ConvertToGitID(old): %w", err) + } + if expectedID.String() != branch.CommitID { + return util.NewInvalidArgumentErrorf("branch commit does not match [expected: %s, given: %s]", expectedID.String(), branch.CommitID) + } + } + + newID, err := gitRepo.ConvertToGitID(newCommitID) + if err != nil { + return fmt.Errorf("ConvertToGitID(new): %w", err) + } + newCommit, err := gitRepo.GetCommit(newID.String()) + if err != nil { + return err + } + + if newCommit.ID.String() == branch.CommitID { + return nil + } + + isForcePush, err := newCommit.IsForcePush(branch.CommitID) + if err != nil { + return err + } + if isForcePush && !force { + return util.NewInvalidArgumentErrorf("Force push %s need a confirm force parameter", branchName) + } + + pushOpts := git.PushOptions{ + Remote: repo.RepoPath(), + Branch: fmt.Sprintf("%s:%s%s", newCommit.ID.String(), git.BranchPrefix, branchName), + Env: repo_module.PushingEnvironment(doer, repo), + Force: isForcePush || force, + } + + if expectedOldCommitID != "" { + pushOpts.ForceWithLease = fmt.Sprintf("%s:%s", git.BranchPrefix+branchName, branch.CommitID) + } + + // branch protection will be checked in the pre received hook, so that we don't need any check here + return gitrepo.Push(ctx, repo, repo, pushOpts) +} + var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default") func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error { diff --git a/services/repository/cache.go b/services/repository/cache.go index b0811a99fc0..4fc8d3ddf78 100644 --- a/services/repository/cache.go +++ b/services/repository/cache.go @@ -9,6 +9,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" ) // CacheRef cachhe last commit information of the branch or the tag @@ -19,7 +20,9 @@ func CacheRef(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep } if gitRepo.LastCommitCache == nil { - commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), commit.CommitsCount) + commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), func() (int64, error) { + return gitrepo.CommitsCountOfCommit(ctx, repo, commit.ID.String()) + }) if err != nil { return err } diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 2c1e88bb59e..d32d3041c29 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -11,7 +11,9 @@ import ( "strings" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -125,7 +127,20 @@ func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo * return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts) } -func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) { +func addLastCommitCache(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, cacheKey, fullName, sha string) error { + if gitRepo.LastCommitCache == nil { + commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) { + return gitrepo.CommitsCountOfCommit(ctx, repo, sha) + }) + if err != nil { + return err + } + gitRepo.LastCommitCache = git.NewLastCommitCache(commitsCount, fullName, gitRepo, cache.GetCache()) + } + return nil +} + +func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) { refType := refCommit.RefName.RefType() commit := refCommit.Commit selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef)) @@ -147,7 +162,7 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito } if opts.IncludeCommitMetadata || opts.IncludeCommitMessage { - err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID) + err = addLastCommitCache(ctx, repo, gitRepo, repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID) if err != nil { return nil, err } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index feb4811bb02..b7f4afdebc2 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -18,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -135,6 +136,14 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st return fileList, nil } +func (t *TemporaryUploadRepository) RemoveRecursivelyFromIndex(ctx context.Context, path string) error { + _, _, err := gitcmd.NewCommand("rm", "--cached", "-r"). + AddDynamicArguments(path). + WithDir(t.basePath). + RunStdBytes(ctx) + return err +} + // RemoveFilesFromIndex removes the given files from the index func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, filenames ...string) error { objFmt, err := t.gitRepo.GetObjectFormat() @@ -354,8 +363,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.User, commitHash, branch string, force bool) error { // Because calls hooks we need to pass in the environment env := repo_module.PushingEnvironment(doer, t.repo) - if err := git.Push(ctx, t.basePath, git.PushOptions{ - Remote: t.repo.RepoPath(), + if err := gitrepo.PushFromLocal(ctx, t.basePath, t.repo, git.PushOptions{ Branch: strings.TrimSpace(commitHash) + ":" + git.BranchPrefix + strings.TrimSpace(branch), Env: env, Force: force, diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 38ac9f25fc2..e7511b3eed4 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -67,13 +67,13 @@ func TestGetTreeViewNodes(t *testing.T) { curRepoLink := "/any/repo-link" renderedIconPool := fileicon.NewRenderedIconPool() mockIconForFile := func(id string) template.HTML { - return template.HTML(``) + return template.HTML(``) } mockIconForFolder := func(id string) template.HTML { - return template.HTML(``) + return template.HTML(``) } mockOpenIconForFolder := func(id string) template.HTML { - return template.HTML(``) + return template.HTML(``) } treeNodes, err := GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.Commit, "", "") assert.NoError(t, err) diff --git a/services/repository/files/update.go b/services/repository/files/update.go index b07055d57aa..4830f711fc2 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -46,7 +46,10 @@ type ChangeRepoFile struct { FromTreePath string ContentReader io.ReadSeeker SHA string - Options *RepoFileOptions + + DeleteRecursively bool // when deleting, work as `git rm -r ...` + + Options *RepoFileOptions // FIXME: need to refactor, internal usage only } // ChangeRepoFilesOptions holds the repository files update options @@ -69,26 +72,6 @@ type RepoFileOptions struct { executable bool } -// ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error. -type ErrRepoFileDoesNotExist struct { - Path string - Name string -} - -// IsErrRepoFileDoesNotExist checks if an error is a ErrRepoDoesNotExist. -func IsErrRepoFileDoesNotExist(err error) bool { - _, ok := err.(ErrRepoFileDoesNotExist) - return ok -} - -func (err ErrRepoFileDoesNotExist) Error() string { - return fmt.Sprintf("repository file does not exist [path: %s]", err.Path) -} - -func (err ErrRepoFileDoesNotExist) Unwrap() error { - return util.ErrNotExist -} - type LazyReadSeeker interface { io.ReadSeeker io.Closer @@ -217,24 +200,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } } - for _, file := range opts.Files { - if file.Operation == "delete" { - // Get the files in the index - filesInIndex, err := t.LsFiles(ctx, file.TreePath) - if err != nil { - return nil, fmt.Errorf("DeleteRepoFile: %w", err) - } - - // Find the file we want to delete in the index - inFilelist := slices.Contains(filesInIndex, file.TreePath) - if !inFilelist { - return nil, ErrRepoFileDoesNotExist{ - Path: file.TreePath, - } - } - } - } - if hasOldBranch { // Get the commit of the original branch commit, err := t.GetBranchCommit(opts.OldBranch) @@ -272,8 +237,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use addedLfsPointers = append(addedLfsPointers, *addedLfsPointer) } case "delete": - if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil { - return nil, err + if file.DeleteRecursively { + if err = t.RemoveRecursivelyFromIndex(ctx, file.TreePath); err != nil { + return nil, err + } + } else { + if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil { + return nil, err + } } default: return nil, fmt.Errorf("invalid file operation: %s %s, supported operations are create, update, delete", file.Operation, file.Options.treePath) diff --git a/services/repository/generate.go b/services/repository/generate.go index caf15265a00..3ec31dac225 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -230,8 +230,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r ) // Clone to temporary path and do the init commit. - templateRepoPath := templateRepo.RepoPath() - if err := git.Clone(ctx, templateRepoPath, tmpDir, git.CloneRepoOptions{ + if err := gitrepo.CloneRepoToLocal(ctx, templateRepo, tmpDir, git.CloneRepoOptions{ Depth: 1, Branch: templateRepo.DefaultBranch, }); err != nil { diff --git a/services/repository/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go index eda499840b3..83813e7ba76 100644 --- a/services/repository/gitgraph/graph_test.go +++ b/services/repository/gitgraph/graph_test.go @@ -238,8 +238,8 @@ func TestCommitStringParsing(t *testing.T) { for _, test := range tests { t.Run(test.testName, func(t *testing.T) { testString := fmt.Sprintf("%s%s", dataFirstPart, test.commitMessage) - idx := strings.Index(testString, "DATA:") - commit, err := NewCommit(0, 0, []byte(testString[idx+5:])) + _, after, _ := strings.Cut(testString, "DATA:") + commit, err := NewCommit(0, 0, []byte(after)) if err != nil && test.shouldPass { t.Errorf("Could not parse %s", testString) return diff --git a/services/repository/gitgraph/parser.go b/services/repository/gitgraph/parser.go index f6bf9b0b908..859deff1133 100644 --- a/services/repository/gitgraph/parser.go +++ b/services/repository/gitgraph/parser.go @@ -44,11 +44,11 @@ func (parser *Parser) Reset() { // AddLineToGraph adds the line as a row to the graph func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error { - idx := bytes.Index(line, []byte("DATA:")) - if idx < 0 { + before, after, ok := bytes.Cut(line, []byte("DATA:")) + if !ok { parser.ParseGlyphs(line) } else { - parser.ParseGlyphs(line[:idx]) + parser.ParseGlyphs(before) } var err error @@ -72,7 +72,7 @@ func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error { } } commitDone = true - if idx < 0 { + if !ok { if err != nil { err = fmt.Errorf("missing data section on line %d with commit: %s. %w", row, string(line), err) } else { @@ -80,7 +80,7 @@ func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error { } continue } - err2 := graph.AddCommit(row, column, flowID, line[idx+5:]) + err2 := graph.AddCommit(row, column, flowID, after) if err != nil && err2 != nil { err = fmt.Errorf("%v %w", err2, err) continue diff --git a/services/repository/merge_upstream.go b/services/repository/merge_upstream.go index 8d6f11372c4..692b8013035 100644 --- a/services/repository/merge_upstream.go +++ b/services/repository/merge_upstream.go @@ -11,6 +11,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/util" @@ -33,8 +34,7 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_ return "up-to-date", nil } - err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{ - Remote: repo.RepoPath(), + err = gitrepo.Push(ctx, repo.BaseRepo, repo, git.PushOptions{ Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch), Env: repo_module.PushingEnvironment(doer, repo), }) diff --git a/services/repository/migrate.go b/services/repository/migrate.go index acac6fd9ad6..8f515326ad6 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -74,8 +74,6 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts migration.MigrateOptions, httpTransport *http.Transport, ) (*repo_model.Repository, error) { - repoPath := repo.RepoPath() - if u.IsOrganization() { t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx) if err != nil { @@ -92,7 +90,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repo.FullName(), err) } - if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{ + if err := gitrepo.CloneExternalRepo(ctx, opts.CloneAddr, repo, git.CloneRepoOptions{ Mirror: true, Quiet: true, Timeout: migrateTimeout, @@ -104,7 +102,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("clone error: %w", err) } - if err := git.WriteCommitGraph(ctx, repoPath); err != nil { + if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil { return repo, err } diff --git a/services/repository/repository.go b/services/repository/repository.go index acc5ce56cf5..93fbcb51f75 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -345,3 +345,31 @@ func HasWiki(ctx context.Context, repo *repo_model.Repository) bool { } return hasWiki && err == nil } + +// CheckCreateRepository check if doer could create a repository in new owner +func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error { + if !doer.CanCreateRepoIn(owner) { + return repo_model.ErrReachLimitOfRepo{Limit: owner.MaxRepoCreation} + } + + if err := repo_model.IsUsableRepoName(name); err != nil { + return err + } + + has, err := repo_model.IsRepositoryModelExist(ctx, owner, name) + if err != nil { + return err + } else if has { + return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name} + } + repo := repo_model.StorageRepo(repo_model.RelativePath(owner.Name, name)) + isExist, err := gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.RelativePath(), err) + return err + } + if !overwriteOrAdopt && isExist { + return repo_model.ErrRepoFilesAlreadyExist{Uname: owner.Name, Name: name} + } + return nil +} diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 98307a447a1..af477fc7f13 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -90,6 +90,17 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d return nil } +// isRepositoryModelOrDirExist returns true if the repository with given name under user has already existed. +func isRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { + has, err := repo_model.IsRepositoryModelExist(ctx, u, repoName) + if err != nil { + return false, err + } + repo := repo_model.StorageRepo(repo_model.RelativePath(u.Name, repoName)) + isExist, err := gitrepo.IsRepositoryExist(ctx, repo) + return has || isExist, err +} + // transferOwnership transfers all corresponding repository items from old user to new one. func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository, teams []*organization.Team) (err error) { repoRenamed := false @@ -143,7 +154,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName newOwnerName = newOwner.Name // ensure capitalisation matches // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { + if has, err := isRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return repo_model.ErrRepoAlreadyExist{ @@ -345,7 +356,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR return err } - has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) + has, err := isRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { diff --git a/services/user/email.go b/services/user/email.go index 5c0de708e9a..c45b3b3ec9f 100644 --- a/services/user/email.go +++ b/services/user/email.go @@ -14,60 +14,6 @@ import ( "code.gitea.io/gitea/modules/util" ) -// AdminAddOrSetPrimaryEmailAddress is used by admins to add or set a user's primary email address -func AdminAddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error { - if strings.EqualFold(u.Email, emailStr) { - return nil - } - - if err := user_model.ValidateEmailForAdmin(emailStr); err != nil { - return err - } - - // Check if address exists already - email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) - if err != nil && !errors.Is(err, util.ErrNotExist) { - return err - } - if email != nil && email.UID != u.ID { - return user_model.ErrEmailAlreadyUsed{Email: emailStr} - } - - // Update old primary address - primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) - if err != nil { - return err - } - - primary.IsPrimary = false - if err := user_model.UpdateEmailAddress(ctx, primary); err != nil { - return err - } - - // Insert new or update existing address - if email != nil { - email.IsPrimary = true - email.IsActivated = true - if err := user_model.UpdateEmailAddress(ctx, email); err != nil { - return err - } - } else { - email = &user_model.EmailAddress{ - UID: u.ID, - Email: emailStr, - IsActivated: true, - IsPrimary: true, - } - if _, err := user_model.InsertEmailAddress(ctx, email); err != nil { - return err - } - } - - u.Email = emailStr - - return user_model.UpdateUserCols(ctx, u, "email") -} - func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error { if strings.EqualFold(u.Email, emailStr) { return nil @@ -77,43 +23,44 @@ func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailSt return err } - if !u.IsOrganization() { - // Check if address exists already - email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) - if err != nil && !errors.Is(err, util.ErrNotExist) { - return err - } - if email != nil { - if email.IsPrimary && email.UID == u.ID { - return nil + return db.WithTx(ctx, func(ctx context.Context) error { + if !u.IsOrganization() { + // Check if address exists already + email, err := user_model.GetEmailAddressByEmail(ctx, emailStr) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return err + } + if email != nil { + if email.IsPrimary && email.UID == u.ID { + return nil + } + return user_model.ErrEmailAlreadyUsed{Email: emailStr} + } + + // Remove old primary address + primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) + if err != nil { + return err + } + if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil { + return err + } + + // Insert new primary address + if _, err := user_model.InsertEmailAddress(ctx, &user_model.EmailAddress{ + UID: u.ID, + Email: emailStr, + IsActivated: true, + IsPrimary: true, + }); err != nil { + return err } - return user_model.ErrEmailAlreadyUsed{Email: emailStr} } - // Remove old primary address - primary, err := user_model.GetPrimaryEmailAddressOfUser(ctx, u.ID) - if err != nil { - return err - } - if _, err := db.DeleteByID[user_model.EmailAddress](ctx, primary.ID); err != nil { - return err - } + u.Email = emailStr - // Insert new primary address - email = &user_model.EmailAddress{ - UID: u.ID, - Email: emailStr, - IsActivated: true, - IsPrimary: true, - } - if _, err := user_model.InsertEmailAddress(ctx, email); err != nil { - return err - } - } - - u.Email = emailStr - - return user_model.UpdateUserCols(ctx, u, "email") + return user_model.UpdateUserCols(ctx, u, "email") + }) } func AddEmailAddresses(ctx context.Context, u *user_model.User, emails []string) error { diff --git a/services/user/email_test.go b/services/user/email_test.go index 76770a92301..a031b12cadc 100644 --- a/services/user/email_test.go +++ b/services/user/email_test.go @@ -9,61 +9,10 @@ import ( organization_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/glob" - "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) -func TestAdminAddOrSetPrimaryEmailAddress(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 27}) - - emails, err := user_model.GetEmailAddresses(t.Context(), user.ID) - assert.NoError(t, err) - assert.Len(t, emails, 1) - - primary, err := user_model.GetPrimaryEmailAddressOfUser(t.Context(), user.ID) - assert.NoError(t, err) - assert.NotEqual(t, "new-primary@example.com", primary.Email) - assert.Equal(t, user.Email, primary.Email) - - assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(t.Context(), user, "new-primary@example.com")) - - primary, err = user_model.GetPrimaryEmailAddressOfUser(t.Context(), user.ID) - assert.NoError(t, err) - assert.Equal(t, "new-primary@example.com", primary.Email) - assert.Equal(t, user.Email, primary.Email) - - emails, err = user_model.GetEmailAddresses(t.Context(), user.ID) - assert.NoError(t, err) - assert.Len(t, emails, 2) - - setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} - defer func() { - setting.Service.EmailDomainAllowList = []glob.Glob{} - }() - - assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(t.Context(), user, "new-primary2@example2.com")) - - primary, err = user_model.GetPrimaryEmailAddressOfUser(t.Context(), user.ID) - assert.NoError(t, err) - assert.Equal(t, "new-primary2@example2.com", primary.Email) - assert.Equal(t, user.Email, primary.Email) - - assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(t.Context(), user, "user27@example.com")) - - primary, err = user_model.GetPrimaryEmailAddressOfUser(t.Context(), user.ID) - assert.NoError(t, err) - assert.Equal(t, "user27@example.com", primary.Email) - assert.Equal(t, user.Email, primary.Email) - - emails, err = user_model.GetEmailAddresses(t.Context(), user.ID) - assert.NoError(t, err) - assert.Len(t, emails, 3) -} - func TestReplacePrimaryEmailAddress(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index a9dc7269829..5f74817ef3f 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -25,8 +25,6 @@ import ( repo_service "code.gitea.io/gitea/services/repository" ) -const DefaultRemote = "origin" - func getWikiWorkingLockKey(repoID int64) string { return fmt.Sprintf("wiki_working_%d", repoID) } @@ -135,7 +133,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model if hasDefaultBranch { if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) - return fmt.Errorf("fnable to read HEAD tree to index in: %s %w", basePath, err) + return fmt.Errorf("unable to read HEAD tree to index in: %s %w", basePath, err) } } @@ -214,8 +212,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return err } - if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{ - Remote: DefaultRemote, + if err := gitrepo.PushFromLocal(gitRepo.Ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{ Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch), Env: repo_module.FullPushingEnvironment( doer, @@ -333,8 +330,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return err } - if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{ - Remote: DefaultRemote, + if err := gitrepo.PushFromLocal(gitRepo.Ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{ Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch), Env: repo_module.FullPushingEnvironment( doer, diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl index b66206446cc..b4621dfa1c6 100644 --- a/templates/repo/diff/section_unified.tmpl +++ b/templates/repo/diff/section_unified.tmpl @@ -1,5 +1,5 @@ {{$file := .file}} -{{/* this tmpl is also used by the PR Conversation page, so the "AfterCommitID" and "DiffBlobExcerptData" may not exist */}} +{{/* this tmpl is also used by the PR Conversation page, so "DiffBlobExcerptData" may not exist */}} {{$diffBlobExcerptData := $.root.DiffBlobExcerptData}} @@ -12,10 +12,10 @@ {{range $k, $line := $section.Lines}} {{if eq .GetType 4}} - {{if $.root.AfterCommitID}} + {{if $diffBlobExcerptData}} {{$line.RenderBlobExcerptButtons $file.NameHash $diffBlobExcerptData}} {{else}} - {{/* for code file preview page or comment diffs on pull comment pages, do not show the expansion arrows */}} + {{/* when DiffBlobExcerptData is not available (code file preview, pull conversation diff comment), do not show the expansion arrows */}} {{end}} {{else}} diff --git a/templates/repo/editor/delete.tmpl b/templates/repo/editor/delete.tmpl index bf6143f1cb9..70769326a7c 100644 --- a/templates/repo/editor/delete.tmpl +++ b/templates/repo/editor/delete.tmpl @@ -1,13 +1,30 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
- {{.CsrfTokenHtml}} - {{template "repo/editor/common_top" .}} - {{template "repo/editor/commit_form" .}} -
+
+ {{template "repo/view_file_tree" .}} +
+
+ {{.CsrfTokenHtml}} + {{template "repo/editor/common_top" .}} +
+ {{/* although the UI isn't good enough, this header is necessary for the "left file tree view" toggle button, this button must exist */}} + {{template "repo/view_file_tree_toggle_button" .}} + {{/* then, to make the page looks overall good, add the breadcrumb here to make the toggle button can be shown in a text row, but not a single button*/}} + +
+ {{template "repo/editor/commit_form" .}} +
+
+
{{template "base/footer" .}} diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 0911d02e1f4..e6b9c557700 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -1,53 +1,59 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
+ {{template "repo/view_file_tree" .}} +
+ - {{.CsrfTokenHtml}} - {{template "repo/editor/common_top" .}} -
- {{template "repo/editor/common_breadcrumb" .}} + > + {{.CsrfTokenHtml}} + {{template "repo/editor/common_top" .}} +
+ {{template "repo/view_file_tree_toggle_button" .}} + {{template "repo/editor/common_breadcrumb" .}} +
+ {{if not .NotEditableReason}} + + {{else}} +
+
+

{{.NotEditableReason}}

+

{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}

+
+
+ {{end}} + {{template "repo/editor/commit_form" .}} +
- {{if not .NotEditableReason}} - - {{else}} -
-
-

{{.NotEditableReason}}

-

{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}

-
-
- {{end}} - {{template "repo/editor/commit_form" .}} - +
{{template "base/footer" .}} diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl index 3e36c77b3b9..847d6df88d7 100644 --- a/templates/repo/editor/upload.tmpl +++ b/templates/repo/editor/upload.tmpl @@ -1,19 +1,25 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
- {{.CsrfTokenHtml}} - {{template "repo/editor/common_top" .}} -
- {{template "repo/editor/common_breadcrumb" .}} +
+ {{template "repo/view_file_tree" .}} +
+ + {{.CsrfTokenHtml}} + {{template "repo/editor/common_top" .}} +
+ {{template "repo/view_file_tree_toggle_button" .}} + {{template "repo/editor/common_breadcrumb" .}} +
+
+ {{template "repo/upload" .}} +
+ {{template "repo/editor/commit_form" .}} +
-
- {{template "repo/upload" .}} -
- {{template "repo/editor/commit_form" .}} - +
{{template "base/footer" .}} diff --git a/templates/repo/find/files.tmpl b/templates/repo/find/files.tmpl deleted file mode 100644 index ce242796bec..00000000000 --- a/templates/repo/find/files.tmpl +++ /dev/null @@ -1,21 +0,0 @@ -{{template "base/head" .}} -
- {{template "repo/header" .}} -
-
- {{.RepoName}} - / -
- -
-
- - - -
-
-

{{ctx.Locale.Tr "repo.find_file.no_matching"}}

-
-
-
-{{template "base/footer" .}} diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl index f99fe2f57ab..99f2a7da7ee 100644 --- a/templates/repo/view.tmpl +++ b/templates/repo/view.tmpl @@ -17,9 +17,7 @@ {{template "repo/code/recently_pushed_new_branches" dict "RecentBranchesPromptData" .RecentBranchesPromptData}}
-
- {{template "repo/view_file_tree" .}} -
+ {{template "repo/view_file_tree" .}}
{{template "repo/view_content" .}}
diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 66e4fffcb9b..b31648fbbe7 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -5,11 +5,7 @@
{{if not $isTreePathRoot}} - + {{template "repo/view_file_tree_toggle_button" .}} {{end}} {{template "repo/branch_dropdown" dict @@ -37,31 +33,6 @@ {{end}} - - {{if $isTreePathRoot}} - {{ctx.Locale.Tr "repo.find_file.go_to_file"}} - {{end}} - - {{if and .RefFullName.IsBranch (not .IsViewFile)}} - - {{end}} - {{if and $isTreePathRoot .Repository.IsTemplate}} {{ctx.Locale.Tr "repo.use_template"}} @@ -86,12 +57,65 @@
+
+ + {{if .RefFullName.IsBranch}} + {{$addFilePath := .TreePath}} + {{if .IsViewFile}} + {{if gt (len .TreeNames) 1}} + {{$addFilePath = StringUtils.Join (slice .TreeNames 0 (Eval (len .TreeNames) "-" 1)) "/"}} + {{else}} + {{$addFilePath = ""}} + {{end}} + {{end}} +
+ + {{if and (not .IsViewFile) (not $isTreePathRoot)}} + + {{end}} + {{end}} {{if $isTreePathRoot}} {{template "repo/clone_panel" .}} {{end}} {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}} - + {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} {{end}} diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl index 8aed05f3469..f79fcc22aa3 100644 --- a/templates/repo/view_file_tree.tmpl +++ b/templates/repo/view_file_tree.tmpl @@ -1,15 +1,17 @@ -
- - {{ctx.Locale.Tr "files"}} -
+
+
+ + {{ctx.Locale.Tr "files"}} +
-{{/* TODO: Dynamically move components such as refSelector and createPR here */}} -
+ {{/* TODO: Dynamically move components such as refSelector and createPR here */}} +
+
diff --git a/templates/repo/view_file_tree_toggle_button.tmpl b/templates/repo/view_file_tree_toggle_button.tmpl new file mode 100644 index 00000000000..3d6ea928edd --- /dev/null +++ b/templates/repo/view_file_tree_toggle_button.tmpl @@ -0,0 +1,6 @@ + diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 145494aa1a9..61443ac4652 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -47,7 +47,7 @@ {{end}} {{end}}
-
+
{{if $commit}} {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink $.Repository}} diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl index fa3e6c6adee..62a42552aec 100644 --- a/templates/shared/combomarkdowneditor.tmpl +++ b/templates/shared/combomarkdowneditor.tmpl @@ -43,6 +43,7 @@
{{svg "octicon-bold"}} {{svg "octicon-italic"}} + {{svg "octicon-strikethrough"}}
{{svg "octicon-quote"}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0cefa6795f4..b37937dcee9 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -6750,6 +6750,66 @@ } } }, + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Update a branch reference to a new commit", + "operationId": "repoUpdateBranch", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the branch", + "name": "branch", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateBranchRepoOption" + } + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "409": { + "$ref": "#/responses/conflict" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + }, "delete": { "produces": [ "application/json" @@ -28702,6 +28762,31 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpdateBranchRepoOption": { + "description": "UpdateBranchRepoOption options when updating a branch reference in a repository", + "type": "object", + "required": [ + "new_commit_id" + ], + "properties": { + "force": { + "description": "Force update even if the change is not a fast-forward", + "type": "boolean", + "x-go-name": "Force" + }, + "new_commit_id": { + "description": "New commit SHA (or any ref) the branch should point to", + "type": "string", + "x-go-name": "NewCommitID" + }, + "old_commit_id": { + "description": "Expected old commit SHA of the branch; if provided it must match the current tip", + "type": "string", + "x-go-name": "OldCommitID" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "UpdateFileOptions": { "description": "UpdateFileOptions options for updating or creating a file\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)", "type": "object", diff --git a/tests/e2e/README.md b/tests/e2e/README.md index db083793d8b..ea3805ab95c 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -17,13 +17,7 @@ make clean frontend ## Install playwright system dependencies ``` -npx playwright install-deps -``` - - -## Run all tests via local act_runner -``` -act_runner exec -W ./.github/workflows/pull-e2e-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest +pnpm exec playwright install-deps ``` ## Run sqlite e2e tests @@ -85,8 +79,8 @@ TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TES Although the main goal of e2e is assertion testing, we have added a framework for visual regress testing. If you are working on front-end features, please use the following: - Check out `main`, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1` to generate outputs. This will initially fail, as no screenshots exist. You can run the e2e tests again to assert it passes. - - Check out your branch, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1`. You should be able to assert you front-end changes don't break any other tests unintentionally. + - Check out your branch, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1`. You should be able to assert you front-end changes don't break any other tests unintentionally. -VISUAL_TEST=1 will create screenshots in tests/e2e/test-snapshots. The test will fail the first time this is enabled (until we get visual test image persistence figured out), because it will be testing against an empty screenshot folder. +VISUAL_TEST=1 will create screenshots in tests/e2e/test-snapshots. The test will fail the first time this is enabled (until we get visual test image persistence figured out), because it will be testing against an empty screenshot folder. ACCEPT_VISUAL=1 will overwrite the snapshot images with new images. diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index d5486b6a392..9dc0ddb9df8 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + webhook_module "code.gitea.io/gitea/modules/webhook" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" @@ -1595,3 +1596,56 @@ jobs: assert.NotNil(t, run) }) } + +func TestPullRequestWithPathsRebase(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + repoName := "actions-pr-paths-rebase" + apiRepo := createActionsTestRepo(t, token, repoName, false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + apiCtx := NewAPITestContext(t, "user2", repoName, auth_model.AccessTokenScopeWriteRepository) + runner := newMockRunner() + runner.registerAsRepoRunner(t, "user2", repoName, "mock-runner", []string{"ubuntu-latest"}, false) + + // init files and dirs + testCreateFile(t, session, "user2", repoName, repo.DefaultBranch, "", "dir1/dir1.txt", "1") + testCreateFile(t, session, "user2", repoName, repo.DefaultBranch, "", "dir2/dir2.txt", "2") + wfFileContent := `name: ci +on: + pull_request: + paths: + - 'dir1/**' +jobs: + ci-job: + runs-on: ubuntu-latest + steps: + - run: echo 'ci' +` + testCreateFile(t, session, "user2", repoName, repo.DefaultBranch, "", ".gitea/workflows/ci.yml", wfFileContent) + + // create a PR to modify "dir1/dir1.txt", the workflow will be triggered + testEditFileToNewBranch(t, session, "user2", repoName, repo.DefaultBranch, "update-dir1", "dir1/dir1.txt", "11") + _, err := doAPICreatePullRequest(apiCtx, "user2", repoName, repo.DefaultBranch, "update-dir1")(t) + assert.NoError(t, err) + pr1Task := runner.fetchTask(t) + _, _, pr1Run := getTaskAndJobAndRunByTaskID(t, pr1Task.Id) + assert.Equal(t, webhook_module.HookEventPullRequest, pr1Run.Event) + + // create a PR to modify "dir2/dir2.txt" then update main branch and rebase, the workflow will not be triggered + testEditFileToNewBranch(t, session, "user2", repoName, repo.DefaultBranch, "update-dir2", "dir2/dir2.txt", "22") + apiPull, err := doAPICreatePullRequest(apiCtx, "user2", repoName, repo.DefaultBranch, "update-dir2")(t) + runner.fetchNoTask(t) + assert.NoError(t, err) + testEditFile(t, session, "user2", repoName, repo.DefaultBranch, "dir1/dir1.txt", "11") // change the file in "dir1" + req := NewRequestWithValues(t, "POST", + fmt.Sprintf("/%s/%s/pulls/%d/update?style=rebase", "user2", repoName, apiPull.Index), // update by rebase + map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusSeeOther) + runner.fetchNoTask(t) + }) +} diff --git a/tests/integration/api_actions_permission_test.go b/tests/integration/api_actions_permission_test.go new file mode 100644 index 00000000000..072e2635a96 --- /dev/null +++ b/tests/integration/api_actions_permission_test.go @@ -0,0 +1,54 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testActionUserSignIn(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/user"). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + resp := MakeRequest(t, req, http.StatusOK) + + var u api.User + DecodeJSON(t, resp, &u) + assert.Equal(t, "gitea-actions", u.UserName) +} + +func testActionUserAccessPublicRepo(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/raw/README.md"). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) + + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + + req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/raw/README.md"). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) +} + +func testActionUserNoAccessOtherPrivateRepo(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo2/raw/README.md"). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + MakeRequest(t, req, http.StatusNotFound) +} + +func TestActionUserAccessPermission(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("ActionUserSignIn", testActionUserSignIn) + t.Run("ActionUserAccessPublicRepo", testActionUserAccessPublicRepo) + t.Run("ActionUserNoAccessOtherPrivateRepo", testActionUserNoAccessOtherPrivateRepo) +} diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index dbd62c4078b..763d4d526b3 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -382,10 +382,12 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { SourceID: 0, Email: &newEmail, }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning")) + resp := MakeRequest(t, req, http.StatusBadRequest) + errMap := make(map[string]string) + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &errMap)) + assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", errMap["message"]) - originalEmail := "user2@example.com" + originalEmail := "user2@example.org" req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ LoginName: "user2", SourceID: 0, diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 2147ef9d0d9..043aa10c7fb 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -4,6 +4,8 @@ package integration import ( + "encoding/base64" + "fmt" "net/http" "net/http/httptest" "net/url" @@ -243,6 +245,79 @@ func TestAPIRenameBranch(t *testing.T) { }) } +func TestAPIUpdateBranchReference(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + ctx := NewAPITestContext(t, "user2", "update-branch", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + giteaURL.Path = ctx.GitPath() + + var defaultBranch string + t.Run("CreateRepo", doAPICreateRepository(ctx, false, func(t *testing.T, repo api.Repository) { + defaultBranch = repo.DefaultBranch + })) + + createBranchReq := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/branches", ctx.Username, ctx.Reponame), &api.CreateBranchRepoOption{ + BranchName: "feature", + OldRefName: defaultBranch, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, createBranchReq, http.StatusCreated) + + var featureInitialCommit string + t.Run("LoadFeatureBranch", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { + featureInitialCommit = branch.Commit.ID + assert.NotEmpty(t, featureInitialCommit) + })) + + content := base64.StdEncoding.EncodeToString([]byte("branch update test")) + var newCommit string + doAPICreateFile(ctx, "docs/update.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: defaultBranch, + NewBranchName: defaultBranch, + Message: "add docs/update.txt", + }, + ContentBase64: content, + }, func(t *testing.T, resp api.FileResponse) { + newCommit = resp.Commit.SHA + assert.NotEmpty(t, newCommit) + })(t) + + updateReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: newCommit, + OldCommitID: featureInitialCommit, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, updateReq, http.StatusNoContent) + + t.Run("FastForwardApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { + assert.Equal(t, newCommit, branch.Commit.ID) + })) + + staleReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: newCommit, + OldCommitID: featureInitialCommit, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, staleReq, http.StatusUnprocessableEntity) + + nonFFReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: featureInitialCommit, + OldCommitID: newCommit, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, nonFFReq, http.StatusUnprocessableEntity) + + forceReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: featureInitialCommit, + OldCommitID: newCommit, + Force: true, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, forceReq, http.StatusNoContent) + + t.Run("ForceApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { + assert.Equal(t, featureInitialCommit, branch.Commit.ID) + })) + }) +} + func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder { token := getUserToken(t, doerName, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{ diff --git a/tests/integration/api_repo_file_helpers.go b/tests/integration/api_repo_file_helpers.go index f8d6fc803f1..9a4c4486646 100644 --- a/tests/integration/api_repo_file_helpers.go +++ b/tests/integration/api_repo_file_helpers.go @@ -5,6 +5,7 @@ package integration import ( "context" + "errors" "strings" "testing" @@ -72,7 +73,7 @@ func deleteFileInBranch(user *user_model.User, repo *repo_model.Repository, tree func createOrReplaceFileInBranch(user *user_model.User, repo *repo_model.Repository, treePath, branchName, content string) error { _, err := deleteFileInBranch(user, repo, treePath, branchName) - if err != nil && !files_service.IsErrRepoFileDoesNotExist(err) { + if err != nil && !errors.Is(err, util.ErrNotExist) { return err } diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go index 44aaee09f89..18efde72142 100644 --- a/tests/integration/attachment_test.go +++ b/tests/integration/attachment_test.go @@ -7,17 +7,23 @@ import ( "bytes" "image" "image/png" + "io/fs" "mime/multipart" "net/http" + "os" "strings" "testing" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/web" + route_web "code.gitea.io/gitea/routers/web" + "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testGeneratePngBytes() []byte { @@ -52,14 +58,38 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, csrf, repoURL return obj["uuid"] } -func TestCreateAnonymousAttachment(t *testing.T) { +func TestAttachments(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment) + t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment) + t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp) + t.Run("GetAttachment", testGetAttachment) +} + +func testUploadAttachmentDeleteTemp(t *testing.T) { + session := loginUser(t, "user2") + countTmpFile := func() int { + // TODO: GOLANG-HTTP-TMPDIR: Golang saves the uploaded file to os.TempDir() when it exceeds the max memory limit. + files, err := fs.Glob(os.DirFS(os.TempDir()), "multipart-*") //nolint:usetesting // Golang's "http" package's behavior + require.NoError(t, err) + return len(files) + } + var tmpFileCountDuringUpload int + defer test.MockVariableValue(&context.ParseMultipartFormMaxMemory, 1)() + defer web.RouteMock(route_web.RouterMockPointBeforeWebRoutes, func(resp http.ResponseWriter, req *http.Request) { + tmpFileCountDuringUpload = countTmpFile() + })() + _ = testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK) + assert.Equal(t, 1, tmpFileCountDuringUpload, "the temp file should exist when uploaded size exceeds the parse form's max memory") + assert.Equal(t, 0, countTmpFile(), "the temp file should be deleted after upload") +} + +func testCreateAnonymousAttachment(t *testing.T) { session := emptyTestSession(t) testCreateIssueAttachment(t, session, GetAnonymousCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther) } -func TestCreateIssueAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testCreateUser2IssueAttachment(t *testing.T) { const repoURL = "user2/repo1" session := loginUser(t, "user2") uuid := testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), repoURL, "image.png", testGeneratePngBytes(), http.StatusOK) @@ -90,8 +120,7 @@ func TestCreateIssueAttachment(t *testing.T) { MakeRequest(t, req, http.StatusOK) } -func TestGetAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testGetAttachment(t *testing.T) { adminSession := loginUser(t, "user1") user2Session := loginUser(t, "user2") user8Session := loginUser(t, "user8") diff --git a/tests/integration/git_ssh_redirect_test.go b/tests/integration/git_ssh_redirect_test.go index 5e35ed2a744..3ae2652412c 100644 --- a/tests/integration/git_ssh_redirect_test.go +++ b/tests/integration/git_ssh_redirect_test.go @@ -6,9 +6,13 @@ package integration import ( "fmt" "net/url" + "os" "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" ) func TestGitSSHRedirect(t *testing.T) { @@ -16,7 +20,8 @@ func TestGitSSHRedirect(t *testing.T) { } func testGitSSHRedirect(t *testing.T, u *url.URL) { - apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) + session := loginUser(t, "user2") withKeyFile(t, "my-testing-key", func(keyFile string) { t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile)) @@ -38,5 +43,39 @@ func testGitSSHRedirect(t *testing.T, u *url.URL) { t.Run("Clone", doGitClone(t.TempDir(), cloneURL)) }) } + + doAPICreateOrganization(apiTestContext, &structs.CreateOrgOption{ + UserName: "olduser2", + FullName: "Old User2", + })(t) + + cloneURL := createSSHUrl("olduser2/repo1.git", u) + t.Run("Clone Should Fail", doGitCloneFail(cloneURL)) + + doAPICreateOrganizationRepository(apiTestContext, "olduser2", &structs.CreateRepoOption{ + Name: "repo1", + AutoInit: true, + })(t) + testEditFile(t, session, "olduser2", "repo1", "master", "README.md", "This is olduser2's repo1\n") + + dstDir := t.TempDir() + t.Run("Clone", doGitClone(dstDir, cloneURL)) + readMEContent, err := os.ReadFile(dstDir + "/README.md") + assert.NoError(t, err) + assert.Equal(t, "This is olduser2's repo1\n", string(readMEContent)) + + apiTestContext2 := NewAPITestContext(t, "user2", "oldrepo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) + doAPICreateRepository(apiTestContext2, false)(t) + testEditFile(t, session, "user2", "oldrepo1", "master", "README.md", "This is user2's oldrepo1\n") + + dstDir = t.TempDir() + cloneURL = createSSHUrl("user2/oldrepo1.git", u) + t.Run("Clone", doGitClone(dstDir, cloneURL)) + readMEContent, err = os.ReadFile(dstDir + "/README.md") + assert.NoError(t, err) + assert.Equal(t, "This is user2's oldrepo1\n", string(readMEContent)) + + cloneURL = createSSHUrl("olduser2/oldrepo1.git", u) + t.Run("Clone Should Fail", doGitCloneFail(cloneURL)) }) } diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index 6821f8bf611..6fd42401c52 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -5,6 +5,7 @@ package integration import ( "fmt" + "net/http" "net/url" "path" "strings" @@ -12,7 +13,6 @@ import ( "time" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" @@ -22,6 +22,7 @@ import ( files_service "code.gitea.io/gitea/services/repository/files" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func getCreateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions { @@ -93,55 +94,6 @@ func getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service } } -func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions { - return &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "delete", - TreePath: "README.md", - SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", - }, - }, - LastCommitID: "", - OldBranch: repo.DefaultBranch, - NewBranch: repo.DefaultBranch, - Message: "Deletes README.md", - Author: &files_service.IdentityOptions{ - GitUserName: "Bob Smith", - GitUserEmail: "bob@smith.com", - }, - Committer: nil, - } -} - -func getExpectedFileResponseForRepoFilesDelete() *api.FileResponse { - // Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined - return &api.FileResponse{ - Content: nil, - Commit: &api.FileCommitResponse{ - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Bob Smith", - Email: "bob@smith.com", - }, - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Bob Smith", - Email: "bob@smith.com", - }, - }, - Message: "Deletes README.md\n", - }, - Verification: &api.PayloadCommitVerification{ - Verified: false, - Reason: "gpg.error.not_signed_commit", - Signature: "", - Payload: "", - }, - } -} - func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.Commit) *api.FileResponse { treePath := "new/file.txt" encoding := "base64" @@ -578,75 +530,88 @@ func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { } func TestChangeRepoFilesForDelete(t *testing.T) { - onGiteaRun(t, testDeleteRepoFiles) -} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx, _ := contexttest.MockContext(t, "user2/repo1") + ctx.SetPathParam("id", "1") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository + doer := ctx.Doer -func testDeleteRepoFiles(t *testing.T, u *url.URL) { - // setup - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "user2/repo1") - ctx.SetPathParam("id", "1") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadGitRepo(t, ctx) - defer ctx.Repo.GitRepo.Close() - repo := ctx.Repo.Repository - doer := ctx.Doer - opts := getDeleteRepoFilesOptions(repo) + t.Run("Delete README.md by commit", func(t *testing.T) { + urlRaw := "/user2/repo1/raw/branch/branch2/README.md" + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK) + opts := &files_service.ChangeRepoFilesOptions{ + OldBranch: "branch2", + LastCommitID: "985f0301dba5e7b34be866819cd15ad3d8f508ee", + Files: []*files_service.ChangeRepoFile{ + { + Operation: "delete", + TreePath: "README.md", + }, + }, + Message: "test message", + } + filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) + require.NoError(t, err) + assert.NotNil(t, filesResponse) + assert.Nil(t, filesResponse.Files[0]) + assert.Equal(t, "test message\n", filesResponse.Commit.Message) + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound) + }) - t.Run("Delete README.md file", func(t *testing.T) { - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() - assert.NotNil(t, filesResponse) - assert.Nil(t, filesResponse.Files[0]) - assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) - }) + t.Run("Delete README.md with options", func(t *testing.T) { + urlRaw := "/user2/repo1/raw/branch/master/README.md" + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK) + opts := &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "delete", + TreePath: "README.md", + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + }, + }, + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + Message: "Message for deleting README.md", + Author: &files_service.IdentityOptions{GitUserName: "Bob Smith", GitUserEmail: "bob@smith.com"}, + } + filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) + require.NoError(t, err) + require.NotNil(t, filesResponse) + assert.Nil(t, filesResponse.Files[0]) + assert.Equal(t, "Message for deleting README.md\n", filesResponse.Commit.Message) + assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "bob@smith.com"}, filesResponse.Commit.Author.Identity) + assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "bob@smith.com"}, filesResponse.Commit.Committer.Identity) + assert.Equal(t, &api.PayloadCommitVerification{Reason: "gpg.error.not_signed_commit"}, filesResponse.Verification) + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound) + }) - t.Run("Verify README.md has been deleted", func(t *testing.T) { - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - assert.Nil(t, filesResponse) - expectedError := "repository file does not exist [path: " + opts.Files[0].TreePath + "]" - assert.EqualError(t, err, expectedError) - }) -} - -// Test opts with branch names removed, same results -func TestChangeRepoFilesForDeleteWithoutBranchNames(t *testing.T) { - onGiteaRun(t, testDeleteRepoFilesWithoutBranchNames) -} - -func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) { - // setup - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "user2/repo1") - ctx.SetPathParam("id", "1") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadGitRepo(t, ctx) - defer ctx.Repo.GitRepo.Close() - - repo := ctx.Repo.Repository - doer := ctx.Doer - opts := getDeleteRepoFilesOptions(repo) - opts.OldBranch = "" - opts.NewBranch = "" - - t.Run("Delete README.md without Branch Name", func(t *testing.T) { - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() - assert.NotNil(t, filesResponse) - assert.Nil(t, filesResponse.Files[0]) - assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) + t.Run("Delete directory", func(t *testing.T) { + urlRaw := "/user2/repo1/raw/branch/sub-home-md-img-check/docs/README.md" + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK) + opts := &files_service.ChangeRepoFilesOptions{ + OldBranch: "sub-home-md-img-check", + LastCommitID: "4649299398e4d39a5c09eb4f534df6f1e1eb87cc", + Files: []*files_service.ChangeRepoFile{ + { + Operation: "delete", + TreePath: "docs", + DeleteRecursively: true, + }, + }, + Message: "test message", + } + filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) + require.NoError(t, err) + assert.NotNil(t, filesResponse) + assert.Nil(t, filesResponse.Files[0]) + assert.Equal(t, "test message\n", filesResponse.Commit.Message) + MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound) + }) }) } diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh deleted file mode 100755 index 2cd26ca6fe4..00000000000 --- a/tools/lint-go-gopls.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -set -uo pipefail - -cd "$(dirname -- "${BASH_SOURCE[0]}")" && cd .. - -IGNORE_PATTERNS=( - "is deprecated" # TODO: fix these -) - -# lint all go files with 'gopls check' and look for lines starting with the -# current absolute path, indicating a error was found. This is necessary -# because the tool does not set non-zero exit code when errors are found. -# ref: https://github.com/golang/go/issues/67078 -ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check -severity=warning "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}")); -NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l) - -if [ "$NUM_ERRORS" -eq "0" ]; then - exit 0; -else - echo "$ERROR_LINES" - echo "Found $NUM_ERRORS 'gopls check' errors" - exit 1; -fi diff --git a/tsconfig.json b/tsconfig.json index 1daf4b7233b..2466faf592c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -40,7 +40,7 @@ "strictBindCallApply": true, "strictBuiltinIteratorReturn": true, "strictFunctionTypes": true, - "strictNullChecks": false, + "strictNullChecks": true, "stripInternal": true, "verbatimModuleSyntax": true, "types": [ diff --git a/web_src/css/editor/fileeditor.css b/web_src/css/editor/fileeditor.css index 698efffc992..12ae97a1094 100644 --- a/web_src/css/editor/fileeditor.css +++ b/web_src/css/editor/fileeditor.css @@ -1,23 +1,3 @@ -.repository.file.editor .tab[data-tab="write"] { - padding: 0 !important; -} - -.repository.file.editor .tab[data-tab="write"] .editor-toolbar { - border: 0 !important; -} - -.repository.file.editor .tab[data-tab="write"] .CodeMirror { - border-left: 0; - border-right: 0; - border-bottom: 0; -} - -.repo-editor-header { - display: flex; - margin: 1rem 0; - padding: 3px 0; -} - .editor-toolbar { border-color: var(--color-secondary); } diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 779339c46b3..aedf53569a9 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -28,7 +28,7 @@ aspect-ratio: 1; transform: translate(-50%, -50%); animation: isloadingspin 1000ms infinite linear; - border-width: 4px; + border-width: 3px; border-style: solid; border-color: var(--color-secondary) var(--color-secondary) var(--color-secondary-dark-8) var(--color-secondary-dark-8); border-radius: var(--border-radius-full); diff --git a/web_src/css/modules/svg.css b/web_src/css/modules/svg.css index 738ec22cd34..e32fa0911f1 100644 --- a/web_src/css/modules/svg.css +++ b/web_src/css/modules/svg.css @@ -1,17 +1,24 @@ -.svg { +/* some material icons have "fill=none" (e.g.: ".txt -> document"), so the CSS styles shouldn't overwrite it, + and material icons should have no "fill" set explicitly, otherwise some like ".editorconfig" won't render correctly */ +.svg:not(.git-entry-icon) { display: inline-block; vertical-align: text-top; fill: currentcolor; } -.svg.git-entry-icon { - fill: transparent; /* some material icons have dark background fill, so need to reset */ -} - .middle .svg { vertical-align: middle; } +/* some browsers like Chrome have a bug: when a SVG is in a "display: none" container and referenced + somewhere else by ``, it won't be rendered correctly. e.g.: ".kts -> kotlin" */ +.svg-icon-container { + position: absolute; + width: 0; + height: 0; + overflow: hidden; +} + /* prevent SVGs from shrinking, like in space-starved flexboxes. the sizes here are cherry-picked for our use cases, feel free to add more. after https://developer.mozilla.org/en-US/docs/Web/CSS/attr#type-or-unit is diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 2b2dd8460d0..73043a0670b 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -150,63 +150,68 @@ td .commit-summary { } } -.repository.file.list .non-diff-file-content .header .icon { +.non-diff-file-content .header .icon { font-size: 1em; } -.repository.file.list .non-diff-file-content .header .small.icon { +.non-diff-file-content .header .small.icon { font-size: 0.75em; } -.repository.file.list .non-diff-file-content .header .tiny.icon { +.non-diff-file-content .header .tiny.icon { font-size: 0.5em; } -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon { +.non-diff-file-content .header .file-actions .btn-octicon { line-height: var(--line-height-default); padding: 8px; vertical-align: middle; color: var(--color-text); } -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon:hover { +.non-diff-file-content .header .file-actions .btn-octicon:hover { color: var(--color-primary); } -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon-danger:hover { +.non-diff-file-content .header .file-actions .btn-octicon-danger:hover { color: var(--color-red); } -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon.disabled { +.non-diff-file-content .header .file-actions .btn-octicon.disabled { color: inherit; opacity: var(--opacity-disabled); cursor: default; } -.repository.file.list .non-diff-file-content .plain-text { +.non-diff-file-content .plain-text { padding: 1em 2em; } -.repository.file.list .non-diff-file-content .plain-text pre { +.non-diff-file-content .plain-text pre { overflow-wrap: anywhere; white-space: pre-wrap; } -.repository.file.list .non-diff-file-content .csv { +.non-diff-file-content .csv { overflow-x: auto; padding: 0 !important; } -.repository.file.list .non-diff-file-content pre { +.non-diff-file-content pre { overflow: auto; } -.repository.file.list .non-diff-file-content .asciicast { +.non-diff-file-content .asciicast { padding: 0 !important; } .repo-editor-header { + display: flex; + margin: 1rem 0; + padding: 3px 0; width: 100%; + gap: 0.5em; + align-items: center; } .repo-editor-header input { @@ -216,17 +221,13 @@ td .commit-summary { margin-right: 5px !important; } -.repository.file.editor .tabular.menu .svg { - margin-right: 5px; -} - .repository.file.editor .commit-form-wrapper { - padding-left: 48px; + padding-left: 58px; } .repository.file.editor .commit-form-wrapper .commit-avatar { float: left; - margin-left: -48px; + margin-left: -58px; } .repository.file.editor .commit-form-wrapper .commit-form { @@ -1443,12 +1444,25 @@ td .commit-summary { flex-grow: 1; } -.repo-button-row .ui.button { +.repo-button-row .ui.button, +.repo-view-container .ui.button.repo-view-file-tree-toggle { flex-shrink: 0; margin: 0; min-height: 30px; } +.repo-view-container .ui.button.repo-view-file-tree-toggle { + padding: 0 6px; +} + +.repo-button-row .repo-file-search-container .ui.input { + height: 30px; +} + +.repo-button-row .ui.dropdown > .menu { + margin-top: 4px; +} + tbody.commit-list { vertical-align: baseline; } @@ -1517,6 +1531,12 @@ tbody.commit-list { line-height: initial; } +.commit-body a.commit code, +.commit-summary a.commit code { + /* these links are generated by the render: ... */ + background: inherit; +} + .git-notes.top { text-align: left; } diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index ee371f1b1c9..60bf1f17f94 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -54,7 +54,9 @@ gap: var(--page-spacing); } -.repo-view-container .repo-view-file-tree-container { +.repo-view-file-tree-container { + display: flex; + flex-direction: column; flex: 0 0 15%; min-width: 0; max-height: 100vh; @@ -65,6 +67,12 @@ overflow-y: hidden; } +@media (max-width: 767.98px) { + .repo-view-file-tree-container { + display: none; + } +} + .repo-view-content { flex: 1; min-width: 0; diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index 1718a1f06b7..f89752dc791 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -244,6 +244,7 @@ gitea-theme-meta-info { --color-highlight-fg: #87651e; --color-highlight-bg: #352c1c; --color-overlay-backdrop: #080808c0; + --color-danger: var(--color-red); accent-color: var(--color-accent); color-scheme: dark; } diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index db54f5e5fbf..1261ef8be03 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -244,6 +244,7 @@ gitea-theme-meta-info { --color-highlight-fg: #eed200; --color-highlight-bg: #fffbdd; --color-overlay-backdrop: #080808c0; + --color-danger: var(--color-red); accent-color: var(--color-accent); color-scheme: light; } diff --git a/web_src/js/bootstrap.ts b/web_src/js/bootstrap.ts index 4d3f39f5bfa..a94e1d66b0c 100644 --- a/web_src/js/bootstrap.ts +++ b/web_src/js/bootstrap.ts @@ -35,7 +35,7 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1; msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact); msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString()); - msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : ''); + msgDiv.querySelector('.ui.message')!.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : ''); msgContainer.prepend(msgDiv); } diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index d805817630c..7c7e0cd94ca 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -5,7 +5,7 @@ import {onMounted, shallowRef} from 'vue'; import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap'; defineProps<{ - values?: HeatmapValue[]; + values: HeatmapValue[]; locale: { textTotalContributions: string; heatMapLocale: Partial; @@ -28,7 +28,7 @@ const endDate = shallowRef(new Date()); onMounted(() => { // work around issue with first legend color being rendered twice and legend cut off - const legend = document.querySelector('.vch__external-legend-wrapper'); + const legend = document.querySelector('.vch__external-legend-wrapper')!; legend.setAttribute('viewBox', '12 0 80 10'); legend.style.marginRight = '-12px'; }); diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index 31db902adce..733144aae1a 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -11,15 +11,17 @@ const props = defineProps<{ }>(); const loading = shallowRef(false); -const issue = shallowRef(null); +const issue = shallowRef(null); const renderedLabels = shallowRef(''); const errorMessage = shallowRef(''); const createdAt = computed(() => { + if (!issue?.value) return ''; return new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}); }); const body = computed(() => { + if (!issue?.value) return ''; const body = issue.value.body.replace(/\n+/g, ' '); return body.length > 85 ? `${body.substring(0, 85)}…` : body; }); diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index e938814ec6c..e1f8475ea83 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -110,9 +110,9 @@ export default defineComponent({ }, mounted() { - const el = document.querySelector('#dashboard-repo-list'); + const el = document.querySelector('#dashboard-repo-list')!; this.changeReposFilter(this.reposFilter); - fomanticQuery(el.querySelector('.ui.dropdown')).dropdown(); + fomanticQuery(el.querySelector('.ui.dropdown')!).dropdown(); this.textArchivedFilterTitles = { 'archived': this.textShowOnlyArchived, diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue index e9aa3c67442..fcc7af1fa08 100644 --- a/web_src/js/components/DiffCommitSelector.vue +++ b/web_src/js/components/DiffCommitSelector.vue @@ -23,7 +23,7 @@ type CommitListResult = { export default defineComponent({ components: {SvgIcon}, data: () => { - const el = document.querySelector('#diff-commit-select'); + const el = document.querySelector('#diff-commit-select')!; return { menuVisible: false, isLoading: false, @@ -35,7 +35,7 @@ export default defineComponent({ mergeBase: el.getAttribute('data-merge-base'), commits: [] as Array, hoverActivated: false, - lastReviewCommitSha: '', + lastReviewCommitSha: '' as string | null, uniqueIdMenu: generateElemId('diff-commit-selector-menu-'), uniqueIdShowAll: generateElemId('diff-commit-selector-show-all-'), }; @@ -165,7 +165,7 @@ export default defineComponent({ }, /** Called when user clicks on since last review */ changesSinceLastReviewClick() { - window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`); + window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1)!.id}${this.queryParams}`); }, /** Clicking on a single commit opens this specific commit */ commitClicked(commitId: string, newWindow = false) { @@ -193,7 +193,7 @@ export default defineComponent({ // find all selected commits and generate a link const firstSelected = this.commits.findIndex((x) => x.selected); const lastSelected = this.commits.findLastIndex((x) => x.selected); - let beforeCommitID: string; + let beforeCommitID: string | null = null; if (firstSelected === 0) { beforeCommitID = this.mergeBase; } else { @@ -204,7 +204,7 @@ export default defineComponent({ if (firstSelected === lastSelected) { // if the start and end are the same, we show this single commit window.location.assign(`${this.issueLink}/commits/${afterCommitID}${this.queryParams}`); - } else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1).id) { + } else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1)!.id) { // if the first commit is selected and the last commit is selected, we show all commits window.location.assign(`${this.issueLink}/files${this.queryParams}`); } else { diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index 981d10c1c16..e2934b967eb 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -12,14 +12,14 @@ const store = diffTreeStore(); onMounted(() => { // Default to true if unset store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false'; - document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', toggleVisibility); + document.querySelector('.diff-toggle-file-tree-button')!.addEventListener('click', toggleVisibility); hashChangeListener(); window.addEventListener('hashchange', hashChangeListener); }); onUnmounted(() => { - document.querySelector('.diff-toggle-file-tree-button').removeEventListener('click', toggleVisibility); + document.querySelector('.diff-toggle-file-tree-button')!.removeEventListener('click', toggleVisibility); window.removeEventListener('hashchange', hashChangeListener); }); @@ -33,7 +33,7 @@ function expandSelectedFile() { if (store.selectedItem) { const box = document.querySelector(store.selectedItem); const folded = box?.getAttribute('data-folded') === 'true'; - if (folded) setFileFolding(box, box.querySelector('.fold-file'), false); + if (folded) setFileFolding(box, box.querySelector('.fold-file')!, false); } } @@ -48,10 +48,10 @@ function updateVisibility(visible: boolean) { } function updateState(visible: boolean) { - const btn = document.querySelector('.diff-toggle-file-tree-button'); + const btn = document.querySelector('.diff-toggle-file-tree-button')!; const [toShow, toHide] = btn.querySelectorAll('.icon'); - const tree = document.querySelector('#diff-file-tree'); - const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text'); + const tree = document.querySelector('#diff-file-tree')!; + const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text')!; btn.setAttribute('data-tooltip-content', newTooltip); toggleElem(tree, visible); toggleElem(toShow, !visible); diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 00748ee9bb2..357a2ba10ee 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -402,7 +402,7 @@ export default defineComponent({ } // auto-scroll to the last log line of the last step - let autoScrollJobStepElement: HTMLElement; + let autoScrollJobStepElement: HTMLElement | undefined; for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) { if (!autoScrollStepIndexes.get(stepIndex)) continue; autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex); @@ -468,7 +468,7 @@ export default defineComponent({ } const logLine = this.elStepsContainer().querySelector(selectedLogStep); if (!logLine) return; - logLine.querySelector('.line-num').click(); + logLine.querySelector('.line-num')!.click(); }, }, }); diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 5a925f99431..1d04fa52397 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -1,7 +1,7 @@ + + + + diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 1f90f925867..dc131383f62 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -1,9 +1,9 @@