diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 28fa9e4555..9d8f54ee13 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "ghcr.io/devcontainers/features/node:1": { "version": "22" }, - "ghcr.io/devcontainers/features/git-lfs:1.2.4": {}, + "ghcr.io/devcontainers/features/git-lfs:1.2.3": {}, "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { diff --git a/.forgejo/workflows-composite/apt-install-from/action.yaml b/.forgejo/workflows-composite/apt-install-from/action.yaml index c1c8c950e3..615e7cb184 100644 --- a/.forgejo/workflows-composite/apt-install-from/action.yaml +++ b/.forgejo/workflows-composite/apt-install-from/action.yaml @@ -18,7 +18,7 @@ runs: - name: install packages run: | apt-get update -qq - apt-get -q install --allow-downgrades -qq -y ${PACKAGES} + apt-get -q install -qq -y ${PACKAGES} env: PACKAGES: ${{inputs.packages}} - name: remove temporary package list to prevent using it in other steps diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml deleted file mode 100644 index 9e5cfb92ed..0000000000 --- a/.forgejo/workflows/testing-integration.yml +++ /dev/null @@ -1,71 +0,0 @@ -# -# Additional integration tests designed to run once a day when -# `mirror.yml` pushes to https://codeberg.org/forgejo-integration/forgejo -# and send a notification via email should they fail. -# -# For debug purposes: -# -# - uncomment [on].pull_request -# - swap 'forgejo-integration' and 'forgejo-coding' -# - open a pull request at https://codeberg.org/forgejo/forgejo and fix things -# - swap 'forgejo-integration' and 'forgejo-coding' -# - comment [on].pull_request -# - -name: testing-integration - -on: -# pull_request: - push: - tags: 'v[0-9]+.[0-9]+.*' - branches: - - 'forgejo' - - 'v*/forgejo' - -jobs: - test-unit: -# if: vars.ROLE == 'forgejo-coding' - if: vars.ROLE == 'forgejo-integration' - runs-on: docker - container: - image: 'data.forgejo.org/oci/node:22-bookworm' - options: --tmpfs /tmp:exec,noatime - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye - - uses: ./.forgejo/workflows-composite/build-backend - - run: | - su forgejo -c 'make test-backend test-check' - timeout-minutes: 120 - env: - RACE_ENABLED: 'true' - TAGS: bindata - test-sqlite: -# if: vars.ROLE == 'forgejo-coding' - if: vars.ROLE == 'forgejo-integration' - runs-on: docker - container: - image: 'data.forgejo.org/oci/node:22-bookworm' - options: --tmpfs /tmp:exec,noatime - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye - - uses: ./.forgejo/workflows-composite/build-backend - - run: | - su forgejo -c 'make test-sqlite-migration test-sqlite' - timeout-minutes: 120 - env: - TAGS: sqlite sqlite_unlock_notify - RACE_ENABLED: true - TEST_TAGS: sqlite sqlite_unlock_notify - USE_REPO_TEST_DIR: 1 diff --git a/build.go b/build.go new file mode 100644 index 0000000000..d410e171c7 --- /dev/null +++ b/build.go @@ -0,0 +1,14 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build vendor + +package main + +// Libraries that are included to vendor utilities used during build. +// These libraries will not be included in a normal compilation. + +import ( + // for embed + _ "github.com/shurcooL/vfsgen" +) diff --git a/build/generate-bindata.go b/build/generate-bindata.go index 2bdfc39574..2fcb7c2f2a 100644 --- a/build/generate-bindata.go +++ b/build/generate-bindata.go @@ -1,6 +1,5 @@ // Copyright 2020 The Gitea Authors. All rights reserved. -// Copyright 2025 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT //go:build ignore @@ -8,18 +7,15 @@ package main import ( "bytes" - "crypto/sha256" + "crypto/sha1" "fmt" - "io" - "io/fs" "log" + "net/http" "os" - "path" "path/filepath" "strconv" - "text/template" - "github.com/klauspost/compress/zstd" + "github.com/shurcooL/vfsgen" ) func needsUpdate(dir, filename string) (bool, []byte) { @@ -34,7 +30,7 @@ func needsUpdate(dir, filename string) (bool, []byte) { oldHash = []byte{} } - hasher := sha256.New() + hasher := sha1.New() err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { @@ -55,7 +51,7 @@ func needsUpdate(dir, filename string) (bool, []byte) { newHash := hasher.Sum([]byte{}) - if !bytes.Equal(oldHash, newHash) { + if bytes.Compare(oldHash, newHash) != 0 { return true, newHash } @@ -81,268 +77,16 @@ func main() { } fmt.Printf("generating bindata for %s\n", packageName) - - root, err := os.OpenRoot(dir) + var fsTemplates http.FileSystem = http.Dir(dir) + err := vfsgen.Generate(fsTemplates, vfsgen.Options{ + PackageName: packageName, + BuildTags: "bindata", + VariableName: "Assets", + Filename: filename, + UseGlobalModTime: useGlobalModTime, + }) if err != nil { - log.Fatal(err) - } - - out, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) - if err != nil { - log.Fatal(err) - } - defer out.Close() - - if err := generate(root.FS(), packageName, useGlobalModTime, out); err != nil { - log.Fatal(err) + log.Fatalf("%v\n", err) } _ = os.WriteFile(filename+".hash", newHash, 0o666) } - -type file struct { - Path string - Name string - UncompressedSize int - CompressedData []byte - UncompressedData []byte -} - -type direntry struct { - Name string - IsDir bool -} - -func generate(fsRoot fs.FS, packageName string, globalTime bool, output io.Writer) error { - enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) - if err != nil { - return err - } - - files := []file{} - - dirs := map[string][]direntry{} - - if err := fs.WalkDir(fsRoot, ".", func(filePath string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - entries, err := fs.ReadDir(fsRoot, filePath) - if err != nil { - return err - } - dirEntries := make([]direntry, 0, len(entries)) - for _, entry := range entries { - dirEntries = append(dirEntries, direntry{Name: entry.Name(), IsDir: entry.IsDir()}) - } - dirs[filePath] = dirEntries - return nil - } - - src, err := fs.ReadFile(fsRoot, filePath) - if err != nil { - return err - } - - dst := enc.EncodeAll(src, nil) - if len(dst) < len(src) { - files = append(files, file{ - Path: filePath, - Name: path.Base(filePath), - UncompressedSize: len(src), - CompressedData: dst, - }) - } else { - files = append(files, file{ - Path: filePath, - Name: path.Base(filePath), - UncompressedData: src, - }) - } - return nil - }); err != nil { - return err - } - - return generatedTmpl.Execute(output, map[string]any{ - "Packagename": packageName, - "GlobalTime": globalTime, - "Files": files, - "Dirs": dirs, - }) -} - -var generatedTmpl = template.Must(template.New("").Parse(`// Code generated by efs-gen. DO NOT EDIT. - -//go:build bindata - -package {{.Packagename}} - -import ( - "bytes" - "time" - "io" - "io/fs" - - "github.com/klauspost/compress/zstd" -) - -type normalFile struct { - name string - content []byte -} - -type compressedFile struct { - name string - uncompressedSize int64 - data []byte -} - -var files = map[string]any{ -{{- range .Files}} - "{{.Path}}": {{if .CompressedData}}compressedFile{"{{.Name}}", {{.UncompressedSize}}, []byte({{printf "%+q" .CompressedData}})}{{else}}normalFile{"{{.Name}}", []byte({{printf "%+q" .UncompressedData}})}{{end}}, -{{- end}} -} - -var dirs = map[string][]fs.DirEntry{ -{{- range $key, $entry := .Dirs}} - "{{$key}}": { -{{- range $entry}} - direntry{"{{.Name}}", {{.IsDir}}}, -{{- end}} - }, -{{- end}} -} - -type assets struct{} - -var Assets = assets{} - -func (a assets) Open(name string) (fs.File, error) { - f, ok := files[name] - if !ok { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} - } - - switch f := f.(type) { - case normalFile: - return file{name: f.name, size: int64(len(f.content)), data: bytes.NewReader(f.content)}, nil - case compressedFile: - r, _ := zstd.NewReader(bytes.NewReader(f.data)) - return &compressFile{name: f.name, size: f.uncompressedSize, data: r, content: f.data}, nil - default: - panic("unknown file type") - } -} - -func (a assets) ReadDir(name string) ([]fs.DirEntry, error) { - d, ok := dirs[name] - if !ok { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} - } - return d, nil -} - -type file struct { - name string - size int64 - data io.ReadSeeker -} - -var _ io.ReadSeeker = (*file)(nil) - -func (f file) Stat() (fs.FileInfo, error) { - return fileinfo{name: f.name, size: f.size}, nil -} - -func (f file) Read(p []byte) (int, error) { - return f.data.Read(p) -} - -func (f file) Seek(offset int64, whence int) (int64, error) { - return f.data.Seek(offset, whence) -} - -func (f file) Close() error { return nil } - -type compressFile struct { - name string - size int64 - data *zstd.Decoder - content []byte - zstdPos int64 - seekPos int64 -} - -var _ io.ReadSeeker = (*compressFile)(nil) - -func (f *compressFile) Stat() (fs.FileInfo, error) { - return fileinfo{name: f.name, size: f.size}, nil -} - -func (f *compressFile) Read(p []byte) (int, error) { - if f.zstdPos > f.seekPos { - if err := f.data.Reset(bytes.NewReader(f.content)); err != nil { - return 0, err - } - f.zstdPos = 0 - } - if f.zstdPos < f.seekPos { - if _, err := io.CopyN(io.Discard, f.data, f.seekPos - f.zstdPos); err != nil { - return 0, err - } - f.zstdPos = f.seekPos - } - n, err := f.data.Read(p) - f.zstdPos += int64(n) - f.seekPos = f.zstdPos - return n, err -} - -func (f *compressFile) Seek(offset int64, whence int) (int64, error) { - switch whence { - case io.SeekStart: - f.seekPos = 0 + offset - case io.SeekCurrent: - f.seekPos += offset - case io.SeekEnd: - f.seekPos = f.size + offset - } - return f.seekPos, nil -} - -func (f *compressFile) Close() error { - f.data.Close() - return nil -} - -func (f *compressFile) ZstdBytes() []byte { return f.content } - -type fileinfo struct { - name string - size int64 -} - -func (f fileinfo) Name() string { return f.name } -func (f fileinfo) Size() int64 { return f.size } -func (f fileinfo) Mode() fs.FileMode { return 0o444 } -func (f fileinfo) ModTime() time.Time { return {{if .GlobalTime}}GlobalModTime(f.name){{else}}time.Unix(0, 0){{end}} } -func (f fileinfo) IsDir() bool { return false } -func (f fileinfo) Sys() any { return nil } - -type direntry struct { - name string - isDir bool -} - -func (d direntry) Name() string { return d.name } -func (d direntry) IsDir() bool { return d.isDir } -func (d direntry) Type() fs.FileMode { - if d.isDir { - return 0o755 | fs.ModeDir - } - return 0o444 -} -func (direntry) Info() (fs.FileInfo, error) { return nil, fs.ErrNotExist } -`)) diff --git a/contrib/autocompletion/bash_autocomplete b/contrib/autocompletion/bash_autocomplete index 58844938a6..5cb62f26a7 100755 --- a/contrib/autocompletion/bash_autocomplete +++ b/contrib/autocompletion/bash_autocomplete @@ -1,3 +1,4 @@ +#! /bin/bash # Heavily inspired by https://github.com/urfave/cli _cli_bash_autocomplete() { @@ -6,9 +7,9 @@ _cli_bash_autocomplete() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" if [[ "$cur" == "-"* ]]; then - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) else - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) fi COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 diff --git a/contrib/autocompletion/zsh_autocomplete b/contrib/autocompletion/zsh_autocomplete index 0fd1a0b175..b3b40df503 100644 --- a/contrib/autocompletion/zsh_autocomplete +++ b/contrib/autocompletion/zsh_autocomplete @@ -9,9 +9,9 @@ _cli_zsh_autocomplete() { local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}") + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}") + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]]; then diff --git a/flake.lock b/flake.lock index dcf7755013..90672733d5 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1749285348, - "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", + "lastModified": 1733392399, + "narHash": "sha256-kEsTJTUQfQFIJOcLYFt/RvNxIK653ZkTBIs4DG+cBns=", "owner": "nixos", "repo": "nixpkgs", - "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", + "rev": "d0797a04b81caeae77bcff10a9dde78bc17f5661", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 01b23258b9..daa66d1d04 100644 --- a/flake.nix +++ b/flake.nix @@ -3,20 +3,35 @@ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; - outputs = - { - nixpkgs, - flake-utils, - ... - }: + outputs = { + nixpkgs, + flake-utils, + ... + }: flake-utils.lib.eachDefaultSystem ( - system: - let + system: let pkgs = nixpkgs.legacyPackages.${system}; - in - { - devShells.default = import ./shell.nix { inherit pkgs; }; - formatter = pkgs.nixfmt-rfc-style; + in { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + # generic + git + git-lfs + gnumake + gnused + gnutar + gzip + + # frontend + nodejs_20 + + # backend + gofumpt + sqlite + go + gopls + ]; + }; } ); } diff --git a/go.mod b/go.mod index 0eca1be551..7ed17b0b7a 100644 --- a/go.mod +++ b/go.mod @@ -89,6 +89,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sergi/go-diff v1.4.0 + github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 github.com/ulikunitz/xz v0.5.12 @@ -221,6 +222,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect @@ -244,6 +246,8 @@ require ( replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 +replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 + replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.26.0 replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1 diff --git a/go.sum b/go.sum index 18e37ae5f6..3b462ccab7 100644 --- a/go.sum +++ b/go.sum @@ -384,6 +384,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI= +github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -502,6 +504,8 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCw github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/models/error.go b/models/error.go index ebaa8a135d..e8962f386b 100644 --- a/models/error.go +++ b/models/error.go @@ -414,7 +414,7 @@ func IsErrSHAOrCommitIDNotProvided(err error) bool { } func (err ErrSHAOrCommitIDNotProvided) Error() string { - return "a SHA or commit ID must be provided when updating a file" + return "a SHA or commit ID must be proved when updating a file" } // ErrInvalidMergeStyle represents an error if merging with disabled merge strategy diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 48c6728f43..8d54ae5e4a 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -5,10 +5,10 @@ package assetfs import ( "context" - "errors" "fmt" "io" "io/fs" + "net/http" "os" "path/filepath" "slices" @@ -25,7 +25,7 @@ import ( // Layer represents a layer in a layered asset file-system. It has a name and works like http.FileSystem type Layer struct { name string - fs fs.FS + fs http.FileSystem localPath string } @@ -34,18 +34,10 @@ func (l *Layer) Name() string { } // Open opens the named file. The caller is responsible for closing the file. -func (l *Layer) Open(name string) (fs.File, error) { +func (l *Layer) Open(name string) (http.File, error) { return l.fs.Open(name) } -func (l *Layer) ReadDir(name string) ([]fs.DirEntry, error) { - dirEntries, err := fs.ReadDir(l.fs, name) - if err != nil && errors.Is(err, fs.ErrNotExist) { - err = nil - } - return dirEntries, err -} - // Local returns a new Layer with the given name, it serves files from the given local path. func Local(name, base string, sub ...string) *Layer { // TODO: the old behavior (StaticRootPath might not be absolute), not ideal, just keep the same as before @@ -56,18 +48,11 @@ func Local(name, base string, sub ...string) *Layer { panic(fmt.Sprintf("Unable to get absolute path for %q: %v", base, err)) } root := util.FilePathJoinAbs(base, sub...) - fsRoot, err := os.OpenRoot(root) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil - } - panic(fmt.Sprintf("Unable to open layer %q", err)) - } - return &Layer{name: name, fs: fsRoot.FS(), localPath: root} + return &Layer{name: name, fs: http.Dir(root), localPath: root} } // Bindata returns a new Layer with the given name, it serves files from the given bindata asset. -func Bindata(name string, fs fs.FS) *Layer { +func Bindata(name string, fs http.FileSystem) *Layer { return &Layer{name: name, fs: fs} } @@ -80,11 +65,11 @@ type LayeredFS struct { // Layered returns a new LayeredFS with the given layers. The first layer is the top layer. func Layered(layers ...*Layer) *LayeredFS { - return &LayeredFS{layers: slices.DeleteFunc(layers, func(layer *Layer) bool { return layer == nil })} + return &LayeredFS{layers: layers} } // Open opens the named file. The caller is responsible for closing the file. -func (l *LayeredFS) Open(name string) (fs.File, error) { +func (l *LayeredFS) Open(name string) (http.File, error) { for _, layer := range l.layers { f, err := layer.Open(name) if err == nil || !os.IsNotExist(err) { @@ -117,18 +102,29 @@ func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) { return nil, "", fs.ErrNotExist } -func shouldInclude(info fs.DirEntry, fileMode ...bool) bool { +func shouldInclude(info fs.FileInfo, fileMode ...bool) bool { if util.CommonSkip(info.Name()) { return false } if len(fileMode) == 0 { return true } else if len(fileMode) == 1 { - return fileMode[0] == !info.IsDir() + return fileMode[0] == !info.Mode().IsDir() } panic("too many arguments for fileMode in shouldInclude") } +func readDir(layer *Layer, name string) ([]fs.FileInfo, error) { + f, err := layer.Open(name) + if os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, err + } + defer f.Close() + return f.Readdir(-1) +} + // ListFiles lists files/directories in the given directory. The fileMode controls the returned files. // * omitted: all files and directories will be returned. // * true: only files will be returned. @@ -137,7 +133,7 @@ func shouldInclude(info fs.DirEntry, fileMode ...bool) bool { func (l *LayeredFS) ListFiles(name string, fileMode ...bool) ([]string, error) { fileSet := make(container.Set[string]) for _, layer := range l.layers { - infos, err := layer.ReadDir(name) + infos, err := readDir(layer, name) if err != nil { return nil, err } @@ -166,7 +162,7 @@ func listAllFiles(layers []*Layer, name string, fileMode ...bool) ([]string, err var list func(dir string) error list = func(dir string) error { for _, layer := range layers { - infos, err := layer.ReadDir(dir) + infos, err := readDir(layer, dir) if err != nil { return err } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 01200dba68..1f9060247f 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -105,10 +105,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { }) t.Run("SSH format", func(t *testing.T) { - if CheckGitVersionAtLeast("2.34.0") != nil { - t.SkipNow() - } - r, err := os.OpenRoot(t.TempDir()) require.NoError(t, err) f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700) diff --git a/modules/public/public.go b/modules/public/public.go index a7db5b62e9..174936fd4a 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -6,7 +6,6 @@ package public import ( "bytes" "io" - "io/fs" "net/http" "os" "path" @@ -60,7 +59,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) { } } -func handleRequest(w http.ResponseWriter, req *http.Request, fs fs.FS, file string) { +func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) { // actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here f, err := fs.Open(util.PathJoinRelX(file)) if err != nil { @@ -87,31 +86,33 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs fs.FS, file stri return } - serveContent(w, req, fi.Name(), fi.ModTime(), f.(io.ReadSeeker)) + serveContent(w, req, fi, fi.ModTime(), f) } -type ZstdBytesProvider interface { - ZstdBytes() []byte +type GzipBytesProvider interface { + GzipBytes() []byte } // serveContent serve http content -func serveContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { - setWellKnownContentType(w, name) +func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { + setWellKnownContentType(w, fi.Name()) encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) - if encodings.Contains("zstd") { - // If the file was compressed, use the bytes directly. - if compressed, ok := content.(ZstdBytesProvider); ok { - rdZstd := bytes.NewReader(compressed.ZstdBytes()) + if encodings.Contains("gzip") { + // try to provide gzip content directly from bindata (provided by vfsgen۰CompressedFileInfo) + if compressed, ok := fi.(GzipBytesProvider); ok { + rdGzip := bytes.NewReader(compressed.GzipBytes()) + // all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name + // then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data if w.Header().Get("Content-Type") == "" { w.Header().Set("Content-Type", "application/octet-stream") } - w.Header().Set("Content-Encoding", "zstd") - httpcache.ServeContentWithCacheControl(w, req, name, modtime, rdZstd) + w.Header().Set("Content-Encoding", "gzip") + httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, rdGzip) return } } - httpcache.ServeContentWithCacheControl(w, req, name, modtime, content) + httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, content) return } diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go index 148d789bba..e19bd976eb 100644 --- a/modules/public/serve_static.go +++ b/modules/public/serve_static.go @@ -12,6 +12,8 @@ import ( "forgejo.org/modules/timeutil" ) +var _ GzipBytesProvider = (*vfsgen۰CompressedFileInfo)(nil) + // GlobalModTime provide a global mod time for embedded asset files func GlobalModTime(filename string) time.Time { return timeutil.GetExecutableModTime() diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index c7e9f65a68..96d0c4bc63 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -93,6 +93,5 @@ "editor.textarea.tab_hint": "Line already indented. Press Tab again or Escape to leave the editor.", "editor.textarea.shift_tab_hint": "No indentation on this line. Press Shift + Tab again or Escape to leave the editor.", "admin.dashboard.cleanup_offline_runners": "Cleanup offline runners", - "settings.visibility.description": "Profile visibility affects others' ability to access your non-private repositories. Learn more", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/package-lock.json b/package-lock.json index 473a334c25..588a359444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "pdfobject": "2.3.0", "postcss": "8.5.4", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.2", + "postcss-nesting": "13.0.1", "pretty-ms": "9.0.0", "sortablejs": "1.15.6", "swagger-ui-dist": "5.17.14", @@ -62,13 +62,13 @@ "devDependencies": { "@axe-core/playwright": "4.10.2", "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", - "@playwright/test": "1.53.0", + "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin-js": "4.4.1", "@stylistic/stylelint-plugin": "3.1.2", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", - "@vitest/eslint-plugin": "1.2.2", + "@vitest/eslint-plugin": "1.2.1", "@vue/test-utils": "2.4.6", "eslint": "9.28.0", "eslint-import-resolver-typescript": "4.4.3", @@ -86,7 +86,7 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "2.2.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "17.6.3", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", @@ -97,7 +97,7 @@ "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.33.1", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, @@ -2131,13 +2131,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0.tgz", - "integrity": "sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.0" + "playwright": "1.52.0" }, "bin": { "playwright": "cli.js" @@ -3448,9 +3448,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", - "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3493,25 +3493,18 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/whatwg-mimetype": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", - "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", + "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/type-utils": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3525,7 +3518,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", + "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3541,16 +3534,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", + "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "engines": { @@ -3566,14 +3559,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", + "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.33.1", + "@typescript-eslint/types": "^8.33.1", "debug": "^4.3.4" }, "engines": { @@ -3588,14 +3581,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", + "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3606,9 +3599,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", + "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", "dev": true, "license": "MIT", "engines": { @@ -3623,14 +3616,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", + "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3647,9 +3640,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", + "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", "dev": true, "license": "MIT", "engines": { @@ -3661,16 +3654,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", + "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.33.1", + "@typescript-eslint/tsconfig-utils": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3706,16 +3699,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", + "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3730,13 +3723,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", + "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4047,9 +4040,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.2.tgz", - "integrity": "sha512-R8NwW+VxyKqVGcMfYsUbdThQyMbtNcoeg+jJeTgMHqWdFdcS0nrODAQXhkplvWzgd7jIJ+GQeydGqFLibsxMxg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.1.tgz", + "integrity": "sha512-JQr1jdVcrsoS7Sdzn83h9sq4DvREf9Q/onTZbJCqTVlv/76qb+TZrLv/9VhjnjSMHweQH5FdpMDeCR6aDe2fgw==", "dev": true, "license": "MIT", "dependencies": { @@ -8436,14 +8429,13 @@ } }, "node_modules/happy-dom": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.0.tgz", - "integrity": "sha512-o3p2Axi1EdIfMaOUulDzO/5yXzLLV0g/54eLPVrkt3u20r3yOuOenHpyp2clAJ0eHMc+HyE139ulQxl+8pEJIw==", + "version": "17.6.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz", + "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.0.0", - "@types/whatwg-mimetype": "^3.0.2", + "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" }, "engines": { @@ -11758,13 +11750,13 @@ } }, "node_modules/playwright": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0.tgz", - "integrity": "sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.0" + "playwright-core": "1.52.0" }, "bin": { "playwright": "cli.js" @@ -11777,9 +11769,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz", - "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12057,9 +12049,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", "funding": [ { "type": "github", @@ -12072,7 +12064,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-resolve-nested": "^3.0.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, @@ -14617,15 +14609,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", - "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", + "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", - "@typescript-eslint/utils": "8.34.0" + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/utils": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -15294,6 +15286,16 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.99.9", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", diff --git a/package.json b/package.json index 1ab46d0a45..f30c334cdb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "pdfobject": "2.3.0", "postcss": "8.5.4", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.2", + "postcss-nesting": "13.0.1", "pretty-ms": "9.0.0", "sortablejs": "1.15.6", "swagger-ui-dist": "5.17.14", @@ -61,13 +61,13 @@ "devDependencies": { "@axe-core/playwright": "4.10.2", "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", - "@playwright/test": "1.53.0", + "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin-js": "4.4.1", "@stylistic/stylelint-plugin": "3.1.2", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", - "@vitest/eslint-plugin": "1.2.2", + "@vitest/eslint-plugin": "1.2.1", "@vue/test-utils": "2.4.6", "eslint": "9.28.0", "eslint-import-resolver-typescript": "4.4.3", @@ -85,7 +85,7 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "2.2.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "17.6.3", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", @@ -96,7 +96,7 @@ "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.33.1", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 549fe9fae0..3408f88dd1 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -480,8 +480,6 @@ func ChangeFiles(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - // "409": - // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -586,8 +584,6 @@ func CreateFile(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - // "409": - // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -688,8 +684,6 @@ func UpdateFile(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - // "409": - // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -763,19 +757,11 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { ctx.Error(http.StatusForbidden, "Access", err) return } - if git_model.IsErrBranchAlreadyExists(err) || - models.IsErrFilenameInvalid(err) || - models.IsErrSHAOrCommitIDNotProvided(err) || - models.IsErrFilePathInvalid(err) || - models.IsErrRepoFileAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || + models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } - if models.IsErrCommitIDDoesNotMatch(err) || - models.IsErrSHADoesNotMatch(err) { - ctx.Error(http.StatusConflict, "Conflict", err) - return - } if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index e8e5d2c54b..aa599bd252 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -225,7 +225,7 @@ func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, ser idToken := &oauth2.OIDCToken{ RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), - Issuer: strings.TrimSuffix(setting.AppURL, "/"), + Issuer: setting.AppURL, Audience: []string{app.ClientID}, Subject: fmt.Sprint(grant.UserID), }, @@ -409,7 +409,7 @@ func IntrospectOAuth(ctx *context.Context) { if err == nil && app != nil { response.Active = true response.Scope = grant.Scope - response.Issuer = strings.TrimSuffix(setting.AppURL, "/") + response.Issuer = setting.AppURL response.Audience = []string{app.ClientID} response.Subject = fmt.Sprint(grant.UserID) } @@ -669,7 +669,6 @@ func GrantApplicationOAuth(ctx *context.Context) { // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities func OIDCWellKnown(ctx *context.Context) { ctx.Data["SigningKey"] = oauth2.DefaultSigningKey - ctx.Data["Issuer"] = strings.TrimSuffix(setting.AppURL, "/") ctx.JSONTemplate("user/auth/oidc_wellknown") } diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 9782711dd0..487b551d6c 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -51,7 +51,6 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { // Scopes: openid oidcToken := createAndParseToken(t, grants[0]) - assert.Equal(t, "https://try.gitea.io", oidcToken.RegisteredClaims.Issuer) assert.Empty(t, oidcToken.Name) assert.Empty(t, oidcToken.PreferredUsername) assert.Empty(t, oidcToken.Profile) @@ -68,7 +67,6 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { // Scopes: openid profile email oidcToken = createAndParseToken(t, grants[0]) - assert.Equal(t, "https://try.gitea.io", oidcToken.RegisteredClaims.Issuer) assert.Equal(t, "User Five", oidcToken.Name) assert.Equal(t, "user5", oidcToken.PreferredUsername) assert.Equal(t, "https://try.gitea.io/user5", oidcToken.Profile) diff --git a/services/repository/files/update.go b/services/repository/files/update.go index f9cdc61db1..c637a4e8db 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -264,34 +264,28 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } if hasOldBranch { - // Get the current commit of the original branch - actualBaseCommit, err := t.GetBranchCommit(opts.OldBranch) + // Get the commit of the original branch + commit, err := t.GetBranchCommit(opts.OldBranch) if err != nil { return nil, err // Couldn't get a commit for the branch } - var lastKnownCommit git.ObjectID // when nil, the sha provided in the opts.Files must match the current blob-sha - if opts.OldBranch != opts.NewBranch { - // when creating a new branch, ignore if a file has been changed in the meantime - // (such changes will visible when doing the merge) - lastKnownCommit = actualBaseCommit.ID - } else if opts.LastCommitID != "" { - lastKnownCommit, err = t.gitRepo.ConvertToGitID(opts.LastCommitID) + // Assigned LastCommitID in opts if it hasn't been set + if opts.LastCommitID == "" { + opts.LastCommitID = commit.ID.String() + } else { + lastCommitID, err := t.gitRepo.ConvertToGitID(opts.LastCommitID) if err != nil { return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err) } + opts.LastCommitID = lastCommitID.String() } for _, file := range opts.Files { - if err := handleCheckErrors(file, actualBaseCommit, lastKnownCommit); err != nil { + if err := handleCheckErrors(file, commit, opts); err != nil { return nil, err } } - - if opts.LastCommitID == "" { - // needed for t.CommitTree - opts.LastCommitID = actualBaseCommit.ID.String() - } } contentStore := lfs.NewContentStore() @@ -354,9 +348,9 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } // handles the check for various issues for ChangeRepoFiles -func handleCheckErrors(file *ChangeRepoFile, actualBaseCommit *git.Commit, lastKnownCommit git.ObjectID) error { +func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error { if file.Operation == "update" || file.Operation == "delete" { - fromEntry, err := actualBaseCommit.GetTreeEntryByPath(file.Options.fromTreePath) + fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath) if err != nil { return err } @@ -369,22 +363,22 @@ func handleCheckErrors(file *ChangeRepoFile, actualBaseCommit *git.Commit, lastK CurrentSHA: fromEntry.ID.String(), } } - } else if lastKnownCommit != nil { - if actualBaseCommit.ID.String() != lastKnownCommit.String() { - // If a lastKnownCommit was given and it doesn't match the actualBaseCommit, - // check if the file has been changed in between - if changed, err := actualBaseCommit.FileChangedSinceCommit(file.Options.treePath, lastKnownCommit.String()); err != nil { + } else if opts.LastCommitID != "" { + // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw + // an error, but only if we aren't creating a new branch. + if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch { + if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil { return err } else if changed { return models.ErrCommitIDDoesNotMatch{ - GivenCommitID: lastKnownCommit.String(), - CurrentCommitID: actualBaseCommit.ID.String(), + GivenCommitID: opts.LastCommitID, + CurrentCommitID: opts.LastCommitID, } } - // The file wasn't modified, so we are good to update it + // The file wasn't modified, so we are good to delete it } } else { - // When updating a file, a lastKnownCommit or SHA needs to be given to make sure other commits + // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits // haven't been made. We throw an error if one wasn't provided. return models.ErrSHAOrCommitIDNotProvided{} } @@ -399,7 +393,7 @@ func handleCheckErrors(file *ChangeRepoFile, actualBaseCommit *git.Commit, lastK subTreePath := "" for index, part := range treePathParts { subTreePath = path.Join(subTreePath, part) - entry, err := actualBaseCommit.GetTreeEntryByPath(subTreePath) + entry, err := commit.GetTreeEntryByPath(subTreePath) if err != nil { if git.IsErrNotExist(err) { // Means there is no item with that name, so we're good diff --git a/shell.nix b/shell.nix deleted file mode 100644 index cfd555fa37..0000000000 --- a/shell.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - pkgs ? import { }, -}: - -pkgs.mkShell { - name = "forgejo"; - nativeBuildInputs = with pkgs; [ - # generic - git - git-lfs - gnumake - gnused - gnutar - gzip - - # frontend - nodejs - - # backend - gofumpt - sqlite - go - gopls - - # tests - openssh - ]; -} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 1b5f55f97b..557ea5ea2b 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -6897,9 +6897,6 @@ "404": { "$ref": "#/responses/notFound" }, - "409": { - "$ref": "#/responses/conflict" - }, "413": { "$ref": "#/responses/quotaExceeded" }, @@ -7013,9 +7010,6 @@ "404": { "$ref": "#/responses/notFound" }, - "409": { - "$ref": "#/responses/conflict" - }, "413": { "$ref": "#/responses/quotaExceeded" }, @@ -7080,9 +7074,6 @@ "404": { "$ref": "#/responses/notFound" }, - "409": { - "$ref": "#/responses/conflict" - }, "413": { "$ref": "#/responses/quotaExceeded" }, diff --git a/templates/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl index d8edcae41d..54bb4a763d 100644 --- a/templates/user/auth/oidc_wellknown.tmpl +++ b/templates/user/auth/oidc_wellknown.tmpl @@ -1,5 +1,5 @@ { - "issuer": "{{.Issuer | JSEscape}}", + "issuer": "{{AppUrl | JSEscape}}", "authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize", "token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token", "jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys", diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index e4c3e82727..f0cbe12049 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -83,7 +83,6 @@ {{end}} - {{ctx.Locale.Tr "settings.visibility.description" "https://forgejo.org/docs/latest/user/repo-permissions/#profile-and-visibility"}}