diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 9d8f54ee13..28fa9e4555 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.3": {},
+ "ghcr.io/devcontainers/features/git-lfs:1.2.4": {},
"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 615e7cb184..c1c8c950e3 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 -qq -y ${PACKAGES}
+ apt-get -q install --allow-downgrades -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
new file mode 100644
index 0000000000..9e5cfb92ed
--- /dev/null
+++ b/.forgejo/workflows/testing-integration.yml
@@ -0,0 +1,71 @@
+#
+# 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
deleted file mode 100644
index d410e171c7..0000000000
--- a/build.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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 2fcb7c2f2a..2bdfc39574 100644
--- a/build/generate-bindata.go
+++ b/build/generate-bindata.go
@@ -1,5 +1,6 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
+// Copyright 2025 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: GPL-3.0-or-later
//go:build ignore
@@ -7,15 +8,18 @@ package main
import (
"bytes"
- "crypto/sha1"
+ "crypto/sha256"
"fmt"
+ "io"
+ "io/fs"
"log"
- "net/http"
"os"
+ "path"
"path/filepath"
"strconv"
+ "text/template"
- "github.com/shurcooL/vfsgen"
+ "github.com/klauspost/compress/zstd"
)
func needsUpdate(dir, filename string) (bool, []byte) {
@@ -30,7 +34,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
oldHash = []byte{}
}
- hasher := sha1.New()
+ hasher := sha256.New()
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
@@ -51,7 +55,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
newHash := hasher.Sum([]byte{})
- if bytes.Compare(oldHash, newHash) != 0 {
+ if !bytes.Equal(oldHash, newHash) {
return true, newHash
}
@@ -77,16 +81,268 @@ func main() {
}
fmt.Printf("generating bindata for %s\n", packageName)
- var fsTemplates http.FileSystem = http.Dir(dir)
- err := vfsgen.Generate(fsTemplates, vfsgen.Options{
- PackageName: packageName,
- BuildTags: "bindata",
- VariableName: "Assets",
- Filename: filename,
- UseGlobalModTime: useGlobalModTime,
- })
+
+ root, err := os.OpenRoot(dir)
if err != nil {
- log.Fatalf("%v\n", err)
+ 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)
}
_ = 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/flake.lock b/flake.lock
index 90672733d5..dcf7755013 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1733392399,
- "narHash": "sha256-kEsTJTUQfQFIJOcLYFt/RvNxIK653ZkTBIs4DG+cBns=",
+ "lastModified": 1749285348,
+ "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "d0797a04b81caeae77bcff10a9dde78bc17f5661",
+ "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index daa66d1d04..01b23258b9 100644
--- a/flake.nix
+++ b/flake.nix
@@ -3,35 +3,20 @@
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 = pkgs.mkShell {
- buildInputs = with pkgs; [
- # generic
- git
- git-lfs
- gnumake
- gnused
- gnutar
- gzip
-
- # frontend
- nodejs_20
-
- # backend
- gofumpt
- sqlite
- go
- gopls
- ];
- };
+ in
+ {
+ devShells.default = import ./shell.nix { inherit pkgs; };
+ formatter = pkgs.nixfmt-rfc-style;
}
);
}
diff --git a/go.mod b/go.mod
index 7ed17b0b7a..0eca1be551 100644
--- a/go.mod
+++ b/go.mod
@@ -89,7 +89,6 @@ 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
@@ -222,7 +221,6 @@ 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
@@ -246,8 +244,6 @@ 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 3b462ccab7..18e37ae5f6 100644
--- a/go.sum
+++ b/go.sum
@@ -384,8 +384,6 @@ 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=
@@ -504,8 +502,6 @@ 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/modules/assetfs/layered.go b/modules/assetfs/layered.go
index 8d54ae5e4a..48c6728f43 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 http.FileSystem
+ fs fs.FS
localPath string
}
@@ -34,10 +34,18 @@ func (l *Layer) Name() string {
}
// Open opens the named file. The caller is responsible for closing the file.
-func (l *Layer) Open(name string) (http.File, error) {
+func (l *Layer) Open(name string) (fs.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
@@ -48,11 +56,18 @@ 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...)
- return &Layer{name: name, fs: http.Dir(root), localPath: root}
+ 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}
}
// Bindata returns a new Layer with the given name, it serves files from the given bindata asset.
-func Bindata(name string, fs http.FileSystem) *Layer {
+func Bindata(name string, fs fs.FS) *Layer {
return &Layer{name: name, fs: fs}
}
@@ -65,11 +80,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: layers}
+ return &LayeredFS{layers: slices.DeleteFunc(layers, func(layer *Layer) bool { return layer == nil })}
}
// Open opens the named file. The caller is responsible for closing the file.
-func (l *LayeredFS) Open(name string) (http.File, error) {
+func (l *LayeredFS) Open(name string) (fs.File, error) {
for _, layer := range l.layers {
f, err := layer.Open(name)
if err == nil || !os.IsNotExist(err) {
@@ -102,29 +117,18 @@ func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) {
return nil, "", fs.ErrNotExist
}
-func shouldInclude(info fs.FileInfo, fileMode ...bool) bool {
+func shouldInclude(info fs.DirEntry, 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.Mode().IsDir()
+ return fileMode[0] == !info.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.
@@ -133,7 +137,7 @@ func readDir(layer *Layer, name string) ([]fs.FileInfo, error) {
func (l *LayeredFS) ListFiles(name string, fileMode ...bool) ([]string, error) {
fileSet := make(container.Set[string])
for _, layer := range l.layers {
- infos, err := readDir(layer, name)
+ infos, err := layer.ReadDir(name)
if err != nil {
return nil, err
}
@@ -162,7 +166,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 := readDir(layer, dir)
+ infos, err := layer.ReadDir(dir)
if err != nil {
return err
}
diff --git a/modules/git/git_test.go b/modules/git/git_test.go
index 1f9060247f..01200dba68 100644
--- a/modules/git/git_test.go
+++ b/modules/git/git_test.go
@@ -105,6 +105,10 @@ 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 174936fd4a..a7db5b62e9 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -6,6 +6,7 @@ package public
import (
"bytes"
"io"
+ "io/fs"
"net/http"
"os"
"path"
@@ -59,7 +60,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
}
}
-func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) {
+func handleRequest(w http.ResponseWriter, req *http.Request, fs fs.FS, 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 {
@@ -86,33 +87,31 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem,
return
}
- serveContent(w, req, fi, fi.ModTime(), f)
+ serveContent(w, req, fi.Name(), fi.ModTime(), f.(io.ReadSeeker))
}
-type GzipBytesProvider interface {
- GzipBytes() []byte
+type ZstdBytesProvider interface {
+ ZstdBytes() []byte
}
// serveContent serve http content
-func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) {
- setWellKnownContentType(w, fi.Name())
+func serveContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) {
+ setWellKnownContentType(w, name)
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
- 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 encodings.Contains("zstd") {
+ // If the file was compressed, use the bytes directly.
+ if compressed, ok := content.(ZstdBytesProvider); ok {
+ rdZstd := bytes.NewReader(compressed.ZstdBytes())
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/octet-stream")
}
- w.Header().Set("Content-Encoding", "gzip")
- httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, rdGzip)
+ w.Header().Set("Content-Encoding", "zstd")
+ httpcache.ServeContentWithCacheControl(w, req, name, modtime, rdZstd)
return
}
}
- httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, content)
+ httpcache.ServeContentWithCacheControl(w, req, name, modtime, content)
return
}
diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go
index e19bd976eb..148d789bba 100644
--- a/modules/public/serve_static.go
+++ b/modules/public/serve_static.go
@@ -12,8 +12,6 @@ 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 96d0c4bc63..c7e9f65a68 100644
--- a/options/locale_next/locale_en-US.json
+++ b/options/locale_next/locale_en-US.json
@@ -93,5 +93,6 @@
"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 588a359444..473a334c25 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.1",
+ "postcss-nesting": "13.0.2",
"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.52.0",
+ "@playwright/test": "1.53.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.1",
+ "@vitest/eslint-plugin": "1.2.2",
"@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": "17.6.3",
+ "happy-dom": "18.0.0",
"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.33.1",
+ "typescript-eslint": "8.34.0",
"vite-string-plugin": "1.3.4",
"vitest": "3.2.3"
},
@@ -2131,13 +2131,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz",
- "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==",
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0.tgz",
+ "integrity": "sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.52.0"
+ "playwright": "1.53.0"
},
"bin": {
"playwright": "cli.js"
@@ -3448,9 +3448,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.15.30",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz",
- "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==",
+ "version": "20.19.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz",
+ "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -3493,18 +3493,25 @@
"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.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz",
- "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
+ "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.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",
+ "@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",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -3518,7 +3525,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.33.1",
+ "@typescript-eslint/parser": "^8.34.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -3534,16 +3541,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz",
- "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
+ "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@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",
+ "@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",
"debug": "^4.3.4"
},
"engines": {
@@ -3559,14 +3566,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz",
- "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==",
+ "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==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.33.1",
- "@typescript-eslint/types": "^8.33.1",
+ "@typescript-eslint/tsconfig-utils": "^8.34.0",
+ "@typescript-eslint/types": "^8.34.0",
"debug": "^4.3.4"
},
"engines": {
@@ -3581,14 +3588,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz",
- "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
+ "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.33.1",
- "@typescript-eslint/visitor-keys": "8.33.1"
+ "@typescript-eslint/types": "8.34.0",
+ "@typescript-eslint/visitor-keys": "8.34.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3599,9 +3606,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "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==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
+ "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3616,14 +3623,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "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==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
+ "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.33.1",
- "@typescript-eslint/utils": "8.33.1",
+ "@typescript-eslint/typescript-estree": "8.34.0",
+ "@typescript-eslint/utils": "8.34.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -3640,9 +3647,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz",
- "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
+ "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3654,16 +3661,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "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==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
+ "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@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",
+ "@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",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -3699,16 +3706,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz",
- "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
+ "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.33.1",
- "@typescript-eslint/types": "8.33.1",
- "@typescript-eslint/typescript-estree": "8.33.1"
+ "@typescript-eslint/scope-manager": "8.34.0",
+ "@typescript-eslint/types": "8.34.0",
+ "@typescript-eslint/typescript-estree": "8.34.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3723,13 +3730,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "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==",
+ "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==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.33.1",
+ "@typescript-eslint/types": "8.34.0",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@@ -4040,9 +4047,9 @@
}
},
"node_modules/@vitest/eslint-plugin": {
- "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==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.2.tgz",
+ "integrity": "sha512-R8NwW+VxyKqVGcMfYsUbdThQyMbtNcoeg+jJeTgMHqWdFdcS0nrODAQXhkplvWzgd7jIJ+GQeydGqFLibsxMxg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8429,13 +8436,14 @@
}
},
"node_modules/happy-dom": {
- "version": "17.6.3",
- "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz",
- "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==",
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.0.tgz",
+ "integrity": "sha512-o3p2Axi1EdIfMaOUulDzO/5yXzLLV0g/54eLPVrkt3u20r3yOuOenHpyp2clAJ0eHMc+HyE139ulQxl+8pEJIw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "webidl-conversions": "^7.0.0",
+ "@types/node": "^20.0.0",
+ "@types/whatwg-mimetype": "^3.0.2",
"whatwg-mimetype": "^3.0.0"
},
"engines": {
@@ -11750,13 +11758,13 @@
}
},
"node_modules/playwright": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
- "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0.tgz",
+ "integrity": "sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.52.0"
+ "playwright-core": "1.53.0"
},
"bin": {
"playwright": "cli.js"
@@ -11769,9 +11777,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
- "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz",
+ "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -12049,9 +12057,9 @@
}
},
"node_modules/postcss-nesting": {
- "version": "13.0.1",
- "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
- "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==",
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz",
+ "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==",
"funding": [
{
"type": "github",
@@ -12064,7 +12072,7 @@
],
"license": "MIT-0",
"dependencies": {
- "@csstools/selector-resolve-nested": "^3.0.0",
+ "@csstools/selector-resolve-nested": "^3.1.0",
"@csstools/selector-specificity": "^5.0.0",
"postcss-selector-parser": "^7.0.0"
},
@@ -14609,15 +14617,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz",
- "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz",
+ "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.33.1",
- "@typescript-eslint/parser": "8.33.1",
- "@typescript-eslint/utils": "8.33.1"
+ "@typescript-eslint/eslint-plugin": "8.34.0",
+ "@typescript-eslint/parser": "8.34.0",
+ "@typescript-eslint/utils": "8.34.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -15286,16 +15294,6 @@
"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 f30c334cdb..1ab46d0a45 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.1",
+ "postcss-nesting": "13.0.2",
"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.52.0",
+ "@playwright/test": "1.53.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.1",
+ "@vitest/eslint-plugin": "1.2.2",
"@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": "17.6.3",
+ "happy-dom": "18.0.0",
"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.33.1",
+ "typescript-eslint": "8.34.0",
"vite-string-plugin": "1.3.4",
"vitest": "3.2.3"
},
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index aa599bd252..e8e5d2c54b 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: setting.AppURL,
+ Issuer: strings.TrimSuffix(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 = setting.AppURL
+ response.Issuer = strings.TrimSuffix(setting.AppURL, "/")
response.Audience = []string{app.ClientID}
response.Subject = fmt.Sprint(grant.UserID)
}
@@ -669,6 +669,7 @@ 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 487b551d6c..9782711dd0 100644
--- a/routers/web/auth/oauth_test.go
+++ b/routers/web/auth/oauth_test.go
@@ -51,6 +51,7 @@ 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)
@@ -67,6 +68,7 @@ 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/shell.nix b/shell.nix
new file mode 100644
index 0000000000..cfd555fa37
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,28 @@
+{
+ 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/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl
index 54bb4a763d..d8edcae41d 100644
--- a/templates/user/auth/oidc_wellknown.tmpl
+++ b/templates/user/auth/oidc_wellknown.tmpl
@@ -1,5 +1,5 @@
{
- "issuer": "{{AppUrl | JSEscape}}",
+ "issuer": "{{.Issuer | 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 f0cbe12049..e4c3e82727 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -83,6 +83,7 @@
{{end}}
+ {{ctx.Locale.Tr "settings.visibility.description" "https://forgejo.org/docs/latest/user/repo-permissions/#profile-and-visibility"}}