Merge pull request '[gitea] cherry-pick' (#2320) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2320
This commit is contained in:
Earl Warren 2024-02-10 10:37:00 +00:00
commit 91f2f02f09
39 changed files with 264 additions and 535 deletions

View file

@ -811,7 +811,7 @@ rules:
wc/no-constructor-params: [2] wc/no-constructor-params: [2]
wc/no-constructor: [2] wc/no-constructor: [2]
wc/no-customized-built-in-elements: [2] wc/no-customized-built-in-elements: [2]
wc/no-exports-with-element: [2] wc/no-exports-with-element: [0]
wc/no-invalid-element-name: [2] wc/no-invalid-element-name: [2]
wc/no-invalid-extends: [2] wc/no-invalid-extends: [2]
wc/no-method-prefixed-with-on: [2] wc/no-method-prefixed-with-on: [2]

View file

@ -31,7 +31,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
@ -134,6 +134,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES += $(GENERATED_GO_DEST)
@ -212,6 +214,8 @@ help:
@echo " - lint-swagger lint swagger files" @echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files" @echo " - lint-templates lint template files"
@echo " - lint-yaml lint yaml files" @echo " - lint-yaml lint yaml files"
@echo " - lint-spell lint spelling"
@echo " - lint-spell-fix lint spelling and fix issues"
@echo " - checks run various consistency checks" @echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files" @echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files" @echo " - checks-backend check backend files"
@ -303,10 +307,6 @@ fmt-check: fmt
exit 1; \ exit 1; \
fi fi
.PHONY: misspell-check
misspell-check:
go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR) @mkdir -p $(MAKE_EVIDENCE_DIR)
@ -368,13 +368,13 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check checks-frontend: lockfile-check svg-check
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check misspell-check forgejo-api-validate swagger-validate security-check checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-frontend-fix lint-backend-fix lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css lint-frontend: lint-js lint-css
@ -412,6 +412,14 @@ lint-swagger: node_modules
lint-md: node_modules lint-md: node_modules
npx markdownlint docs *.md npx markdownlint docs *.md
.PHONY: lint-spell
lint-spell:
@go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
.PHONY: lint-spell-fix
lint-spell-fix:
@go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
.PHONY: lint-go .PHONY: lint-go
lint-go: lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)

View file

@ -216,16 +216,18 @@ func runServ(c *cli.Context) error {
} }
} }
// LowerCase and trim the repoPath as that's how they are stored.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
} }
username := strings.ToLower(rr[0]) username := rr[0]
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) reponame := strings.TrimSuffix(rr[1], ".git")
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if alphaDashDotPattern.MatchString(reponame) { if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)

View file

