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"}}