@ -29,7 +29,7 @@ menu:
[ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。 [ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。 标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`enviroment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。 在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`environment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。 包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。

View file

@ -243,10 +243,10 @@ documentation using:
make generate-swagger make generate-swagger
``` ```
You should validate your generated Swagger file and spell-check it with: You should validate your generated Swagger file:
```bash ```bash
make swagger-validate misspell-check make swagger-validate
``` ```
You should commit the changed swagger JSON file. The continuous integration You should commit the changed swagger JSON file. The continuous integration

View file

@ -228,10 +228,10 @@ Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images`
make generate-swagger make generate-swagger
``` ```
您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查 您应该验证生成的 Swagger 文件:
```bash ```bash
make swagger-validate misspell-check make swagger-validate
``` ```
您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成: 您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成:

View file

@ -27,13 +27,7 @@ Next, [install Node.js with npm](https://nodejs.org/en/download/) which is
required to build the JavaScript and CSS files. The minimum supported Node.js required to build the JavaScript and CSS files. The minimum supported Node.js
version is @minNodeVersion@ and the latest LTS version is recommended. version is @minNodeVersion@ and the latest LTS version is recommended.
**Note**: When executing make tasks that require external tools, like **Note**: Go version @minGoVersion@ or higher is required. However, it is recommended to
`make misspell-check`, Gitea will automatically download and build these as
necessary. To be able to use these, you must have the `"$GOPATH/bin"` directory
on the executable path. If you don't add the go bin directory to the
executable path, you will have to manage this yourself.
**Note 2**: Go version @minGoVersion@ or higher is required. However, it is recommended to
obtain the same version as our continuous integration, see the advice given in obtain the same version as our continuous integration, see the advice given in
[Hacking on Gitea](development/hacking-on-gitea.md) [Hacking on Gitea](development/hacking-on-gitea.md)

View file

@ -21,9 +21,7 @@ menu:
接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/) 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。 接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/) 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。
**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。 **注意**:需要 Go 版本 @minGoVersion@ 或更高版本。不过建议获取与我们的持续集成continuous integration, CI相同的版本请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过建议获取与我们的持续集成continuous integration, CI相同的版本请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
## 下载 ## 下载

View file

@ -42,7 +42,7 @@ The following package managers are currently supported:
| [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` | | [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` |
| [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` | | [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` |
| [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` | | [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` |
| [Swift](usage/packages/rubygems.md) | Swift | `swift` | | [Swift](usage/packages/swift.md) | Swift | `swift` |
| [Vagrant](usage/packages/vagrant.md) | - | `vagrant` | | [Vagrant](usage/packages/vagrant.md) | - | `vagrant` |
**The following paragraphs only apply if Packages are not globally disabled!** **The following paragraphs only apply if Packages are not globally disabled!**

View file

@ -26,7 +26,8 @@ To work with the Swift package registry, you need to use [swift](https://www.swi
To register the package registry and provide credentials, execute: To register the package registry and provide credentials, execute:
```shell ```shell
swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password} swift package-registry set https://gitea.example.com/api/packages/{owner}/swift
swift package-registry login https://gitea.example.com/api/packages/{owner}/swift --username {username} --password {password}
``` ```
| Placeholder | Description | | Placeholder | Description |

View file

@ -44,12 +44,12 @@ func fatalTestError(fmtStr string, args ...any) {
} }
// InitSettings initializes config provider and load common settings for tests // InitSettings initializes config provider and load common settings for tests
func InitSettings(extraConfigs ...string) { func InitSettings() {
if setting.CustomConf == "" { if setting.CustomConf == "" {
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
_ = os.Remove(setting.CustomConf) _ = os.Remove(setting.CustomConf)
} }
setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n")) setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings() setting.LoadCommonSettings()
if err := setting.PrepareAppDataPath(); err != nil { if err := setting.PrepareAppDataPath(); err != nil {

View file

@ -271,7 +271,7 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error
return time.Time{}, err return time.Time{}, err
} }
commitTime := strings.TrimSpace(stdout) commitTime := strings.TrimSpace(stdout)
return time.Parse(GitTimeLayout, commitTime) return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
} }
// DivergeObject represents commit count diverging commits // DivergeObject represents commit count diverging commits

View file

@ -183,11 +183,7 @@ func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, er
} }
} }
tag.Tagger, err = newSignatureFromCommitline([]byte(ref["creator"])) tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
if err != nil {
return nil, fmt.Errorf("parse tagger: %w", err)
}
tag.Message = ref["contents"] tag.Message = ref["contents"]
// strip PGP signature if present in contents field // strip PGP signature if present in contents field
pgpStart := strings.Index(tag.Message, beginpgp) pgpStart := strings.Index(tag.Message, beginpgp)

View file

@ -227,7 +227,7 @@ func TestRepository_parseTagRef(t *testing.T) {
ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Type: "commit", Type: "commit",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n",
Signature: nil, Signature: nil,
}, },
@ -256,7 +256,7 @@ func TestRepository_parseTagRef(t *testing.T) {
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n",
Signature: nil, Signature: nil,
}, },
@ -314,7 +314,7 @@ qbHDASXl
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md",
Signature: &CommitGPGSignature{ Signature: &CommitGPGSignature{
Signature: `-----BEGIN PGP SIGNATURE----- Signature: `-----BEGIN PGP SIGNATURE-----
@ -363,14 +363,3 @@ Add changelog of v1.9.1 (#7859)
}) })
} }
} }
func parseAuthorLine(t *testing.T, committer string) *Signature {
t.Helper()
sig, err := newSignatureFromCommitline([]byte(committer))
if err != nil {
t.Fatalf("parse author line '%s': %v", committer, err)
}
return sig
}

View file

@ -4,7 +4,46 @@
package git package git
const ( import (
// GitTimeLayout is the (default) time layout used by git. "strconv"
GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700" "strings"
"time"
"code.gitea.io/gitea/modules/log"
) )
// Helper to get a signature from the commit line, which looks like:
//
// full name <user@example.com> 1378823654 +0200
//
// Haven't found the official reference for the standard format yet.
// This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time.
func parseSignatureFromCommitLine(line string) *Signature {
sig := &Signature{}
s1, sx, ok1 := strings.Cut(line, " <")
s2, s3, ok2 := strings.Cut(sx, "> ")
if !ok1 || !ok2 {
sig.Name = line
return sig
}
sig.Name, sig.Email = s1, s2
if strings.Count(s3, " ") == 1 {
ts, tz, _ := strings.Cut(s3, " ")
seconds, _ := strconv.ParseInt(ts, 10, 64)
if tzTime, err := time.Parse("-0700", tz); err == nil {
sig.When = time.Unix(seconds, 0).In(tzTime.Location())
}
} else {
// the old gitea code tried to parse the date in a few different formats, but it's not clear why.
// according to public document, only the standard format "timestamp timezone" could be found, so drop other formats.
log.Error("suspicious commit line format: %q", line)
for _, fmt := range []string{ /*"Mon Jan _2 15:04:05 2006 -0700"*/ } {
if t, err := time.Parse(fmt, s3); err == nil {
sig.When = t
break
}
}
}
return sig
}

View file

@ -7,52 +7,8 @@
package git package git
import ( import (
"bytes"
"strconv"
"strings"
"time"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
) )
// Signature represents the Author or Committer information. // Signature represents the Author or Committer information.
type Signature = object.Signature type Signature = object.Signature
// Helper to get a signature from the commit line, which looks like these:
//
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
//
// but without the "author " at the beginning (this method should)
// be used for author and committer.
//
// FIXME: include timezone for timestamp!
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
sig := new(Signature)
emailStart := bytes.IndexByte(line, '<')
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
}
emailEnd := bytes.IndexByte(line, '>')
sig.Email = string(line[emailStart+1 : emailEnd])
// Check date format.
if len(line) > emailEnd+2 {
firstChar := line[emailEnd+2]
if firstChar >= 48 && firstChar <= 57 {
timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
seconds, _ := strconv.ParseInt(timestring, 10, 64)
sig.When = time.Unix(seconds, 0)
} else {
sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
if err != nil {
return nil, err
}
}
} else {
// Fall back to unix 0 time
sig.When = time.Unix(0, 0)
}
return sig, nil
}

View file

@ -7,21 +7,17 @@
package git package git
import ( import (
"bytes"
"fmt" "fmt"
"strconv"
"strings"
"time" "time"
"code.gitea.io/gitea/modules/util"
) )
// Signature represents the Author or Committer information. // Signature represents the Author, Committer or Tagger information.
type Signature struct { type Signature struct {
// Name represents a person name. It is an arbitrary string. Name string // the committer name, it can be anything
Name string Email string // the committer email, it can be anything
// Email is an email, but it cannot be assumed to be well-formed. When time.Time // the timestamp of the signature
Email string
// When is the timestamp of the signature.
When time.Time
} }
func (s *Signature) String() string { func (s *Signature) String() string {
@ -30,71 +26,5 @@ func (s *Signature) String() string {
// Decode decodes a byte array representing a signature to signature // Decode decodes a byte array representing a signature to signature
func (s *Signature) Decode(b []byte) { func (s *Signature) Decode(b []byte) {
sig, _ := newSignatureFromCommitline(b) *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b))
s.Email = sig.Email
s.Name = sig.Name
s.When = sig.When
}
// Helper to get a signature from the commit line, which looks like these:
//
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
//
// but without the "author " at the beginning (this method should)
// be used for author and committer.
// FIXME: there are a lot of "return sig, err" (but the err is also nil), that's the old behavior, to avoid breaking
func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
sig = new(Signature)
emailStart := bytes.LastIndexByte(line, '<')
emailEnd := bytes.LastIndexByte(line, '>')
if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
return sig, err
}
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
}
sig.Email = string(line[emailStart+1 : emailEnd])
hasTime := emailEnd+2 < len(line)
if !hasTime {
return sig, err
}
// Check date format.
firstChar := line[emailEnd+2]
if firstChar >= 48 && firstChar <= 57 {
idx := bytes.IndexByte(line[emailEnd+2:], ' ')
if idx < 0 {
return sig, err
}
timestring := string(line[emailEnd+2 : emailEnd+2+idx])
seconds, _ := strconv.ParseInt(timestring, 10, 64)
sig.When = time.Unix(seconds, 0)
idx += emailEnd + 3
if idx >= len(line) || idx+5 > len(line) {
return sig, err
}
timezone := string(line[idx : idx+5])
tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64)
tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64)
if err1 != nil || err2 != nil {
return sig, err
}
if tzhours < 0 {
tzmins *= -1
}
tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
sig.When = sig.When.In(tz)
} else {
sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
if err != nil {
return sig, err
}
}
return sig, err
} }

View file

@ -0,0 +1,47 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestParseSignatureFromCommitLine(t *testing.T) {
tests := []struct {
line string
want *Signature
}{
{
line: "a b <c@d.com> 12345 +0100",
want: &Signature{
Name: "a b",
Email: "c@d.com",
When: time.Unix(12345, 0).In(time.FixedZone("", 3600)),
},
},
{
line: "bad line",
want: &Signature{Name: "bad line"},
},
{
line: "bad < line",
want: &Signature{Name: "bad < line"},
},
{
line: "bad > line",
want: &Signature{Name: "bad > line"},
},
{
line: "bad-line <name@example.com>",
want: &Signature{Name: "bad-line <name@example.com>"},
},
}
for _, test := range tests {
got := parseSignatureFromCommitLine(test.line)
assert.EqualValues(t, test.want, got)
}
}

View file

@ -7,6 +7,8 @@ import (
"bytes" "bytes"
"sort" "sort"
"strings" "strings"
"code.gitea.io/gitea/modules/util"
) )
const ( const (
@ -59,11 +61,7 @@ l:
// A commit can have one or more parents // A commit can have one or more parents
tag.Type = string(line[spacepos+1:]) tag.Type = string(line[spacepos+1:])
case "tagger": case "tagger":
sig, err := newSignatureFromCommitline(line[spacepos+1:]) tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:]))
if err != nil {
return nil, err
}
tag.Tagger = sig
} }
nextline += eol + 1 nextline += eol + 1
case eol == 0: case eol == 0:

View file

@ -180,11 +180,17 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
} }
if len(reqs) > 0 { if len(reqs) > 0 {
_, err := b.inner.Client.Bulk(). esBatchSize := 50
Index(b.inner.VersionedIndexName()).
Add(reqs...). for i := 0; i < len(reqs); i += esBatchSize {
Do(ctx) _, err := b.inner.Client.Bulk().
return err Index(b.inner.VersionedIndexName()).
Add(reqs[i:min(i+esBatchSize, len(reqs))]...).
Do(ctx)
if err != nil {
return err
}
}
} }
return nil return nil
} }

View file

@ -197,7 +197,7 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
// NewConfigProviderFromFile load configuration from file. // NewConfigProviderFromFile load configuration from file.
// NOTE: do not print any log except error. // NOTE: do not print any log except error.
func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) { func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
cfg := ini.Empty(configProviderLoadOptions()) cfg := ini.Empty(configProviderLoadOptions())
loadedFromEmpty := true loadedFromEmpty := true
@ -214,12 +214,6 @@ func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvi
} }
} }
for _, s := range extraConfigs {
if err := cfg.Append([]byte(s)); err != nil {
return nil, fmt.Errorf("unable to append more config: %v", err)
}
}
cfg.NameMapper = ini.SnackCase cfg.NameMapper = ini.SnackCase
return &iniConfigProvider{ return &iniConfigProvider{
file: file, file: file,

View file

@ -92,9 +92,9 @@ func PrepareAppDataPath() error {
return nil return nil
} }
func InitCfgProvider(file string, extraConfigs ...string) { func InitCfgProvider(file string) {
var err error var err error
if CfgProvider, err = NewConfigProviderFromFile(file, extraConfigs...); err != nil { if CfgProvider, err = NewConfigProviderFromFile(file); err != nil {
log.Fatal("Unable to init config provider from %q: %v", file, err) log.Fatal("Unable to init config provider from %q: %v", file, err)
} }
CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls

View file

@ -1030,10 +1030,10 @@ reactions_more=再加载 %d
unit_disabled=站点管理员已禁用此仓库单元。 unit_disabled=站点管理员已禁用此仓库单元。
language_other=其它 language_other=其它
adopt_search=输入用户名以搜索未被收录的仓库... (留空以查找全部) adopt_search=输入用户名以搜索未被收录的仓库... (留空以查找全部)
adopt_preexisting_label=收录文件 adopt_preexisting_label=收录仓库
adopt_preexisting=收录已存在的文件 adopt_preexisting=收录已存在的仓库
adopt_preexisting_content=从 %s 创建仓库 adopt_preexisting_content=从 %s 创建仓库
adopt_preexisting_success=从 %s 收录文件并创建仓库成功 adopt_preexisting_success=从 %s 收录仓库成功
delete_preexisting_label=刪除 delete_preexisting_label=刪除
delete_preexisting=删除已存在的文件 delete_preexisting=删除已存在的文件
delete_preexisting_content=删除 %s 中的文件 delete_preexisting_content=删除 %s 中的文件

302
package-lock.json generated
View file

@ -30,7 +30,6 @@
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.9", "katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
"mermaid": "10.7.0", "mermaid": "10.7.0",
"mini-css-extract-plugin": "2.8.0", "mini-css-extract-plugin": "2.8.0",
"minimatch": "9.0.3", "minimatch": "9.0.3",
@ -2261,13 +2260,13 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.20.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.20.0", "@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.20.0" "@typescript-eslint/visitor-keys": "6.21.0"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -2278,9 +2277,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.20.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -2291,13 +2290,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.20.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.20.0", "@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.20.0", "@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -2319,17 +2318,17 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.20.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
"integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.20.0", "@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.20.0", "@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.20.0", "@typescript-eslint/typescript-estree": "6.21.0",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -2344,12 +2343,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.20.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.20.0", "@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -2449,9 +2448,9 @@
} }
}, },
"node_modules/@vitest/snapshot/node_modules/magic-string": { "node_modules/@vitest/snapshot/node_modules/magic-string": {
"version": "0.30.6", "version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
"integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
@ -4372,17 +4371,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/diff": { "node_modules/diff": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
@ -6993,224 +6981,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/lightningcss": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz",
"integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==",
"dependencies": {
"detect-libc": "^1.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.23.0",
"lightningcss-darwin-x64": "1.23.0",
"lightningcss-freebsd-x64": "1.23.0",
"lightningcss-linux-arm-gnueabihf": "1.23.0",
"lightningcss-linux-arm64-gnu": "1.23.0",
"lightningcss-linux-arm64-musl": "1.23.0",
"lightningcss-linux-x64-gnu": "1.23.0",
"lightningcss-linux-x64-musl": "1.23.0",
"lightningcss-win32-x64-msvc": "1.23.0"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz",
"integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz",
"integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz",
"integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz",
"integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz",
"integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz",
"integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz",
"integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz",
"integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-loader": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lightningcss-loader/-/lightningcss-loader-2.1.0.tgz",
"integrity": "sha512-mB+M/lvs/GdXT4yc8ZiNgLUAbYpPI9grDyC3ybz/Zo6s4GZv53iZnLTnkJT/Qm3Sh89dbFUm+omoHFXCfZtcXw==",
"dependencies": {
"browserslist": "^4.21.4",
"lightningcss": "^1.16.0",
"webpack-sources": "^3.2.3"
},
"peerDependencies": {
"webpack": ">=5"
}
},
"node_modules/lightningcss-loader/node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz",
"integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -10620,12 +10390,12 @@
"integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==" "integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ=="
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "1.0.3", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
"integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16.13.0" "node": ">=16"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=4.2.0" "typescript": ">=4.2.0"
@ -10769,9 +10539,9 @@
"dev": true "dev": true
}, },
"node_modules/ufo": { "node_modules/ufo": {
"version": "1.3.2", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
"integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
"dev": true "dev": true
}, },
"node_modules/uint8-to-base64": { "node_modules/uint8-to-base64": {
@ -11149,9 +10919,9 @@
} }
}, },
"node_modules/vitest/node_modules/magic-string": { "node_modules/vitest/node_modules/magic-string": {
"version": "0.30.6", "version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
"integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"

View file

@ -29,7 +29,6 @@
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.9", "katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
"mermaid": "10.7.0", "mermaid": "10.7.0",
"mini-css-extract-plugin": "2.8.0", "mini-css-extract-plugin": "2.8.0",
"minimatch": "9.0.3", "minimatch": "9.0.3",
@ -86,8 +85,6 @@
"vitest": "1.2.2" "vitest": "1.2.2"
}, },
"browserslist": [ "browserslist": [
"defaults", "defaults"
"not ie > 0",
"not ie_mob > 0"
] ]
} }

View file

@ -157,7 +157,7 @@ func EnumeratePackageVersions(ctx *context.Context) {
} }
type Resource struct { type Resource struct {
Name string `json:"id"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Checksum string `json:"checksum"` Checksum string `json:"checksum"`
} }

View file

@ -61,17 +61,17 @@ func List(ctx *context.Context) {
var workflows []Workflow var workflows []Workflow
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil { if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("IsEmpty", err)
return return
} else if !empty { } else if !empty {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("GetBranchCommit", err)
return return
} }
entries, err := actions.ListWorkflows(commit) entries, err := actions.ListWorkflows(commit)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("ListWorkflows", err)
return return
} }
@ -95,7 +95,7 @@ func List(ctx *context.Context) {
workflow := Workflow{Entry: *entry} workflow := Workflow{Entry: *entry}
content, err := actions.GetContentFromEntry(entry) content, err := actions.GetContentFromEntry(entry)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("GetContentFromEntry", err)
return return
} }
wf, err := model.ReadWorkflow(bytes.NewReader(content)) wf, err := model.ReadWorkflow(bytes.NewReader(content))
@ -172,7 +172,7 @@ func List(ctx *context.Context) {
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts) runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("FindAndCount", err)
return return
} }
@ -181,7 +181,7 @@ func List(ctx *context.Context) {
} }
if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil { if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("LoadTriggerUser", err)
return return
} }
@ -189,7 +189,7 @@ func List(ctx *context.Context) {
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.ServerError("GetActors", err)
return return
} }
ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors) ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)

View file

@ -74,6 +74,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
"Release": rel, "Release": rel,
"Subject": subject, "Subject": subject,
"Language": locale.Language(), "Language": locale.Language(),
"Link": rel.HTMLURL(),
} }
var mailBody bytes.Buffer var mailBody bytes.Buffer

View file

@ -1,7 +1,7 @@
{{/* Attributes: {{/* Attributes:
* root * root
* ContainerClasses * ContainerClasses
* (TODO: search "branch_dropdown" in the template direcotry) * (TODO: search "branch_dropdown" in the template directory)
*/}} */}}
{{$defaultSelectedRefName := $.root.BranchName}} {{$defaultSelectedRefName := $.root.BranchName}}
{{if and .root.IsViewTag (not .noTag)}} {{if and .root.IsViewTag (not .noTag)}}

View file

@ -371,7 +371,7 @@
<div class="timeline-item event"> <div class="timeline-item event">
{{if .OriginalAuthor}} {{if .OriginalAuthor}}
{{else}} {{else}}
{{/* Some timeline avatars need a offset to correctly allign with their speech {{/* Some timeline avatars need a offset to correctly align with their speech
bubble. The condition depends on review type and for positive reviews whether bubble. The condition depends on review type and for positive reviews whether
there is a comment element or not */}} there is a comment element or not */}}
<a class="timeline-avatar{{if or (and (eq .Review.Type 1) (or .Content .Attachments)) (and (eq .Review.Type 2) (or .Content .Attachments)) (eq .Review.Type 3)}} timeline-avatar-offset{{end}}"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}> <a class="timeline-avatar{{if or (and (eq .Review.Type 1) (or .Content .Attachments)) (and (eq .Review.Type 2) (or .Content .Attachments)) (eq .Review.Type 3)}} timeline-avatar-offset{{end}}"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>

View file

@ -8,8 +8,8 @@
{{range $idx, $release := .Releases}} {{range $idx, $release := .Releases}}
<li class="ui grid"> <li class="ui grid">
<div class="ui four wide column meta"> <div class="ui four wide column meta">
<a class="muted" href="{{if not .Sha1}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}}</a> <a class="muted" href="{{if not (and .Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}}</a>
{{if .Sha1}} {{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a> <a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a>
{{template "repo/branch_dropdown" dict "root" $ "release" .}} {{template "repo/branch_dropdown" dict "root" $ "release" .}}
{{end}} {{end}}
@ -22,36 +22,18 @@
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> <span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span>
{{else if .IsPrerelease}} {{else if .IsPrerelease}}
<span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> <span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span>
{{else if not .IsTag}} {{else}}
<span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span> <span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span>
{{end}} {{end}}
</h4> </h4>
<div> <div>
{{if and $.CanCreateRelease (not .IsTag)}} {{if $.CanCreateRelease}}
<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow"> <a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow">
{{svg "octicon-pencil"}} {{svg "octicon-pencil"}}
</a> </a>
{{end}} {{end}}
</div> </div>
</div> </div>
{{if .IsTag}}
<p class="text grey">
{{if gt .Publisher.ID 0}}
<span class="author">
{{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}}
<a href="{{.Publisher.HomeLink}}">{{.Publisher.Name}}</a>
</span>
<span class="released">
{{ctx.Locale.Tr "repo.tagged_this"}}
</span>
{{if .CreatedUnix}}
<span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span>
{{end}}
|
{{end}}
<span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.tag.ahead.target" .TargetBehind}}</span>
</p>
{{else}}
<p class="text grey"> <p class="text grey">
<span class="author"> <span class="author">
{{if .OriginalAuthor}} {{if .OriginalAuthor}}
@ -69,11 +51,10 @@
{{if .CreatedUnix}} {{if .CreatedUnix}}
<span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span> <span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span>
{{end}} {{end}}
{{if not .IsDraft}} {{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}}</span> | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}}</span>
{{end}} {{end}}
</p> </p>
{{end}}
<div class="markup desc"> <div class="markup desc">
{{Str2html .Note}} {{Str2html .Note}}
</div> </div>

View file

@ -5,7 +5,7 @@ import {stringPlugin} from 'vite-string-plugin';
export default defineConfig({ export default defineConfig({
test: { test: {
include: ['web_src/**/*.test.js'], include: ['web_src/**/*.test.js'],
setupFiles: ['./web_src/js/test/setup.js'], setupFiles: ['web_src/js/vitest.setup.js'],
environment: 'jsdom', environment: 'jsdom',
testTimeout: 20000, testTimeout: 20000,
open: false, open: false,

View file

@ -1,5 +1,6 @@
// for performance considerations, it only uses performant syntax import {isDocumentFragmentOrElementNode} from '../utils/dom.js';
// for performance considerations, it only uses performant syntax
function attachDirAuto(el) { function attachDirAuto(el) {
if (el.type !== 'hidden' && if (el.type !== 'hidden' &&
el.type !== 'checkbox' && el.type !== 'checkbox' &&
@ -18,7 +19,7 @@ export function initDirAuto() {
const len = mutation.addedNodes.length; const len = mutation.addedNodes.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const addedNode = mutation.addedNodes[i]; const addedNode = mutation.addedNodes[i];
if (addedNode.nodeType !== Node.ELEMENT_NODE && addedNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) continue; if (!isDocumentFragmentOrElementNode(addedNode)) continue;
if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') attachDirAuto(addedNode); if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') attachDirAuto(addedNode);
const children = addedNode.querySelectorAll('input, textarea'); const children = addedNode.querySelectorAll('input, textarea');
const len = children.length; const len = children.length;

View file

@ -1,4 +1,5 @@
import tippy, {followCursor} from 'tippy.js'; import tippy, {followCursor} from 'tippy.js';
import {isDocumentFragmentOrElementNode} from '../utils/dom.js';
const visibleInstances = new Set(); const visibleInstances = new Set();
@ -136,8 +137,6 @@ function attachChildrenLazyTooltip(target) {
} }
} }
const elementNodeTypes = new Set([Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE]);
export function initGlobalTooltips() { export function initGlobalTooltips() {
// use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed // use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed
const observerConnect = (observer) => observer.observe(document, { const observerConnect = (observer) => observer.observe(document, {
@ -152,11 +151,10 @@ export function initGlobalTooltips() {
if (mutation.type === 'childList') { if (mutation.type === 'childList') {
// mainly for Vue components and AJAX rendered elements // mainly for Vue components and AJAX rendered elements
for (const el of mutation.addedNodes) { for (const el of mutation.addedNodes) {
if (elementNodeTypes.has(el.nodeType)) { if (!isDocumentFragmentOrElementNode(el)) continue;
attachChildrenLazyTooltip(el); attachChildrenLazyTooltip(el);
if (el.hasAttribute('data-tooltip-content')) { if (el.hasAttribute('data-tooltip-content')) {
attachLazyTooltip(el); attachLazyTooltip(el);
}
} }
} }
} else if (mutation.type === 'attributes') { } else if (mutation.type === 'attributes') {

View file

@ -59,6 +59,17 @@ export function onDomReady(cb) {
} }
} }
// checks whether an element is owned by the current document, and whether it is a document fragment or element node
// if it is, it means it is a "normal" element managed by us, which can be modified safely.
export function isDocumentFragmentOrElementNode(el) {
try {
return el.ownerDocument === document && el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
} catch {
// in case the el is not in the same origin, then the access to nodeType would fail
return false;
}
}
// autosize a textarea to fit content. Based on // autosize a textarea to fit content. Based on
// https://github.com/github/textarea-autosize // https://github.com/github/textarea-autosize
// --------------------------------------------------------------------- // ---------------------------------------------------------------------

View file

@ -1,17 +1,21 @@
// Convert an absolute or relative URL to an absolute URL with the current origin // Convert an absolute or relative URL to an absolute URL with the current origin
export function toOriginUrl(urlStr) {
try {
// only process absolute HTTP/HTTPS URL or relative URLs ('/xxx' or '//host/xxx')
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}
window.customElements.define('gitea-origin-url', class extends HTMLElement { window.customElements.define('gitea-origin-url', class extends HTMLElement {
connectedCallback() { connectedCallback() {
const urlStr = this.getAttribute('data-url'); this.textContent = toOriginUrl(this.getAttribute('data-url'));
try {
// only process absolute HTTP/HTTPS URL or relative URLs ('/xxx' or '//host/xxx')
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const url = new URL(urlStr, window.origin);
url.protocol = window.location.protocol;
url.host = window.location.host;
this.textContent = url.toString();
return;
}
} catch {}
this.textContent = urlStr;
} }
}); });

View file

@ -0,0 +1,17 @@
import {toOriginUrl} from './GiteaOriginUrl.js';
test('toOriginUrl', () => {
const oldLocation = window.location;
for (const origin of ['https://example.com', 'https://example.com:3000']) {
window.location = new URL(`${origin}/`);
expect(toOriginUrl('/')).toEqual(`${origin}/`);
expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`);
}
window.location = oldLocation;
});

View file

@ -55,12 +55,6 @@ const filterCssImport = (url, ...args) => {
return true; return true;
}; };
// in case lightningcss fails to load, fall back to esbuild for css minify
let LightningCssMinifyPlugin;
try {
({LightningCssMinifyPlugin} = await import('lightningcss-loader'));
} catch {}
/** @type {import("webpack").Configuration} */ /** @type {import("webpack").Configuration} */
export default { export default {
mode: isProduction ? 'production' : 'development', mode: isProduction ? 'production' : 'development',
@ -110,12 +104,9 @@ export default {
new EsbuildPlugin({ new EsbuildPlugin({
target: 'es2020', target: 'es2020',
minify: true, minify: true,
css: !LightningCssMinifyPlugin, css: true,
legalComments: 'none', legalComments: 'none',
}), }),
LightningCssMinifyPlugin && new LightningCssMinifyPlugin({
sourceMap: sourceMaps === 'true',
}),
], ],
splitChunks: { splitChunks: {
chunks: 'async', chunks: 'async',