Merge branch 'main' into lunny/add_comment_move_issue_column

This commit is contained in:
Lunny Xiao 2024-04-08 14:42:40 +08:00
commit 1c478e6e8b
199 changed files with 3165 additions and 3533 deletions

View File

@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true
ignorePatterns:
- /web_src/js/vendor
- /web_src/fomantic
parserOptions:
sourceType: module
@ -536,7 +537,7 @@ rules:
no-underscore-dangle: [0]
no-unexpected-multiline: [2]
no-unmodified-loop-condition: [2]
no-unneeded-ternary: [0]
no-unneeded-ternary: [2]
no-unreachable-loop: [2]
no-unreachable: [2]
no-unsafe-finally: [2]
@ -715,12 +716,14 @@ rules:
unicorn/import-style: [0]
unicorn/new-for-builtins: [2]
unicorn/no-abusive-eslint-disable: [0]
unicorn/no-anonymous-default-export: [0]
unicorn/no-array-callback-reference: [0]
unicorn/no-array-for-each: [2]
unicorn/no-array-method-this-argument: [2]
unicorn/no-array-push-push: [2]
unicorn/no-array-reduce: [2]
unicorn/no-await-expression-member: [0]
unicorn/no-await-in-promise-methods: [2]
unicorn/no-console-spaces: [0]
unicorn/no-document-cookie: [2]
unicorn/no-empty-file: [2]
@ -737,6 +740,7 @@ rules:
unicorn/no-null: [0]
unicorn/no-object-as-default-parameter: [0]
unicorn/no-process-exit: [0]
unicorn/no-single-promise-in-promise-methods: [2]
unicorn/no-static-only-class: [2]
unicorn/no-thenable: [2]
unicorn/no-this-assignment: [2]

7
.github/labeler.yml vendored
View File

@ -4,13 +4,6 @@ modifies/docs:
- "**/*.md"
- "docs/**"
modifies/frontend:
- changed-files:
- any-glob-to-any-file:
- "web_src/**"
- "tailwind.config.js"
- "webpack.config.js"
modifies/templates:
- changed-files:
- all-globs-to-any-file:

View File

@ -4,6 +4,8 @@
/modules/options/bindata.go
/modules/public/bindata.go
/modules/templates/bindata.go
/vendor
/options/gitignore
/options/license
/public/assets
/vendor
node_modules

View File

@ -6,14 +6,13 @@ package cmd
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/dump"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -25,89 +24,17 @@ import (
"github.com/urfave/cli/v2"
)
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
if verbose {
log.Info("Adding file %s", customName)
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return addReader(w, file, fileInfo, filePath, verbose)
}
func isSubdir(upper, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
return true, nil
}
return false, nil
}
type outputType struct {
Enum []string
Default string
selected string
}
func (o outputType) Join() string {
return strings.Join(o.Enum, ", ")
}
func (o *outputType) Set(value string) error {
for _, enum := range o.Enum {
if enum == value {
o.selected = value
return nil
}
}
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
}
return o.selected
}
var outputTypeEnum = &outputType{
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"},
Default: "zip",
}
// CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{
Name: "dump",
Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file.
It can be used for backup and capture Gitea server image to send to maintainer`,
Action: runDump,
Name: "dump",
Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`,
Action: runDump,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`,
},
&cli.BoolFlag{
Name: "verbose",
@ -160,64 +87,52 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Name: "skip-index",
Usage: "Skip bleve index data",
},
&cli.GenericFlag{
&cli.StringFlag{
Name: "type",
Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")),
},
},
}
func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
log.Fatal(format, args...)
}
func runDump(ctx *cli.Context) error {
var file *os.File
fileName := ctx.String("file")
outType := ctx.String("type")
if fileName == "-" {
file = os.Stdout
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
} else {
for _, suffix := range outputTypeEnum.Enum {
if strings.HasSuffix(fileName, "."+suffix) {
fileName = strings.TrimSuffix(fileName, "."+suffix)
break
}
}
fileName += "." + outType
}
setting.MustInstalled()
// make sure we are logging to the console no matter what the configuration tells us do to
// FIXME: don't use CfgProvider directly
if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Setting logging mode to console failed: %v", err)
}
if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil {
fatal("Setting console logger to stderr failed: %v", err)
}
// Set loglevel to Warn if quiet-mode is requested
if ctx.Bool("quiet") {
if _, err := setting.CfgProvider.Section("log.console").NewKey("LEVEL", "Warn"); err != nil {
fatal("Setting console log-level failed: %v", err)
}
}
if !setting.InstallLock {
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
return fmt.Errorf("gitea is not initialized")
}
setting.LoadSettings() // cannot access session settings otherwise
quite := ctx.Bool("quiet")
verbose := ctx.Bool("verbose")
if verbose && ctx.Bool("quiet") {
return fmt.Errorf("--quiet and --verbose cannot both be set")
if verbose && quite {
fatal("Option --quiet and --verbose cannot both be set")
}
// outFileName is either "-" or a file name (will be made absolute)
outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
if outType == "" {
fatal("Invalid output type")
}
outFile := os.Stdout
if outFileName != "-" {
var err error
if outFileName, err = filepath.Abs(outFileName); err != nil {
fatal("Unable to get absolute path of dump file: %v", err)
}
if exist, _ := util.IsExist(outFileName); exist {
fatal("Dump file %q exists", outFileName)
}
if outFile, err = os.Create(outFileName); err != nil {
fatal("Unable to create dump file %q: %v", outFileName, err)
}
defer outFile.Close()
}
setupConsoleLogger(util.Iif(quite, log.WARN, log.INFO), log.CanColorStderr, os.Stderr)
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise
stdCtx, cancel := installSignals()
defer cancel()
@ -226,44 +141,32 @@ func runDump(ctx *cli.Context) error {
return err
}
if err := storage.Init(); err != nil {
if err = storage.Init(); err != nil {
return err
}
if file == nil {
file, err = os.Create(fileName)
if err != nil {
fatal("Unable to open %s: %v", fileName, err)
}
}
defer file.Close()
absFileName, err := filepath.Abs(fileName)
if err != nil {
return err
}
var iface any
if fileName == "-" {
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
} else {
iface, err = archiver.ByExtension(fileName)
}
archiverGeneric, err := archiver.ByExtension("." + outType)
if err != nil {
fatal("Unable to get archiver for extension: %v", err)
}
w, _ := iface.(archiver.Writer)
if err := w.Create(file); err != nil {
archiverWriter := archiverGeneric.(archiver.Writer)
if err := archiverWriter.Create(outFile); err != nil {
fatal("Creating archiver.Writer failed: %v", err)
}
defer w.Close()
defer archiverWriter.Close()
dumper := &dump.Dumper{
Writer: archiverWriter,
Verbose: verbose,
}
dumper.GlobalExcludeAbsPath(outFileName)
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
log.Info("Skip dumping local repositories")
} else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
if err := dumper.AddRecursiveExclude("repos", setting.RepoRootPath, nil); err != nil {
fatal("Failed to include repositories: %v", err)
}
@ -276,8 +179,7 @@ func runDump(ctx *cli.Context) error {
if err != nil {
return err
}
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
}); err != nil {
fatal("Failed to dump LFS objects: %v", err)
}
@ -310,15 +212,13 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to dump database: %v", err)
}
if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
}
if len(setting.CustomConf) > 0 {
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
@ -326,8 +226,8 @@ func runDump(ctx *cli.Context) error {
} else {
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
if is, _ := dump.IsSubdir(setting.AppDataPath, setting.CustomPath); !is {
if err := dumper.AddRecursiveExclude("custom", setting.CustomPath, nil); err != nil {
fatal("Failed to include custom: %v", err)
}
} else {
@ -364,8 +264,7 @@ func runDump(ctx *cli.Context) error {
excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.Log.RootPath)
excludes = append(excludes, absFileName)
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
fatal("Failed to include data directory: %v", err)
}
}
@ -377,8 +276,7 @@ func runDump(ctx *cli.Context) error {
if err != nil {
return err
}
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
}); err != nil {
fatal("Failed to dump attachments: %v", err)
}
@ -392,8 +290,7 @@ func runDump(ctx *cli.Context) error {
if err != nil {
return err
}
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
}); err != nil {
fatal("Failed to dump packages: %v", err)
}
@ -409,80 +306,23 @@ func runDump(ctx *cli.Context) error {
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
}
if isExist {
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
if err := dumper.AddRecursiveExclude("log", setting.Log.RootPath, nil); err != nil {
fatal("Failed to include log: %v", err)
}
}
}
if fileName != "-" {
if err = w.Close(); err != nil {
_ = util.Remove(fileName)
fatal("Failed to save %s: %v", fileName, err)
if outFileName == "-" {
log.Info("Finish dumping to stdout")
} else {
if err = archiverWriter.Close(); err != nil {
_ = os.Remove(outFileName)
fatal("Failed to save %q: %v", outFileName, err)
}
if err := os.Chmod(fileName, 0o600); err != nil {
if err = os.Chmod(outFileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err)
}
}
if fileName != "-" {
log.Info("Finish dumping in file %s", fileName)
} else {
log.Info("Finish dumping to stdout")
}
return nil
}
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
return err
}
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
return err
}
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
shouldAdd := file.Mode().IsRegular()
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
return err
}
}
}
log.Info("Finish dumping in file %s", outFileName)
}
return nil
}

View File

@ -114,7 +114,7 @@ func showWebStartupMessage(msg string) {
log.Info("* WorkPath: %s", setting.AppWorkPath)
log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg)
log.Info("%s", msg) // show startup message
}
func serveInstall(ctx *cli.Context) error {

View File

@ -2315,6 +2315,8 @@ LEVEL = Info
;SHOW_FOOTER_VERSION = true
;; Show template execution time in the footer
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
;; Show the "powered by" text in the footer
;SHOW_FOOTER_POWERED_BY = true
;; Generate sitemap. Defaults to `true`.
;ENABLE_SITEMAP = true
;; Enable/Disable RSS/Atom feed

View File

@ -1429,5 +1429,6 @@ Like `uses: https://gitea.com/actions/checkout@v4` or `uses: http://your-git-ser
- `SHOW_FOOTER_VERSION`: **true**: Show Gitea and Go version information in the footer.
- `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer.
- `SHOW_FOOTER_POWERED_BY`: **true**: Show the "powered by" text in the footer.
- `ENABLE_SITEMAP`: **true**: Generate sitemap.
- `ENABLE_FEED`: **true**: Enable/Disable RSS/Atom feed.

View File

@ -1353,5 +1353,6 @@ PROXY_HOSTS = *.github.com
- `SHOW_FOOTER_VERSION`: **true**: 在页面底部显示Gitea的版本。
- `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: 在页脚显示模板执行的时间。
- `SHOW_FOOTER_POWERED_BY`: **true**: 在页脚显示“由...提供动力”的文本。
- `ENABLE_SITEMAP`: **true**: 生成sitemap.
- `ENABLE_FEED`: **true**: 是否启用RSS/Atom

View File

@ -304,7 +304,8 @@ services:
- GITEA__mailer__ENABLED=true
- GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set}
- GITEA__mailer__PROTOCOL=smtps
- GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set}
- GITEA__mailer__SMTP_ADDR=${GITEA__mailer__SMTP_ADDR:?GITEA__mailer__SMTP_ADDR not set}
- GITEA__mailer__SMTP_PORT=${GITEA__mailer__SMTP_PORT:?GITEA__mailer__SMTP_PORT not set}
- GITEA__mailer__USER=${GITEA__mailer__USER:-apikey}
- GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}"""
```
@ -545,7 +546,7 @@ In this option, the idea is that the host SSH uses an `AuthorizedKeysCommand` in
```bash
cat <<"EOF" | sudo tee /home/git/docker-shell
#!/bin/sh
/usr/bin/docker exec -i --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@"
/usr/bin/docker exec -i -u git --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@"
EOF
sudo chmod +x /home/git/docker-shell
sudo usermod -s /home/git/docker-shell git
@ -560,7 +561,7 @@ Add the following block to `/etc/ssh/sshd_config`, on the host:
```bash
Match User git
AuthorizedKeysCommandUser git
AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k
AuthorizedKeysCommand /usr/bin/docker exec -i -u git gitea /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k
```
(From 1.16.0 you will not need to set the `-c /data/gitea/conf/app.ini` option.)

6
go.mod
View File

@ -105,11 +105,11 @@ require (
github.com/yuin/goldmark v1.7.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.21.0
golang.org/x/crypto v0.22.0
golang.org/x/image v0.15.0
golang.org/x/net v0.22.0
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.18.0
golang.org/x/sys v0.18.0
golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.19.0
google.golang.org/grpc v1.62.1

16
go.sum
View File

@ -846,8 +846,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
@ -881,8 +881,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -932,8 +932,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -943,8 +943,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -139,13 +139,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific
}
}
keyID := ""
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
}
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
}
keyID := tryGetKeyIDFromSignature(sig)
defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that

View File

@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) {
}
return sig, nil
}
func tryGetKeyIDFromSignature(sig *packet.Signature) string {
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
}
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
}
return ""
}

View File

@ -11,7 +11,9 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/keybase/go-crypto/openpgp/packet"
"github.com/stretchr/testify/assert"
)
@ -391,3 +393,13 @@ epiDVQ==
assert.Equal(t, time.Unix(1586105389, 0), expire)
}
}
func TestTryGetKeyIDFromSignature(t *testing.T) {
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
}))
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
}))
}

View File

@ -76,23 +76,14 @@ func calcFingerprintNative(publicKeyContent string) (string, error) {
// CalcFingerprint calculate public key's fingerprint
func CalcFingerprint(publicKeyContent string) (string, error) {
// Call the method based on configuration
var (
fnName, fp string
err error
)
if len(setting.SSH.KeygenPath) == 0 {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
useNative := setting.SSH.KeygenPath == ""
calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen)
fp, err := calcFn(publicKeyContent)
if err != nil {
if IsErrKeyUnableVerify(err) {
log.Info("%s", publicKeyContent)
return "", err
}
return "", fmt.Errorf("%s: %w", fnName, err)
return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err)
}
return fp, nil
}

View File

@ -10,21 +10,21 @@ import (
)
// CountOrphanedObjects count subjects with have no existing refobject anymore
func CountOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) (int64, error) {
func CountOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) (int64, error) {
return GetEngine(ctx).
Table("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}).
Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refObject + "`.id"}).
Select("COUNT(`" + subject + "`.`id`)").
Count()
}
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
func DeleteOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) error {
func DeleteOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) error {
subQuery := builder.Select("`"+subject+"`.id").
From("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"})
Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refObject + "`.id"})
b := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`")
_, err := GetEngine(ctx).Exec(b)
return err

View File

@ -0,0 +1,9 @@
-
id: 1
project_id: 1
issue_id: 1
-
id: 2
project_id: 1
issue_id: 1

View File

@ -577,6 +577,8 @@ var migrations = []Migration{
// Gitea 1.22.0 ends at 294
// v294 -> v295
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
// v295 -> v296
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
}

View File

@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
)
func TestMain(m *testing.M) {
base.MainTest(m)
}

View File

@ -1,22 +1,53 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"fmt"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
// CommentMetaData stores metadata for a comment, these data will not be changed once inserted into database
type CommentMetaData struct {
ProjectColumnID int64 `json:"project_column_id"`
ProjectColumnName string `json:"project_column_name"`
ProjectName string `json:"project_name"`
}
func AddCommentMetaDataColumn(x *xorm.Engine) error {
type Comment struct {
CommentMetaData CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
// AddUniqueIndexForProjectIssue adds unique indexes for project issue table
func AddUniqueIndexForProjectIssue(x *xorm.Engine) error {
// remove possible duplicated records in table project_issue
type result struct {
IssueID int64
ProjectID int64
Cnt int
}
var results []result
if err := x.Select("issue_id, project_id, count(*) as cnt").
Table("project_issue").
GroupBy("issue_id, project_id").
Having("count(*) > 1").
Find(&results); err != nil {
return err
}
for _, r := range results {
if x.Dialect().URI().DBType == schemas.MSSQL {
if _, err := x.Exec(fmt.Sprintf("delete from project_issue where id in (SELECT top %d id FROM project_issue WHERE issue_id = ? and project_id = ?)", r.Cnt-1), r.IssueID, r.ProjectID); err != nil {
return err
}
} else {
var ids []int64
if err := x.SQL("SELECT id FROM project_issue WHERE issue_id = ? and project_id = ? limit ?", r.IssueID, r.ProjectID, r.Cnt-1).Find(&ids); err != nil {
return err
}
if _, err := x.Table("project_issue").In("id", ids).Delete(); err != nil {
return err
}
}
}
return x.Sync(new(Comment))
// add unique index for project_issue table
type ProjectIssue struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX unique(s)"`
ProjectID int64 `xorm:"INDEX unique(s)"`
}
return x.Sync(new(ProjectIssue))
}

View File

@ -0,0 +1,52 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"slices"
"testing"
"code.gitea.io/gitea/models/migrations/base"
"github.com/stretchr/testify/assert"
"xorm.io/xorm/schemas"
)
func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
type ProjectIssue struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
ProjectID int64 `xorm:"INDEX"`
}
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(ProjectIssue))
defer deferable()
if x == nil || t.Failed() {
return
}
cnt, err := x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
assert.NoError(t, err)
assert.EqualValues(t, 2, cnt)
assert.NoError(t, AddUniqueIndexForProjectIssue(x))
cnt, err = x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
tables, err := x.DBMetas()
assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables))
found := false
for _, index := range tables[0].Indexes {
if index.Type == schemas.UniqueType {
found = true
slices.Equal(index.Cols, []string{"project_id", "issue_id"})
break
}
}
assert.True(t, found)
}

View File

@ -0,0 +1,22 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"xorm.io/xorm"
)
// CommentMetaData stores metadata for a comment, these data will not be changed once inserted into database
type CommentMetaData struct {
ProjectColumnID int64 `json:"project_column_id"`
ProjectColumnName string `json:"project_column_name"`
ProjectTitle string `json:"project_title"`
}
func AddCommentMetaDataColumn(x *xorm.Engine) error {
type Comment struct {
CommentMetaData CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
}
return x.Sync(new(Comment))
}

View File

@ -53,7 +53,7 @@ func (repo *Repository) IsDependenciesEnabled(ctx context.Context) bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
log.Trace("%s", err)
log.Trace("IsDependenciesEnabled: %v", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies

View File

@ -256,14 +256,6 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) {
return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
}
// DeleteInactiveEmailAddresses deletes inactive email addresses
func DeleteInactiveEmailAddresses(ctx context.Context) error {
_, err := db.GetEngine(ctx).
Where("is_activated = ?", false).
Delete(new(EmailAddress))
return err
}
// ActivateEmail activates the email address to given user.
func ActivateEmail(ctx context.Context, email *EmailAddress) error {
ctx, committer, err := db.TxContext(ctx)

View File

@ -4,6 +4,7 @@
package charset
import (
"regexp"
"strings"
"testing"
@ -156,13 +157,16 @@ func TestEscapeControlReader(t *testing.T) {
tests = append(tests, test)
}
re := regexp.MustCompile(`repo.ambiguous_character:\d+,\d+`) // simplify the output for the tests, remove the translation variants
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := &strings.Builder{}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{})
assert.NoError(t, err)
assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String())
outStr := output.String()
outStr = re.ReplaceAllString(outStr, "repo.ambiguous_character")
assert.Equal(t, tt.result, outStr)
})
}
}

View File

@ -561,14 +561,14 @@ func TestFormatError(t *testing.T) {
err: &csv.ParseError{
Err: csv.ErrFieldCount,
},
expectedMessage: "repo.error.csv.invalid_field_count",
expectedMessage: "repo.error.csv.invalid_field_count:0",
expectsError: false,
},
{
err: &csv.ParseError{
Err: csv.ErrBareQuote,
},
expectedMessage: "repo.error.csv.unexpected",
expectedMessage: "repo.error.csv.unexpected:0,0",
expectsError: false,
},
{

174
modules/dump/dumper.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package dump
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"slices"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
)
var SupportedOutputTypes = []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"}
// PrepareFileNameAndType prepares the output file name and type, if the type is not supported, it returns an empty "outType"
func PrepareFileNameAndType(argFile, argType string) (outFileName, outType string) {
if argFile == "" && argType == "" {
outType = SupportedOutputTypes[0]
outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType)
} else if argFile == "" {
outType = argType
outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType)
} else if argType == "" {
if filepath.Ext(outFileName) == "" {
outType = SupportedOutputTypes[0]
outFileName = argFile
} else {
for _, t := range SupportedOutputTypes {
if strings.HasSuffix(argFile, "."+t) {
outFileName = argFile
outType = t
}
}
}
} else {
outFileName, outType = argFile, argType
}
if !slices.Contains(SupportedOutputTypes, outType) {
return "", ""
}
return outFileName, outType
}
func IsSubdir(upper, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
return true, nil
}
return false, nil
}
type Dumper struct {
Writer archiver.Writer
Verbose bool
globalExcludeAbsPaths []string
}
func (dumper *Dumper) AddReader(r io.ReadCloser, info os.FileInfo, customName string) error {
if dumper.Verbose {
log.Info("Adding file %s", customName)
}
return dumper.Writer.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func (dumper *Dumper) AddFile(filePath, absPath string) error {
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return dumper.AddReader(file, fileInfo, filePath)
}
func (dumper *Dumper) normalizeFilePath(absPath string) string {
absPath = filepath.Clean(absPath)
if setting.IsWindows {
absPath = strings.ToLower(absPath)
}
return absPath
}
func (dumper *Dumper) GlobalExcludeAbsPath(absPaths ...string) {
for _, absPath := range absPaths {
dumper.globalExcludeAbsPaths = append(dumper.globalExcludeAbsPaths, dumper.normalizeFilePath(absPath))
}
}
func (dumper *Dumper) shouldExclude(absPath string, excludes []string) bool {
norm := dumper.normalizeFilePath(absPath)
return slices.Contains(dumper.globalExcludeAbsPaths, norm) || slices.Contains(excludes, norm)
}
func (dumper *Dumper) AddRecursiveExclude(insidePath, absPath string, excludes []string) error {
excludes = slices.Clone(excludes)
for i := range excludes {
excludes[i] = dumper.normalizeFilePath(excludes[i])
}
return dumper.addFileOrDir(insidePath, absPath, excludes)
}
func (dumper *Dumper) addFileOrDir(insidePath, absPath string, excludes []string) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
if dumper.shouldExclude(currentAbsPath, excludes) {
continue
}
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if err := dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
return err
}
if err = dumper.addFileOrDir(currentInsidePath, currentAbsPath, excludes); err != nil {
return err
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
shouldAdd := file.Mode().IsRegular()
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
return err
}
}
}
}
return nil
}

113
modules/dump/dumper_test.go Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package dump
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"testing"
"time"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
"github.com/stretchr/testify/assert"
)
func TestPrepareFileNameAndType(t *testing.T) {
defer timeutil.MockSet(time.Unix(1234, 0))()
test := func(argFile, argType, expFile, expType string) {
outFile, outType := PrepareFileNameAndType(argFile, argType)
assert.Equal(t,
fmt.Sprintf("outFile=%s, outType=%s", expFile, expType),
fmt.Sprintf("outFile=%s, outType=%s", outFile, outType),
fmt.Sprintf("argFile=%s, argType=%s", argFile, argType),
)
}
test("", "", "gitea-dump-1234.zip", "zip")
test("", "tar.gz", "gitea-dump-1234.tar.gz", "tar.gz")
test("", "no-such", "", "")
test("-", "", "-", "zip")
test("-", "tar.gz", "-", "tar.gz")
test("-", "no-such", "", "")
test("a", "", "a", "zip")
test("a", "tar.gz", "a", "tar.gz")
test("a", "no-such", "", "")
test("a.zip", "", "a.zip", "zip")
test("a.zip", "tar.gz", "a.zip", "tar.gz")
test("a.zip", "no-such", "", "")
test("a.tar.gz", "", "a.tar.gz", "zip")
test("a.tar.gz", "tar.gz", "a.tar.gz", "tar.gz")
test("a.tar.gz", "no-such", "", "")
}
func TestIsSubDir(t *testing.T) {
tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/sub"), 0o755)
isSub, err := IsSubdir(filepath.Join(tmpDir, "include"), filepath.Join(tmpDir, "include"))
assert.NoError(t, err)
assert.True(t, isSub)
isSub, err = IsSubdir(filepath.Join(tmpDir, "include"), filepath.Join(tmpDir, "include/sub"))
assert.NoError(t, err)
assert.True(t, isSub)
isSub, err = IsSubdir(filepath.Join(tmpDir, "include/sub"), filepath.Join(tmpDir, "include"))
assert.NoError(t, err)
assert.False(t, isSub)
}
type testWriter struct {
added []string
}
func (t *testWriter) Create(out io.Writer) error {
return nil
}
func (t *testWriter) Write(f archiver.File) error {
t.added = append(t.added, f.Name())
return nil
}
func (t *testWriter) Close() error {
return nil
}
func TestDumper(t *testing.T) {
sortStrings := func(s []string) []string {
sort.Strings(s)
return s
}
tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude1"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude2"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/sub"), 0o755)
_ = os.WriteFile(filepath.Join(tmpDir, "include/a"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/sub/b"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude1/a-1"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude2/a-2"), nil, 0o644)
tw := &testWriter{}
d := &Dumper{Writer: tw}
d.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1"))
err := d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), []string{filepath.Join(tmpDir, "include/exclude2")})
assert.NoError(t, err)
assert.EqualValues(t, sortStrings([]string{"include/a", "include/sub", "include/sub/b"}), sortStrings(tw.added))
tw = &testWriter{}
d = &Dumper{Writer: tw}
err = d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil)
assert.NoError(t, err)
assert.EqualValues(t, sortStrings([]string{"include/exclude2", "include/exclude2/a-2", "include/a", "include/sub", "include/sub/b", "include/exclude1", "include/exclude1/a-1"}), sortStrings(tw.added))
}

View File

@ -26,14 +26,14 @@ type Commit struct {
Author *Signature
Committer *Signature
CommitMessage string
Signature *CommitGPGSignature
Signature *CommitSignature
Parents []ObjectID // ID strings
submoduleCache *ObjectCache
}
// CommitGPGSignature represents a git commit signature part.
type CommitGPGSignature struct {
// CommitSignature represents a git commit signature part.
type CommitSignature struct {
Signature string
Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
}

View File

@ -13,7 +13,7 @@ import (
"github.com/go-git/go-git/v5/plumbing/object"
)
func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
func convertPGPSignature(c *object.Commit) *CommitSignature {
if c.PGPSignature == "" {
return nil
}
@ -57,7 +57,7 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
return nil
}
return &CommitGPGSignature{
return &CommitSignature{
Signature: c.PGPSignature,
Payload: w.String(),
}

View File

@ -99,7 +99,7 @@ readLoop:
}
}
commit.CommitMessage = messageSB.String()
commit.Signature = &CommitGPGSignature{
commit.Signature = &CommitSignature{
Signature: signatureSB.String(),
Payload: payloadSB.String(),
}

View File

@ -185,17 +185,15 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
tag.Message = ref["contents"]
// strip PGP signature if present in contents field
pgpStart := strings.Index(tag.Message, beginpgp)
if pgpStart >= 0 {
tag.Message = tag.Message[0:pgpStart]
}
// strip any signature if present in contents field
_, tag.Message, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.Message), 0)
// annotated tag with GPG signature
if tag.Type == "tag" && ref["contents:signature"] != "" {
payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n",
tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message))
tag.Signature = &CommitGPGSignature{
tag.Signature = &CommitSignature{
Signature: ref["contents:signature"],
Payload: payload,
}

View File

@ -315,7 +315,7 @@ qbHDASXl
Type: "tag",
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",
Signature: &CommitGPGSignature{
Signature: &CommitSignature{
Signature: `-----BEGIN PGP SIGNATURE-----
aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3

View File

@ -6,16 +6,10 @@ package git
import (
"bytes"
"sort"
"strings"
"code.gitea.io/gitea/modules/util"
)
const (
beginpgp = "\n-----BEGIN PGP SIGNATURE-----\n"
endpgp = "\n-----END PGP SIGNATURE-----"
)
// Tag represents a Git tag.
type Tag struct {
Name string
@ -24,7 +18,7 @@ type Tag struct {
Type string
Tagger *Signature
Message string
Signature *CommitGPGSignature
Signature *CommitSignature
}
// Commit return the commit of the tag reference
@ -32,6 +26,36 @@ func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) {
return gitRepo.getCommit(tag.Object)
}
func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) {
pos := messageStart
signStart, signEnd := -1, -1
for {
eol := bytes.IndexByte(data[pos:], '\n')
if eol < 0 {
break
}
line := data[pos : pos+eol]
signType, hasPrefix := bytes.CutPrefix(line, []byte("-----BEGIN "))
signType, hasSuffix := bytes.CutSuffix(signType, []byte(" SIGNATURE-----"))
if hasPrefix && hasSuffix {
signEndBytes := append([]byte("\n-----END "), signType...)
signEndBytes = append(signEndBytes, []byte(" SIGNATURE-----")...)
signEnd = bytes.Index(data[pos:], signEndBytes)
if signEnd != -1 {
signStart = pos
signEnd = pos + signEnd + len(signEndBytes)
}
}
pos += eol + 1
}
if signStart != -1 && signEnd != -1 {
msgEnd := max(messageStart, signStart-1)
return string(data[:msgEnd]), string(data[messageStart:msgEnd]), string(data[signStart:signEnd])
}
return string(data), string(data[messageStart:]), ""
}
// Parse commit information from the (uncompressed) raw
// data from the commit object.
// \n\n separate headers from message
@ -40,47 +64,37 @@ func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) {
tag.ID = objectFormat.EmptyObjectID()
tag.Object = objectFormat.EmptyObjectID()
tag.Tagger = &Signature{}
// we now have the contents of the commit object. Let's investigate...
nextline := 0
l:
pos := 0
for {
eol := bytes.IndexByte(data[nextline:], '\n')
switch {
case eol > 0:
line := data[nextline : nextline+eol]
spacepos := bytes.IndexByte(line, ' ')
reftype := line[:spacepos]
switch string(reftype) {
case "object":
id, err := NewIDFromString(string(line[spacepos+1:]))
if err != nil {
return nil, err
}
tag.Object = id
case "type":
// A commit can have one or more parents
tag.Type = string(line[spacepos+1:])
case "tagger":
tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:]))
}
nextline += eol + 1
case eol == 0:
tag.Message = string(data[nextline+1:])
break l
default:
break l
eol := bytes.IndexByte(data[pos:], '\n')
if eol == -1 {
break // shouldn't happen, but could just tolerate it
}
if eol == 0 {
pos++
break // end of headers
}
line := data[pos : pos+eol]
key, val, _ := bytes.Cut(line, []byte(" "))
switch string(key) {
case "object":
id, err := NewIDFromString(string(val))
if err != nil {
return nil, err
}
tag.Object = id
case "type":
tag.Type = string(val) // A commit can have one or more parents
case "tagger":
tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(val))
}
pos += eol + 1
}
idx := strings.LastIndex(tag.Message, beginpgp)
if idx > 0 {
endSigIdx := strings.Index(tag.Message[idx:], endpgp)
if endSigIdx > 0 {
tag.Signature = &CommitGPGSignature{
Signature: tag.Message[idx+1 : idx+endSigIdx+len(endpgp)],
Payload: string(data[:bytes.LastIndex(data, []byte(beginpgp))+1]),
}
tag.Message = tag.Message[:idx+1]
}
payload, msg, sign := parsePayloadSignature(data, pos)
tag.Message = msg
if len(sign) > 0 {
tag.Signature = &CommitSignature{Signature: sign, Payload: payload}
}
return tag, nil
}

View File

@ -12,24 +12,28 @@ import (
func Test_parseTagData(t *testing.T) {
testData := []struct {
data []byte
tag Tag
data string
expected Tag
}{
{data: []byte(`object 3b114ab800c6432ad42387ccf6bc8d4388a2885a
{
data: `object 3b114ab800c6432ad42387ccf6bc8d4388a2885a
type commit
tag 1.22.0
tagger Lucas Michot <lucas@semalead.com> 1484491741 +0100
`), tag: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: &Sha1Hash{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a},
Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0)},
Message: "",
Signature: nil,
}},
{data: []byte(`object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc
`,
expected: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("3b114ab800c6432ad42387ccf6bc8d4388a2885a"),
Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "",
Signature: nil,
},
},
{
data: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc
type commit
tag 1.22.1
tagger Lucas Michot <lucas@semalead.com> 1484553735 +0100
@ -37,37 +41,57 @@ tagger Lucas Michot <lucas@semalead.com> 1484553735 +0100
test message
o
ono`), tag: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: &Sha1Hash{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc},
Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0)},
Message: "test message\no\n\nono",
Signature: nil,
}},
ono`,
expected: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc"),
Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0).In(time.FixedZone("", 3600))},
Message: "test message\no\n\nono",
Signature: nil,
},
},
{
data: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa
type commit
tag v0
tagger dummy user <dummy-email@example.com> 1484491741 +0100
dummy message
-----BEGIN SSH SIGNATURE-----
dummy signature
-----END SSH SIGNATURE-----
`,
expected: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa"),
Type: "commit",
Tagger: &Signature{Name: "dummy user", Email: "dummy-email@example.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "dummy message",
Signature: &CommitSignature{
Signature: `-----BEGIN SSH SIGNATURE-----
dummy signature
-----END SSH SIGNATURE-----`,
Payload: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa
type commit
tag v0
tagger dummy user <dummy-email@example.com> 1484491741 +0100
dummy message`,
},
},
},
}
for _, test := range testData {
tag, err := parseTagData(Sha1ObjectFormat, test.data)
tag, err := parseTagData(Sha1ObjectFormat, []byte(test.data))
assert.NoError(t, err)
assert.EqualValues(t, test.tag.ID, tag.ID)
assert.EqualValues(t, test.tag.Object, tag.Object)
assert.EqualValues(t, test.tag.Name, tag.Name)
assert.EqualValues(t, test.tag.Message, tag.Message)
assert.EqualValues(t, test.tag.Type, tag.Type)
if test.tag.Signature != nil && assert.NotNil(t, tag.Signature) {
assert.EqualValues(t, test.tag.Signature.Signature, tag.Signature.Signature)
assert.EqualValues(t, test.tag.Signature.Payload, tag.Signature.Payload)
} else {
assert.Nil(t, tag.Signature)
}
if test.tag.Tagger != nil && assert.NotNil(t, tag.Tagger) {
assert.EqualValues(t, test.tag.Tagger.Name, tag.Tagger.Name)
assert.EqualValues(t, test.tag.Tagger.Email, tag.Tagger.Email)
assert.EqualValues(t, test.tag.Tagger.When.Unix(), tag.Tagger.When.Unix())
} else {
assert.Nil(t, tag.Tagger)
}
assert.Equal(t, test.expected, *tag)
}
tag, err := parseTagData(Sha1ObjectFormat, []byte("type commit\n\nfoo\n-----BEGIN SSH SIGNATURE-----\ncorrupted..."))
assert.NoError(t, err)
assert.Equal(t, "foo\n-----BEGIN SSH SIGNATURE-----\ncorrupted...", tag.Message)
}

View File

@ -22,7 +22,7 @@ type Result struct {
UpdatedUnix timeutil.TimeStamp
Language string
Color string
Lines []ResultLine
Lines []*ResultLine
}
type ResultLine struct {
@ -70,16 +70,18 @@ func writeStrings(buf *bytes.Buffer, strs ...string) error {
return nil
}
func HighlightSearchResultCode(filename string, lineNums []int, code string) []ResultLine {
func HighlightSearchResultCode(filename, language string, lineNums []int, code string) []*ResultLine {
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
hl, _ := highlight.Code(filename, "", code)
hl, _ := highlight.Code(filename, language, code)
highlightedLines := strings.Split(string(hl), "\n")
// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
lines := make([]ResultLine, min(len(highlightedLines), len(lineNums)))
lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums)))
for i := 0; i < len(lines); i++ {
lines[i].Num = lineNums[i]
lines[i].FormattedContent = template.HTML(highlightedLines[i])
lines[i] = &ResultLine{
Num: lineNums[i],
FormattedContent: template.HTML(highlightedLines[i]),
}
}
return lines
}
@ -122,7 +124,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
UpdatedUnix: result.UpdatedUnix,
Language: result.Language,
Color: result.Color,
Lines: HighlightSearchResultCode(result.Filename, lineNums, formattedLinesBuffer.String()),
Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()),
}, nil
}

View File

@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call
if err != nil {
return err
}
defer f.Close()
if err := callback(p, f, nil); err != nil {
return err
}
@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, content)
return err

View File

@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{
fullIssuePatternProcessor,
comparePatternProcessor,
codePreviewPatternProcessor,
fullHashPatternProcessor,
shortLinkProcessor,
linkProcessor,

View File

@ -0,0 +1,92 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup
import (
"html/template"
"net/url"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"golang.org/x/net/html"
)
// codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20"
var codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`)
type RenderCodePreviewOptions struct {
FullURL string
OwnerName string
RepoName string
CommitID string
FilePath string
LineStart, LineStop int
}
func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosStop int, htm template.HTML, err error) {
m := codePreviewPattern.FindStringSubmatchIndex(node.Data)
if m == nil {
return 0, 0, "", nil
}
opts := RenderCodePreviewOptions{
FullURL: node.Data[m[0]:m[1]],
OwnerName: node.Data[m[2]:m[3]],
RepoName: node.Data[m[4]:m[5]],
CommitID: node.Data[m[6]:m[7]],
FilePath: node.Data[m[8]:m[9]],
}
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
return 0, 0, "", nil
}
u, err := url.Parse(opts.FilePath)
if err != nil {
return 0, 0, "", err
}
opts.FilePath = strings.TrimPrefix(u.Path, "/")
lineStartStr, lineStopStr, _ := strings.Cut(node.Data[m[10]:m[11]], "-")
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
opts.LineStart, opts.LineStop = lineStart, lineStop
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx.Ctx, opts)
return m[0], m[1], h, err
}
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
for node != nil {
if node.Type != html.TextNode {
node = node.NextSibling
continue
}
urlPosStart, urlPosEnd, h, err := renderCodeBlock(ctx, node)
if err != nil || h == "" {
if err != nil {
log.Error("Unable to render code preview: %v", err)
}
node = node.NextSibling
continue
}
next := node.NextSibling
textBefore := node.Data[:urlPosStart]
textAfter := node.Data[urlPosEnd:]
// "textBefore" could be empty if there is only a URL in the text node, then an empty node (p, or li) will be left here.
// However, the empty node can't be simply removed, because:
// 1. the following processors will still try to access it (need to double-check undefined behaviors)
// 2. the new node is inserted as "<p>{TextBefore}<div NewNode/>{TextAfter}</p>" (the parent could also be "li")
// then it is resolved as: "<p>{TextBefore}</p><div NewNode/><p>{TextAfter}</p>",
// so unless it could correctly replace the parent "p/li" node, it is very difficult to eliminate the "TextBefore" empty node.
node.Data = textBefore
node.Parent.InsertBefore(&html.Node{Type: html.RawNode, Data: string(h)}, next)
if textAfter != "" {
node.Parent.InsertBefore(&html.Node{Type: html.TextNode, Data: textAfter}, next)
}
node = next
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup_test
import (
"context"
"html/template"
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert"
)
func TestRenderCodePreview(t *testing.T) {
markup.Init(&markup.ProcessorHelper{
RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
return "<div>code preview</div>", nil
},
})
test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Type: "markdown",
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
}

View File

@ -436,6 +436,10 @@ func TestColorPreview(t *testing.T) {
testcase string
expected string
}{
{ // do not render color names
"The CSS class `red` is there",
"<p>The CSS class <code>red</code> is there</p>\n",
},
{ // hex
"`#FF0000`",
`<p><code>#FF0000<span class="color-preview" style="background-color: #FF0000"></span></code></p>` + nl,
@ -445,8 +449,8 @@ func TestColorPreview(t *testing.T) {
`<p><code>rgb(16, 32, 64)<span class="color-preview" style="background-color: rgb(16, 32, 64)"></span></code></p>` + nl,
},
{ // short hex
"This is the color white `#000`",
`<p>This is the color white <code>#000<span class="color-preview" style="background-color: #000"></span></code></p>` + nl,
"This is the color white `#0a0`",
`<p>This is the color white <code>#0a0<span class="color-preview" style="background-color: #0a0"></span></code></p>` + nl,
},
{ // hsl
"HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.",
@ -507,9 +511,17 @@ func TestMathBlock(t *testing.T) {
`\(a\) \(b\)`,
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
},
{
`$a$.`,
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
},
{
`.$a$`,
`<p>.$a$</p>` + nl,
},
{
`$a a$b b$`,
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
`<p>$a a$b b$</p>` + nl,
},
{
`a a$b b`,
@ -517,7 +529,15 @@ func TestMathBlock(t *testing.T) {
},
{
`a$b $a a$b b$`,
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
`<p>a$b $a a$b b$</p>` + nl,
},
{
"a$x$",
`<p>a$x$</p>` + nl,
},
{
"$x$a",
`<p>$x$a</p>` + nl,
},
{
"$$a$$",

View File

@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte {
return parser.start[0:1]
}
func isPunctuation(b byte) bool {
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
}
func isAlphanumeric(b byte) bool {
// Github only cares about 0-9A-Za-z
return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
}
// Parse parses the current line and returns a result of parsing.
@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
}
precedingCharacter := block.PrecendingCharacter()
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
// need to exclude things like `a$` from being considered a start
return nil
}
@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
ender += pos
// Now we want to check the character at the end of our parser section
// that is ender + len(parser.end)
// that is ender + len(parser.end) and check if char before ender is '\'
pos = ender + len(parser.end)
if len(line) <= pos {
break
}
if !isAlphanumeric(line[pos]) {
suceedingCharacter := line[pos]
if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
return nil
}
if line[ender-1] != '\\' {
break
}
// move the pointer onwards
ender += len(parser.end)
}

View File

@ -49,9 +49,28 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
return ast.WalkContinue, nil
}
// cssColorHandler checks if a string is a render-able CSS color value.
// The code is from "github.com/microcosm-cc/bluemonday/css.ColorHandler", except that it doesn't handle color words like "red".
func cssColorHandler(value string) bool {
value = strings.ToLower(value)
if css.HexRGB.MatchString(value) {
return true
}
if css.RGB.MatchString(value) {
return true
}
if css.RGBA.MatchString(value) {
return true
}
if css.HSL.MatchString(value) {
return true
}
return css.HSLA.MatchString(value)
}
func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
colorContent := v.Text(reader.Source())
if css.ColorHandler(strings.ToLower(string(colorContent))) {
if cssColorHandler(string(colorContent)) {
v.AppendChild(v, NewColorPreview(colorContent))
}
}

View File

@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"html/template"
"io"
"net/url"
"path/filepath"
@ -33,6 +34,8 @@ type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
}
var DefaultProcessorHelper ProcessorHelper

View File

@ -60,6 +60,21 @@ func createDefaultPolicy() *bluemonday.Policy {
// For JS code copy and Mermaid loading state
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).OnElements("pre")
// For code preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-preview-[-\w]+( file-content)?$`)).Globally()
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-num$`)).OnElements("td")
policy.AllowAttrs("data-line-number").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-code chroma$`)).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-inner$`)).OnElements("div")
// For code preview (unicode escape)
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^file-view( unicode-escaped)?$`)).OnElements("table")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-escape$`)).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^toggle-escape-button btn interact-bg$`)).OnElements("a") // don't use button, button might submit a form
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ambiguous-code-point|escaped-code-point|broken-code-point)$`)).OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^char$`)).OnElements("span")
policy.AllowAttrs("data-tooltip-content", "data-escaped").OnElements("span")
// For color preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")

View File

@ -58,6 +58,7 @@ type Package struct {
type Metadata struct {
Description string `json:"description,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"`
Readme string `json:"readme,omitempty"`
Authors string `json:"authors,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
@ -71,6 +72,7 @@ type Dependency struct {
Version string `json:"version"`
}
// https://learn.microsoft.com/en-us/nuget/reference/nuspec
type nuspecPackage struct {
Metadata struct {
ID string `xml:"id"`
@ -80,6 +82,7 @@ type nuspecPackage struct {
ProjectURL string `xml:"projectUrl"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
Readme string `xml:"readme"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
@ -89,6 +92,11 @@ type nuspecPackage struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
Dependencies struct {
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
Group []struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct {
@ -122,14 +130,14 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
}
defer f.Close()
return ParseNuspecMetaData(f)
return ParseNuspecMetaData(archive, f)
}
}
return nil, ErrMissingNuspecFile
}
// ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
func ParseNuspecMetaData(r io.Reader) (*Package, error) {
func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
var p nuspecPackage
if err := xml.NewDecoder(r).Decode(&p); err != nil {
return nil, err
@ -166,6 +174,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
Dependencies: make(map[string][]Dependency),
}
if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme)
if err == nil {
buf, _ := io.ReadAll(f)
m.Readme = string(buf)
_ = f.Close()
}
}
if len(p.Metadata.Dependencies.Dependency) > 0 {
deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
for _, dep := range p.Metadata.Dependencies.Dependency {
if dep.ID == "" || dep.Version == "" {
continue
}
deps = append(deps, Dependency{
ID: dep.ID,
Version: dep.Version,
})
}
m.Dependencies[""] = deps
}
for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency {

View File

@ -6,7 +6,6 @@ package nuget
import (
"archive/zip"
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -19,6 +18,7 @@ const (
projectURL = "https://gitea.io"
description = "Package Description"
releaseNotes = "Package Release Notes"
readme = "Readme"
repositoryURL = "https://gitea.io/gitea/gitea"
targetFramework = ".NETStandard2.1"
dependencyID = "System.Text.Json"
@ -36,6 +36,7 @@ const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<description>` + description + `</description>
<releaseNotes>` + releaseNotes + `</releaseNotes>
<repository url="` + repositoryURL + `" />
<readme>README.md</readme>
<dependencies>
<group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
@ -60,17 +61,19 @@ const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
</package>`
func TestParsePackageMetaData(t *testing.T) {
createArchive := func(name, content string) []byte {
createArchive := func(files map[string]string) []byte {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
w, _ := archive.Create(name)
w.Write([]byte(content))
for name, content := range files {
w, _ := archive.Create(name)
w.Write([]byte(content))
}
archive.Close()
return buf.Bytes()
}
t.Run("MissingNuspecFile", func(t *testing.T) {
data := createArchive("dummy.txt", "")
data := createArchive(map[string]string{"dummy.txt": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
data := createArchive("sub/package.nuspec", "")
data := createArchive(map[string]string{"sub/package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidNuspecFile", func(t *testing.T) {
data := createArchive("package.nuspec", "")
data := createArchive(map[string]string{"package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidPackageId", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata></metadata>
</package>`)
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidPackageVersion", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>`+id+`</id>
<id>` + id + `</id>
</metadata>
</package>`)
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
})
t.Run("Valid", func(t *testing.T) {
data := createArchive("package.nuspec", nuspecContent)
t.Run("MissingReadme", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": nuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Empty(t, np.Metadata.Readme)
})
}
func TestParseNuspecMetaData(t *testing.T) {
t.Run("Dependency Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(nuspecContent))
data := createArchive(map[string]string{
"package.nuspec": nuspecContent,
"README.md": readme,
})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType)
@ -139,6 +146,7 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework)
@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, dependencyVersion, deps[0].Version)
t.Run("NormalizedVersion", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version>
</metadata>
</package>`))
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version>
</metadata>
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
@ -162,7 +172,9 @@ func TestParseNuspecMetaData(t *testing.T) {
})
t.Run("Symbols Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(symbolsNuspecContent))
data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, SymbolsPackage, np.PackageType)

View File

@ -315,21 +315,25 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
}
}
// DeprecatedWarnings contains the warning message for various deprecations, including: setting option, file/folder, etc
var DeprecatedWarnings []string
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
var StartupProblems []string
func logStartupProblem(skip int, level log.Level, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
log.Log(skip+1, level, "%s", msg)
StartupProblems = append(StartupProblems, msg)
}
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
msg := fmt.Sprintf("Deprecated config option `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
log.Error("%v", msg)
DeprecatedWarnings = append(DeprecatedWarnings, msg)
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
}
}
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey)
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
}
}

View File

@ -58,7 +58,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(Indexer.IssuePath) {
Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
}
checkOverlappedPath("indexer.ISSUE_INDEXER_PATH", Indexer.IssuePath)
checkOverlappedPath("[indexer].ISSUE_INDEXER_PATH", Indexer.IssuePath)
} else {
Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
if Indexer.IssueType == "meilisearch" {

View File

@ -185,8 +185,13 @@ func InitLoggersForTest() {
initAllLoggers()
}
var initLoggerDisabled bool
// initAllLoggers creates all the log services
func initAllLoggers() {
if initLoggerDisabled {
return
}
initManagedLoggers(log.GetManager(), CfgProvider)
golog.SetFlags(0)
@ -194,6 +199,10 @@ func initAllLoggers() {
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
}
func DisableLoggerInit() {
initLoggerDisabled = true
}
func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) {
loadLogGlobalFrom(cfg)
prepareLoggerConfig(cfg)

View File

@ -118,6 +118,10 @@ func loadOAuth2From(rootCfg ConfigProvider) {
return
}
if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" {
OAuth2.DefaultApplications = nil
}
// Handle the rename of ENABLE to ENABLED
deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0")
if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") {
@ -168,7 +172,7 @@ func GetGeneralTokenSigningSecret() []byte {
}
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
log.Warn("OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
logStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
return jwtSecret
}
return *generalSigningSecret.Load()

View File

@ -32,3 +32,21 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
assert.Len(t, actual, 32)
assert.EqualValues(t, expected, actual)
}
func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg)
assert.Equal(t, []string{"git-credential-oauth", "git-credential-manager", "tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = tea
`)
loadOAuth2From(cfg)
assert.Equal(t, []string{"tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS =
`)
loadOAuth2From(cfg)
assert.Nil(t, nil, OAuth2.DefaultApplications)
}

View File

@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log"
type OtherConfig struct {
ShowFooterVersion bool
ShowFooterTemplateLoadTime bool
ShowFooterPoweredBy bool
EnableFeed bool
EnableSitemap bool
}
@ -15,6 +16,7 @@ type OtherConfig struct {
var Other = OtherConfig{
ShowFooterVersion: true,
ShowFooterTemplateLoadTime: true,
ShowFooterPoweredBy: true,
EnableSitemap: true,
EnableFeed: true,
}

View File

@ -286,7 +286,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
RepoRootPath = filepath.Clean(RepoRootPath)
}
checkOverlappedPath("repository.ROOT", RepoRootPath)
checkOverlappedPath("[repository].ROOT", RepoRootPath)
defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
for _, charset := range Repository.DetectedCharsetsOrder {

View File

@ -331,7 +331,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(PprofDataPath) {
PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
}
checkOverlappedPath("server.PPROF_DATA_PATH", PprofDataPath)
checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath)
landingPage := sec.Key("LANDING_PAGE").MustString("home")
switch landingPage {

View File

@ -46,7 +46,7 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig)
checkOverlappedPath("session.PROVIDER_CONFIG", SessionConfig.ProviderConfig)
checkOverlappedPath("[session].PROVIDER_CONFIG", SessionConfig.ProviderConfig)
}
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
SessionConfig.CookiePath = AppSubURL

View File

@ -235,9 +235,7 @@ var configuredPaths = make(map[string]string)
func checkOverlappedPath(name, path string) {
// TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
if targetName, ok := configuredPaths[path]; ok && targetName != name {
msg := fmt.Sprintf("Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
log.Error("%s", msg)
DeprecatedWarnings = append(DeprecatedWarnings, msg)
logStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
}
configuredPaths[path] = name
}

View File

@ -240,7 +240,7 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
}
}
checkOverlappedPath("storage."+name+".PATH", storage.Path)
checkOverlappedPath("[storage."+name+"].PATH", storage.Path)
return &storage, nil
}

View File

@ -53,13 +53,13 @@ func NewFuncMap() template.FuncMap {
"JsonUtils": NewJsonUtils,
// -----------------------------------------------------------------
// svg / avatar / icon
// svg / avatar / icon / color
"svg": svg.RenderHTML,
"EntryIcon": base.EntryIcon,
"MigrationIcon": MigrationIcon,
"ActionIcon": ActionIcon,
"SortArrow": SortArrow,
"SortArrow": SortArrow,
"ContrastColor": util.ContrastColor,
// -----------------------------------------------------------------
// time / number / format
@ -106,6 +106,9 @@ func NewFuncMap() template.FuncMap {
"ShowFooterTemplateLoadTime": func() bool {
return setting.Other.ShowFooterTemplateLoadTime
},
"ShowFooterPoweredBy": func() bool {
return setting.Other.ShowFooterPoweredBy
},
"AllowedReactions": func() []string {
return setting.UI.Reactions
},

View File

@ -123,16 +123,10 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
var (
archivedCSSClass string
textColor = "#111"
textColor = util.ContrastColor(label.Color)
labelScope = label.ExclusiveScope()
)
r, g, b := util.HexToRBGColor(label.Color)
// Determine if label text should be light or dark to be readable on background color
if util.UseLightTextOnBackground(r, g, b) {
textColor = "#eee"
}
description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
if label.IsArchived() {
@ -153,7 +147,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
// Make scope and item background colors slightly darker and lighter respectively.
// More contrast needed with higher luminance, empirically tweaked.
luminance := util.GetLuminance(r, g, b)
luminance := util.GetRelativeLuminance(label.Color)
contrast := 0.01 + luminance*0.03
// Ensure we add the same amount of contrast also near 0 and 1.
darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
@ -162,6 +156,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
r, g, b := util.HexToRBGColor(label.Color)
scopeBytes := []byte{
uint8(math.Min(math.Round(r*darkenFactor), 255)),
uint8(math.Min(math.Round(g*darkenFactor), 255)),

View File

@ -21,8 +21,9 @@ var (
)
// MockSet sets the time to a mocked time.Time
func MockSet(now time.Time) {
func MockSet(now time.Time) func() {
mockNow = now
return MockUnset
}
// MockUnset will unset the mocked time.Time

View File

@ -6,6 +6,7 @@ package translation
import (
"fmt"
"html/template"
"strings"
)
// MockLocale provides a mocked locale without any translations
@ -19,18 +20,25 @@ func (l MockLocale) Language() string {
return "en"
}
func (l MockLocale) TrString(s string, _ ...any) string {
return s
func (l MockLocale) TrString(s string, args ...any) string {
return sprintAny(s, args...)
}
func (l MockLocale) Tr(s string, a ...any) template.HTML {
return template.HTML(s)
func (l MockLocale) Tr(s string, args ...any) template.HTML {
return template.HTML(sprintAny(s, args...))
}
func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return template.HTML(key1)
return template.HTML(sprintAny(key1, args...))
}
func (l MockLocale) PrettyNumber(v any) string {
return fmt.Sprint(v)
}
func sprintAny(s string, args ...any) string {
if len(args) == 0 {
return s
}
return s + ":" + fmt.Sprintf(strings.Repeat(",%v", len(args))[1:], args...)
}

View File

@ -4,22 +4,10 @@ package util
import (
"fmt"
"math"
"strconv"
"strings"
)
// Check similar implementation in web_src/js/utils/color.js and keep synchronization
// Return R, G, B values defined in reletive luminance
func getLuminanceRGB(channel float64) float64 {
sRGB := channel / 255
if sRGB <= 0.03928 {
return sRGB / 12.92
}
return math.Pow((sRGB+0.055)/1.055, 2.4)
}
// Get color as RGB values in 0..255 range from the hex color string (with or without #)
func HexToRBGColor(colorString string) (float64, float64, float64) {
hexString := colorString
@ -47,19 +35,23 @@ func HexToRBGColor(colorString string) (float64, float64, float64) {
return r, g, b
}
// return luminance given RGB channels
// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance
func GetLuminance(r, g, b float64) float64 {
R := getLuminanceRGB(r)
G := getLuminanceRGB(g)
B := getLuminanceRGB(b)
luminance := 0.2126*R + 0.7152*G + 0.0722*B
return luminance
// Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance
// Keep this in sync with web_src/js/utils/color.js
func GetRelativeLuminance(color string) float64 {
r, g, b := HexToRBGColor(color)
return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255
}
// Reference from: https://firsching.ch/github_labels.html
// In the future WCAG 3 APCA may be a better solution.
// Check if text should use light color based on RGB of background
func UseLightTextOnBackground(r, g, b float64) bool {
return GetLuminance(r, g, b) < 0.453
func UseLightText(backgroundColor string) bool {
return GetRelativeLuminance(backgroundColor) < 0.453
}
// Given a background color, returns a black or white foreground color that the highest
// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better.
// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42
func ContrastColor(backgroundColor string) string {
if UseLightText(backgroundColor) {
return "#fff"
}
return "#000"
}

View File

@ -33,33 +33,31 @@ func Test_HexToRBGColor(t *testing.T) {
}
}
func Test_UseLightTextOnBackground(t *testing.T) {
func Test_UseLightText(t *testing.T) {
cases := []struct {
r float64
g float64
b float64
expected bool
color string
expected string
}{
{215, 58, 74, true},
{0, 117, 202, true},
{207, 211, 215, false},
{162, 238, 239, false},
{112, 87, 255, true},
{0, 134, 114, true},
{228, 230, 105, false},
{216, 118, 227, true},
{255, 255, 255, false},
{43, 134, 133, true},
{43, 135, 134, true},
{44, 135, 134, true},
{59, 182, 179, true},
{124, 114, 104, true},
{126, 113, 108, true},
{129, 112, 109, true},
{128, 112, 112, true},
{"#d73a4a", "#fff"},
{"#0075ca", "#fff"},
{"#cfd3d7", "#000"},
{"#a2eeef", "#000"},
{"#7057ff", "#fff"},
{"#008672", "#fff"},
{"#e4e669", "#000"},
{"#d876e3", "#000"},
{"#ffffff", "#000"},
{"#2b8684", "#fff"},
{"#2b8786", "#fff"},
{"#2c8786", "#000"},
{"#3bb6b3", "#000"},
{"#7c7268", "#fff"},
{"#7e716c", "#fff"},
{"#81706d", "#fff"},
{"#807070", "#fff"},
{"#84b6eb", "#000"},
}
for n, c := range cases {
result := UseLightTextOnBackground(c.r, c.g, c.b)
assert.Equal(t, c.expected, result, "case %d: error should match", n)
assert.Equal(t, c.expected, ContrastColor(c.color), "case %d: error should match", n)
}
}

View File

@ -213,6 +213,14 @@ func ToPointer[T any](val T) *T {
return &val
}
// Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal"
func Iif[T any](condition bool, trueVal, falseVal T) T {
if condition {
return trueVal
}
return falseVal
}
// IfZero returns "def" if "v" is a zero value, otherwise "v"
func IfZero[T comparable](v, def T) T {
var zero T

View File

@ -0,0 +1,11 @@
Copyright 1989, 1990 Advanced Micro Devices, Inc.
This software is the property of Advanced Micro Devices, Inc (AMD) which
specifically grants the user the right to modify, use and distribute this
software provided this notice is not removed or altered. All other rights
are reserved by AMD.
AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS
SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL
DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR
USE OF THIS SOFTWARE.

View File

@ -210,21 +210,21 @@ PART 1: INITIAL CONTRIBUTOR AND DESIGNATED WEB SITE
The Initial Contributor is:
____________________________________________________
 
[Enter full name of Initial Contributor]
Address of Initial Contributor:
________________________________________________
 
________________________________________________
 
________________________________________________
 
[Enter address above]
The Designated Web Site is:
__________________________________________________
 
[Enter URL for Designated Web Site of Initial Contributor]
NOTE: The Initial Contributor is to complete this Part 1, along with Parts 2, 3, and 5, and, if applicable, Parts 4 and 6.
@ -237,27 +237,27 @@ The date on which the Initial Work was first available under this License: _____
PART 3: GOVERNING JURISDICTION
For the purposes of this License, the Governing Jurisdiction is _________________________________________________. [Initial Contributor to Enter Governing Jurisdiction here]
For the purposes of this License, the Governing Jurisdiction is _________________________________________________. [Initial Contributor to Enter Governing Jurisdiction here]
PART 4: THIRD PARTIES
For the purposes of this License, "Third Party" has the definition set forth below in the ONE paragraph selected by the Initial Contributor from paragraphs A, B, C, D and E when the Initial Work is distributed or otherwise made available by the Initial Contributor. To select one of the following paragraphs, the Initial Contributor must place an "X" or "x" in the selection box alongside the one respective paragraph selected.
SELECTION
 
BOX PARAGRAPH
[  ] A. "THIRD PARTY" means any third party.
 
 
[  ] B. "THIRD PARTY" means any third party except for any of the following: (a) a wholly owned subsidiary of the Subsequent Contributor in question; (b) a legal entity (the "PARENT") that wholly owns the Subsequent Contributor in question; or (c) a wholly owned subsidiary of the wholly owned subsidiary in (a) or of the Parent in (b).
 
 
[  ] C. "THIRD PARTY" means any third party except for any of the following: (a) any Person directly or indirectly owning a majority of the voting interest in the Subsequent Contributor or (b) any Person in which the Subsequent Contributor directly or indirectly owns a majority voting interest.
 
 
[  ] D. "THIRD PARTY" means any third party except for any Person directly or indirectly controlled by the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise.
 
 
[  ] E. "THIRD PARTY" means any third party except for any Person directly or indirectly controlling, controlled by, or under common control with the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise.
[ ] A. "THIRD PARTY" means any third party.
[ ] B. "THIRD PARTY" means any third party except for any of the following: (a) a wholly owned subsidiary of the Subsequent Contributor in question; (b) a legal entity (the "PARENT") that wholly owns the Subsequent Contributor in question; or (c) a wholly owned subsidiary of the wholly owned subsidiary in (a) or of the Parent in (b).
[ ] C. "THIRD PARTY" means any third party except for any of the following: (a) any Person directly or indirectly owning a majority of the voting interest in the Subsequent Contributor or (b) any Person in which the Subsequent Contributor directly or indirectly owns a majority voting interest.
[ ] D. "THIRD PARTY" means any third party except for any Person directly or indirectly controlled by the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise.
[ ] E. "THIRD PARTY" means any third party except for any Person directly or indirectly controlling, controlled by, or under common control with the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise.
The default definition of "THIRD PARTY" is the definition set forth in paragraph A, if NONE OR MORE THAN ONE of paragraphs A, B, C, D or E in this Part 4 are selected by the Initial Contributor.
PART 5: NOTICE
@ -271,8 +271,8 @@ PART 6: PATENT LICENSING TERMS
For the purposes of this License, paragraphs A, B, C, D and E of this Part 6 of Exhibit A are only incorporated and form part of the terms of the License if the Initial Contributor places an "X" or "x" in the selection box alongside the YES answer to the question immediately below.
Is this a Patents-Included License pursuant to Section 2.2 of the License?
YES [      ]
NO [      ]
YES [ ]
NO [ ]
By default, if YES is not selected by the Initial Contributor, the answer is NO.

View File

@ -4,5 +4,5 @@ Any user of this software should understand that IBM cannot provide technical su
Any person who transfers this source code or any derivative work must include the IBM copyright notice, this paragraph, and the preceding two paragraphs in the transferred software.
COPYRIGHT   I B M   CORPORATION 2002
LICENSED MATERIAL  -  PROGRAM PROPERTY OF I B M
COPYRIGHT I B M CORPORATION 2002
LICENSED MATERIAL - PROGRAM PROPERTY OF I B M

View File

@ -12,15 +12,15 @@ The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive lice
This licence does not affect your freedom under fair dealing or fair use or any other copyright or database right exceptions and limitations.
You are free to:
copy, publish, distribute and transmit the Information;
copy, publish, distribute and transmit the Information;
adapt the Information;
exploit the Information for Non-Commercial purposes for example, by combining it with other information in your own product or application.
You are not permitted to:
exercise any of the rights granted to you by this licence in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation.
exercise any of the rights granted to you by this licence in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation.
You must, where you do any of the above:
acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence;
acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence;
If the Information Provider does not provide a specific attribution statement, you must use the following:
Contains information licensed under the Non-Commercial Government Licence v2.0.

View File

@ -2,7 +2,7 @@ Netscape Public LIcense version 1.1
AMENDMENTS
The Netscape Public License Version 1.1 ("NPL") consists of the Mozilla Public License Version 1.1 with the following Amendments, including Exhibit A-Netscape Public License.  Files identified with "Exhibit A-Netscape Public License" are governed by the Netscape Public License Version 1.1.
The Netscape Public License Version 1.1 ("NPL") consists of the Mozilla Public License Version 1.1 with the following Amendments, including Exhibit A-Netscape Public License. Files identified with "Exhibit A-Netscape Public License" are governed by the Netscape Public License Version 1.1.
Additional Terms applicable to the Netscape Public License.
@ -28,7 +28,7 @@ Additional Terms applicable to the Netscape Public License.
Notwithstanding the limitations of Section 11 above, the provisions regarding litigation in Section 11(a), (b) and (c) of the License shall apply to all disputes relating to this License.
EXHIBIT A-Netscape Public License.
 
"The contents of this file are subject to the Netscape Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/NPL/
Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License.
@ -37,8 +37,8 @@ The Original Code is Mozilla Communicator client code, released March 31, 1998.
The Initial Developer of the Original Code is Netscape Communications Corporation. Portions created by Netscape are Copyright (C) 1998-1999 Netscape Communications Corporation. All Rights Reserved.
Contributor(s): ______________________________________.
 
Alternatively, the contents of this file may be used under the terms of the _____ license (the  "[___] License"), in which case the provisions of [______] License are applicable  instead of those above.  If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the NPL, indicate your decision by deleting  the provisions above and replace  them with the notice and other provisions required by the [___] License.  If you do not delete the provisions above, a recipient may use your version of this file under either the NPL or the [___] License."
Alternatively, the contents of this file may be used under the terms of the _____ license (the "[___] License"), in which case the provisions of [______] License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the NPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the [___] License. If you do not delete the provisions above, a recipient may use your version of this file under either the NPL or the [___] License."
Mozilla Public License Version 1.1

12
options/license/OAR Normal file
View File

@ -0,0 +1,12 @@
COPYRIGHT (c) 1989-2013, 2015.
On-Line Applications Research Corporation (OAR).
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION
OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS
SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.

View File

@ -6,7 +6,7 @@ OPEN CASCADE releases and makes publicly available the source code of the softwa
It is not the purpose of this license to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this license has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
Please read this license carefully and completely before downloading this software. By downloading, using, modifying, distributing and sublicensing this software, you indicate your acceptance to be bound by the terms and conditions of this license. If you do not want to accept or cannot accept for any reasons the terms and conditions of this license, please do not download or use in any manner this software.
 
1. Definitions
Unless there is something in the subject matter or in the context inconsistent therewith, the capitalized terms used in this License shall have the following meaning.
@ -26,13 +26,13 @@ Unless there is something in the subject matter or in the context inconsistent t
"Software": means the Original Code, the Modifications, the combination of Original Code and any Modifications or any respective portions thereof.
"You" or "Your": means an individual or a legal entity exercising rights under this License
 
2. Acceptance of license
By using, reproducing, modifying, distributing or sublicensing the Software or any portion thereof, You expressly indicate Your acceptance of the terms and conditions of this License and undertake to act in accordance with all the provisions of this License applicable to You.
 
3. Scope and purpose
This License applies to the Software and You may not use, reproduce, modify, distribute, sublicense or circulate the Software, or any portion thereof, except as expressly provided under this License. Any attempt to otherwise use, reproduce, modify, distribute or sublicense the Software is void and will automatically terminate Your rights under this License.
 
4. Contributor license
Subject to the terms and conditions of this License, the Initial Developer and each of the Contributors hereby grant You a world-wide, royalty-free, irrevocable and non-exclusive license under the Applicable Intellectual Property Rights they own or control, to use, reproduce, modify, distribute and sublicense the Software provided that:

View File

@ -10,20 +10,20 @@ The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive lice
This licence does not affect your freedom under fair dealing or fair use or any other copyright or database right exceptions and limitations.
You are free to:
copy, publish, distribute and transmit the Information;
copy, publish, distribute and transmit the Information;
adapt the Information;
exploit the Information commercially for example, by combining it with other Information, or by including it in your own product or application.
You must, where you do any of the above:
acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence;
 If the Information Provider does not provide a specific attribution statement, or if you are using Information from several Information Providers and multiple attributions are not practical in your product or application, you may consider using the following: Contains public sector information licensed under the Open Government Licence v1.0.
acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence;
If the Information Provider does not provide a specific attribution statement, or if you are using Information from several Information Providers and multiple attributions are not practical in your product or application, you may consider using the following: Contains public sector information licensed under the Open Government Licence v1.0.
ensure that you do not use the Information in a way that suggests any official status or that the Information Provider endorses you or your use of the Information;
ensure that you do not mislead others or misrepresent the Information or its source;
ensure that your use of the Information does not breach the Data Protection Act 1998 or the Privacy and Electronic Communications (EC Directive) Regulations 2003.
These are important conditions of this licence and if you fail to comply with them the rights granted to you under this licence, or any similar licence granted by the Licensor, will end automatically.
 Exemptions
Exemptions
This licence does not cover the use of:
- personal data in the Information;
@ -48,22 +48,22 @@ Definitions
In this licence, the terms below have the following meanings:
Informationmeans information protected by copyright or by database right (for example, literary and artistic works, content, data and source code) offered for use under the terms of this licence.
Information means information protected by copyright or by database right (for example, literary and artistic works, content, data and source code) offered for use under the terms of this licence.
Information Providermeans the person or organisation providing the Information under this licence.
Information Provider means the person or organisation providing the Information under this licence.
Licensormeans any Information Provider which has the authority to offer Information under the terms of this licence or the Controller of Her Majestys Stationery Office, who has the authority to offer Information subject to Crown copyright and Crown database rights and Information subject to copyright and database right that has been assigned to or acquired by the Crown, under the terms of this licence.
Licensor means any Information Provider which has the authority to offer Information under the terms of this licence or the Controller of Her Majestys Stationery Office, who has the authority to offer Information subject to Crown copyright and Crown database rights and Information subject to copyright and database right that has been assigned to or acquired by the Crown, under the terms of this licence.
Useas a verb, means doing any act which is restricted by copyright or database right, whether in the original medium or in any other medium, and includes without limitation distributing, copying, adapting, modifying as may be technically necessary to use it in a different mode or format.
Use as a verb, means doing any act which is restricted by copyright or database right, whether in the original medium or in any other medium, and includes without limitation distributing, copying, adapting, modifying as may be technically necessary to use it in a different mode or format.
Youmeans the natural or legal person, or body of persons corporate or incorporate, acquiring rights under this licence.
You means the natural or legal person, or body of persons corporate or incorporate, acquiring rights under this licence.
About the Open Government Licence
The Controller of Her Majestys Stationery Office (HMSO) has developed this licence as a tool to enable Information Providers in the public sector to license the use and re-use of their Information under a common open licence. The Controller invites public sector bodies owning their own copyright and database rights to permit the use of their Information under this licence.
The Controller of HMSO has authority to license Information subject to copyright and database right owned by the Crown. The extent of the Controllers offer to license this Information under the terms of this licence is set out in the UK Government Licensing Framework.
The Controller of HMSO has authority to license Information subject to copyright and database right owned by the Crown. The extent of the Controllers offer to license this Information under the terms of this licence is set out in the UK Government Licensing Framework.
This is version 1.0 of the Open Government Licence. The Controller of HMSO may, from time to time, issue new versions of the Open Government Licence. However, you may continue to use Information licensed under this version should you wish to do so.
These terms have been aligned to be interoperable with any Creative Commons Attribution Licence, which covers copyright, and Open Data Commons Attribution License, which covers database rights and applicable copyrights.
Further context, best practice and guidance can be found in the UK Government Licensing Framework section on The National Archives website.
Further context, best practice and guidance can be found in the UK Government Licensing Framework section on The National Archives website.

View File

@ -100,7 +100,8 @@ If it is impossible for You to comply with any of the terms of this License with
5.1 Failure to Comply
The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60-days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30-days after Your receipt of the notice.
5.2 Patent Infringement Claims If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
5.2 Patent Infringement Claims
If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
5.3 Additional Compliance Terms
Notwithstanding the foregoing in this Section 5, for purposes of this Section, if You breach Section 3.1 (Distribution of Source Form), Section 3.2 (Distribution of Executable Form), Section 3.3 (Distribution of a Larger Work), or Section 3.4 (Notices), then becoming compliant as described in Section 5.1 must also include, no later than 30 days after receipt by You of notice of such violation by a Contributor, making the Covered Software available in Source Code Form as required by this License on a publicly available computer network for a period of no less than three (3) years.

View File

@ -1,22 +1,22 @@
# Solderpad Hardware Licence Version 2.0
This licence (the “Licence”) operates as a wraparound licence to the Apache License Version 2.0 (the “Apache License”) and grants to You the rights, and imposes the obligations, set out in the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following extensions. It must be read in conjunction with the Apache License. Section 1 below modifies definitions in the Apache License, and section 2 below replaces sections 2 of the Apache License. You may, at your option, choose to treat any Work released under this License as released under the Apache License (thus ignoring all sections written below entirely). Words in italics indicate changes rom the Apache License, but are indicative and not to be taken into account in interpretation.
This licence (the “Licence”) operates as a wraparound licence to the Apache License Version 2.0 (the “Apache License”) and grants to You the rights, and imposes the obligations, set out in the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following extensions. It must be read in conjunction with the Apache License. Section 1 below modifies definitions in the Apache License, and section 2 below replaces sections 2 of the Apache License. You may, at your option, choose to treat any Work released under this License as released under the Apache License (thus ignoring all sections written below entirely). Words in italics indicate changes rom the Apache License, but are indicative and not to be taken into account in interpretation.
1. The definitions set out in the Apache License are modified as follows:
Copyright any reference to copyright (whether capitalised or not) includes Rights (as defined below).
Copyright any reference to copyright (whether capitalised or not) includes Rights (as defined below).
Contribution also includes any design, as well as any work of authorship.
Contribution also includes any design, as well as any work of authorship.
Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of the Work and Derivative Works thereof.
Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of the Work and Derivative Works thereof.
Object form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works).
Object form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works).
Rights means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks).
Rights means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks).
Source form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files.
Work also includes a design or work of authorship, whether in Source form or other Object form.
Source form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files.
Work also includes a design or work of authorship, whether in Source form or other Object form.
2. Grant of Licence
2.1 Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist.
2.1 Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist.

View File

@ -19,7 +19,7 @@ The following definitions shall replace the corresponding definitions in the Apa
"License" shall mean this Solderpad Hardware License version 2.1, being the terms and conditions for use, manufacture, instantiation, adaptation, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the Rights owner or entity authorized by the Rights owner that is granting the License.
 
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship or design. For the purposes of this License, Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the Work and Derivative Works thereof.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object or material and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works).

View File

@ -36,13 +36,13 @@ Sun Industry Standards Source License - Version 1.1
2.0 SOURCE CODE LICENSE
2.1 The Initial Developer Grant The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: 
2.1 The Initial Developer Grant The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims:
(a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, and/or as part of a Larger Work; and
(b) under Patents Claims infringed by the making, using or selling of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Code (or portions thereof).
(c) the licenses granted in this Section 2.1(a) and (b) are effective on the date Initial Developer first distributes Original Code under the terms of this License.
(d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices, including but not limited to Modifications. 
(d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices, including but not limited to Modifications.
3.0 DISTRIBUTION OBLIGATIONS
@ -92,14 +92,14 @@ This License represents the complete agreement concerning subject matter hereof.
EXHIBIT A - Sun Standards License
"The contents of this file are subject to the Sun Standards License Version 1.1 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at _______________________________.
"The contents of this file are subject to the Sun Standards License Version 1.1 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at _______________________________.
Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
express or implied. See the License for the specific language governing rights and limitations under the License.
The Original Code is ______________________________________.
The Initial Developer of the Original Code is: 
The Initial Developer of the Original Code is:
Sun Microsystems, Inc..
Portions created by: _______________________________________

View File

@ -4,7 +4,7 @@ Copyright (c) 1994-2002 World Wide Web Consortium, (Massachusetts Institute of T
This W3C work (including software, documents, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its documentation, with or without modification,  for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make:
Permission to use, copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make:
1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.

12
options/license/xzoom Normal file
View File

@ -0,0 +1,12 @@
Copyright Itai Nahshon 1995, 1996.
This program is distributed with no warranty.
Source files for this program may be distributed freely.
Modifications to this file are okay as long as:
a. This copyright notice and comment are preserved and
left at the top of the file.
b. The man page is fixed to reflect the change.
c. The author of this change adds his name and change
description to the list of changes below.
Executable files may be distributed with sources, or with
exact location where the source code can be obtained.

View File

@ -2790,7 +2790,6 @@ settings=Nastavení správce
dashboard.new_version_hint=Gitea %s je nyní k dispozici, právě u vás běži %s. Podívej se na <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blogu</a> pro více informací.
dashboard.statistic=Souhrn
dashboard.operations=Operace údržby
dashboard.system_status=Status systému
dashboard.operation_name=Název operace
dashboard.operation_switch=Přepnout

View File

@ -2798,7 +2798,6 @@ settings=Administratoreinstellungen
dashboard.new_version_hint=Gitea %s ist jetzt verfügbar, deine derzeitige Version ist %s. Weitere Details findest du im <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">Blog</a>.
dashboard.statistic=Übersicht
dashboard.operations=Wartungsoperationen
dashboard.system_status=System-Status
dashboard.operation_name=Name der Operation
dashboard.operation_switch=Wechseln

View File

@ -2687,7 +2687,6 @@ settings=Ρυθμίσεις Διαχειριστή
dashboard.new_version_hint=Το Gitea %s είναι διαθέσιμο, τώρα εκτελείτε το %s. Ανατρέξτε <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">στο blog</a> για περισσότερες λεπτομέρειες.
dashboard.statistic=Περίληψη
dashboard.operations=Λειτουργίες Συντήρησης
dashboard.system_status=Κατάσταση Συστήματος
dashboard.operation_name=Όνομα Λειτουργίας
dashboard.operation_switch=Αλλαγή

View File

@ -1233,6 +1233,8 @@ file_view_rendered = View Rendered
file_view_raw = View Raw
file_permalink = Permalink
file_too_large = The file is too large to be shown.
code_preview_line_from_to = Lines %[1]d to %[2]d in %[3]s
code_preview_line_in = Line %[1]d in %[2]s
invisible_runes_header = `This file contains invisible Unicode characters`
invisible_runes_description = `This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.`
ambiguous_runes_header = `This file contains ambiguous Unicode characters`

View File

@ -2672,7 +2672,6 @@ settings=Configuración de Admin
dashboard.new_version_hint=Gitea %s ya está disponible, estás ejecutando %s. Revisa <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">el blog</a> para más detalles.
dashboard.statistic=Resumen
dashboard.operations=Operaciones de mantenimiento
dashboard.system_status=Estado del sistema
dashboard.operation_name=Nombre de la operación
dashboard.operation_switch=Interruptor

View File

@ -2064,7 +2064,6 @@ last_page=واپسین
total=مجموع: %d
dashboard.statistic=چکیده
dashboard.operations=عملیات‌های نگهداری
dashboard.system_status=وضعیت سامانه
dashboard.operation_name=نام عملیات
dashboard.operation_switch=تعویض

View File

@ -1407,7 +1407,6 @@ last_page=Viimeisin
total=Yhteensä: %d
dashboard.statistic=Yhteenveto
dashboard.operations=Huoltotoimet
dashboard.system_status=Järjestelmän tila
dashboard.operation_name=Toiminnon nimi
dashboard.operation_switch=Vaihda

View File

@ -2712,7 +2712,6 @@ settings=Paramètres administrateur
dashboard.new_version_hint=Gitea %s est maintenant disponible, vous utilisez %s. Consultez <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">le blog</a> pour plus de détails.
dashboard.statistic=Résumé
dashboard.operations=Opérations de maintenance
dashboard.system_status=État du système
dashboard.operation_name=Nom de l'Opération
dashboard.operation_switch=Basculer

View File

@ -1266,7 +1266,6 @@ last_page=Utolsó
total=Összesen: %d
dashboard.statistic=Összefoglaló
dashboard.operations=Karbantartási műveletek
dashboard.system_status=Rendszer Állapota
dashboard.operation_name=Művelet Neve
dashboard.operation_switch=Váltás

View File

@ -2233,7 +2233,6 @@ last_page=Ultima
total=Totale: %d
dashboard.statistic=Riepilogo
dashboard.operations=Operazioni di manutenzione
dashboard.system_status=Stato del sistema
dashboard.operation_name=Nome Operazione
dashboard.operation_switch=Cambia

View File

@ -113,6 +113,7 @@ loading=読み込み中…
error=エラー
error404=アクセスしようとしたページは<strong>存在しない</strong>か、閲覧が<strong>許可されていません</strong>。
go_back=戻る
invalid_data=無効なデータ: %v
never=無し
unknown=不明
@ -143,13 +144,41 @@ name=名称
value=
filter=フィルター
filter.clear=フィルターをクリア
filter.is_archived=アーカイブ
filter.not_archived=非アーカイブ
filter.is_fork=フォーク
filter.not_fork=非フォーク
filter.is_mirror=ミラー
filter.not_mirror=非ミラー
filter.is_template=テンプレート
filter.not_template=非テンプレート
filter.public=公開
filter.private=プライベート
no_results_found=見つかりません。
[search]
search=検索…
type_tooltip=検索タイプ
fuzzy=あいまい
fuzzy_tooltip=検索ワードに近い結果も含めます
match=一致
match_tooltip=検索ワードと完全に一致する結果のみ含めます
repo_kind=リポジトリを検索...
user_kind=ユーザーを検索...
org_kind=組織を検索...
team_kind=チームを検索…
code_kind=コードを検索...
code_search_unavailable=現在コード検索は利用できません。 サイト管理者にお問い合わせください。
code_search_by_git_grep=現在のコード検索結果は "git grep" で提供されています。 サイト管理者がリポジトリインデクサーを有効にすると、より良い結果が得られるかもしれません。
package_kind=パッケージを検索...
project_kind=プロジェクトを検索...
branch_kind=ブランチを検索...
commit_kind=コミットを検索...
runner_kind=ランナーを検索...
no_results=一致する結果が見つかりませんでした
keyword_search_unavailable=現在キーワード検索は利用できません。 サイト管理者にお問い合わせください。
[aria]
navbar=ナビゲーションバー
@ -256,6 +285,7 @@ email_title=メール設定
smtp_addr=SMTPホスト
smtp_port=SMTPポート
smtp_from=メール送信者
smtp_from_invalid=「メール送信者」のアドレスが無効です
smtp_from_helper=Giteaが使用するメールアドレス。 メールアドレスのみ、または、 "名前" <email@example.com> の形式で入力してください。
mailer_user=SMTPユーザー名
mailer_password=SMTPパスワード
@ -315,6 +345,7 @@ env_config_keys=環境設定
env_config_keys_prompt=以下の環境変数も設定ファイルに適用されます:
[home]
nav_menu=ナビゲーションメニュー
uname_holder=ユーザー名またはメールアドレス
password_holder=パスワード
switch_dashboard_context=ダッシュボードのコンテキスト切替
@ -618,6 +649,23 @@ block.block.org=組織向けにユーザーをブロック
block.block.failure=ユーザーのブロックに失敗しました: %s
block.unblock=ブロックを解除
block.unblock.failure=ユーザーのブロック解除に失敗しました: %s
block.blocked=あなたはこのユーザーをブロックしています。
block.title=ユーザーをブロックする
block.info=ユーザーをブロックすると、そのユーザーは、プルリクエストやイシューの作成、コメントの投稿など、リポジトリに対する操作ができなくなります。 ユーザーのブロックについてはよく確認してください。
block.info_1=ユーザーをブロックすることで、あなたのアカウントとあなたのリポジトリに対する以下の行為を阻止します:
block.info_2=あなたのアカウントのフォロー
block.info_3=あなたのユーザー名で@メンションして通知を送ること
block.info_4=そのユーザーのリポジトリに、あなたを共同作業者として招待すること
block.info_5=リポジトリへの、スター、フォーク、ウォッチ
block.info_6=イシューやプルリクエストの作成、コメント投稿
block.info_7=イシューやプルリクエストでの、あなたのコメントに対するリアクションの送信
block.user_to_block=ブロックするユーザー
block.note=メモ
block.note.title=メモ(任意):
block.note.info=メモはブロックされるユーザーには表示されません。
block.note.edit=メモを編集
block.list=ブロックしたユーザー
block.list.none=ブロックしているユーザーはいません。
[settings]
profile=プロフィール
@ -661,8 +709,8 @@ language=言語
ui=テーマ
hidden_comment_types=非表示にするコメントの種類
hidden_comment_types_description=ここでチェックを入れたコメントの種類は、イシューのページには表示されません。 たとえば「ラベル」にチェックを入れると、「<ユーザー> が <ラベル> を追加/削除」といったコメントはすべて除去されます。
hidden_comment_types.ref_tooltip=このイシューが別のイシューやコミット等から参照されたというコメント
hidden_comment_types.issue_ref_tooltip=このイシューに関連付けるブランチやタグをユーザーが変更したというコメント
hidden_comment_types.ref_tooltip=このイシューが別のイシューやコミット等から参照されたというコメント
hidden_comment_types.issue_ref_tooltip=このイシューのブランチやタグへの関連付けをユーザーが変更した、というコメント
comment_type_group_reference=参照
comment_type_group_label=ラベル
comment_type_group_milestone=マイルストーン
@ -732,7 +780,7 @@ add_email_success=新しいメールアドレスを追加しました。
email_preference_set_success=メール設定を保存しました。
add_openid_success=新しいOpenIDアドレスを追加しました。
keep_email_private=メールアドレスを隠す
keep_email_private_popup=これによりプロフィールでメールアドレスが隠され、Webインターフェースでのプルリクエスト作成やファイル編集でもメールアドレスが隠されます。 プッシュ済みのコミットは変更されません。
keep_email_private_popup=あなたのプロフィールからメールアドレスが隠され、Webインターフェースを使ったプルリクエスト作成やファイル編集でもメールアドレスが隠されます。 プッシュ済みのコミットは変更されません。 コミットであなたのアカウントに関連付ける場合は %s を使用してください
openid_desc=OpenIDを使うと外部プロバイダーに認証を委任することができます。
manage_ssh_keys=SSHキーの管理
@ -956,6 +1004,7 @@ fork_branch=フォークにクローンされるブランチ
all_branches=すべてのブランチ
fork_no_valid_owners=このリポジトリには有効なオーナーがいないため、フォークできません。
use_template=このテンプレートを使用
open_with_editor=%s で開く
download_zip=ZIPファイルをダウンロード
download_tar=TAR.GZファイルをダウンロード
download_bundle=バンドルをダウンロード
@ -1008,6 +1057,7 @@ watchers=ウォッチャー
stargazers=スターゲイザー
stars_remove_warning=これを指定すると、このリポジトリのスターはすべて削除されます。
forks=フォーク
stars=スター
reactions_more=さらに %d 件
unit_disabled=サイト管理者がこのリポジトリセクションを無効にしています。
language_other=その他
@ -1039,7 +1089,7 @@ transfer.no_permission_to_reject=この移転を拒否する権限がありま
desc.private=プライベート
desc.public=公開
desc.template=テンプレート
desc.internal=組織
desc.internal=
desc.archived=アーカイブ
desc.sha256=SHA256
@ -1257,6 +1307,8 @@ editor.file_editing_no_longer_exists=編集中のファイル "%s" が、もう
editor.file_deleting_no_longer_exists=削除しようとしたファイル "%s" が、すでにリポジトリ内にありません。
editor.file_changed_while_editing=あなたが編集を開始したあと、ファイルの内容が変更されました。 <a target="_blank" rel="noopener noreferrer" href="%s">ここをクリック</a>して何が変更されたか確認するか、<strong>もう一度"変更をコミット"をクリック</strong>して上書きします。
editor.file_already_exists=ファイル "%s" は、このリポジトリに既に存在します。
editor.commit_id_not_matching=コミットIDが編集を開始したときのIDと一致しません。 パッチ用のブランチにコミットしたあとマージしてください。
editor.push_out_of_date=このプッシュは最新ではないようです。
editor.commit_empty_file_header=空ファイルのコミット
editor.commit_empty_file_text=コミットしようとしているファイルは空です。 続行しますか?
editor.no_changes_to_show=表示する変更箇所はありません。
@ -1281,6 +1333,7 @@ commits.commits=コミット
commits.no_commits=共通のコミットはありません。 "%s" と "%s" の履歴はすべて異なっています。
commits.nothing_to_compare=二つのブランチは同じ内容です。
commits.search.tooltip=`キーワード "author:"、"committer:"、"after:"、"before:" を付けて指定できます。 例 "revert author:Alice before:2019-01-13"`
commits.search_branch=このブランチ
commits.search_all=すべてのブランチ
commits.author=作成者
commits.message=メッセージ
@ -1339,6 +1392,7 @@ projects.column.new=新しい列
projects.column.set_default=デフォルトに設定
projects.column.set_default_desc=この列を未分類のイシューやプルリクエストが入るデフォルトの列にします
projects.column.delete=列を削除
projects.column.deletion_desc=プロジェクト列を削除すると、関連するすべてのイシューがデフォルトの列に移動します。 続行しますか?
projects.column.color=カラー
projects.open=オープン
projects.close=クローズ
@ -1738,7 +1792,7 @@ pulls.is_checking=マージのコンフリクトを確認中です。 少し待
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
pulls.required_status_check_missing=必要なステータスチェックが見つかりません
pulls.required_status_check_missing=必要なチェックがいくつか抜けています
pulls.required_status_check_administrator=管理者であるため、このプルリクエストをマージすることは可能です。
pulls.blocked_by_approvals=このプルリクエストはまだ承認数が足りません。 %[1]d/%[2]dの承認を得ています。
pulls.blocked_by_rejection=このプルリクエストは公式レビューアにより変更要請されています。
@ -1907,6 +1961,7 @@ wiki.original_git_entry_tooltip=フレンドリーリンクを使用する代わ
activity=アクティビティ
activity.navbar.pulse=Pulse
activity.navbar.contributors=貢献者
activity.navbar.recent_commits=最近のコミット
activity.period.filter_label=期間:
activity.period.daily=1日
activity.period.halfweekly=3日
@ -2571,6 +2626,7 @@ component_loading_failed=%sを読み込めませんでした
component_loading_info=少し時間がかかるかもしれません…
component_failed_to_load=予期しないエラーが発生しました。
contributors.what=実績
recent_commits.what=最近のコミット
[org]
org_name_holder=組織名
@ -2684,6 +2740,7 @@ teams.add_nonexistent_repo=追加しようとしているリポジトリは存
teams.add_duplicate_users=ユーザーは既にチームのメンバーです。
teams.repos.none=このチームがアクセスできるリポジトリはありません。
teams.members.none=このチームにはメンバーがいません。
teams.members.blocked_user=組織によってブロックされているため、ユーザーを追加できません。
teams.specific_repositories=指定したリポジトリ
teams.specific_repositories_helper=メンバーは、明示的にチームへ追加したリポジトリにのみアクセスできます。 これを選択しても、すでに<i>すべてのリポジトリ</i>で追加されたリポジトリは自動的に除去<strong>されません</strong>。
teams.all_repositories=すべてのリポジトリ
@ -2719,7 +2776,6 @@ settings=管理設定
dashboard.new_version_hint=Gitea %s が入手可能になりました。 現在実行しているのは %s です。 詳細は <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">ブログ</a> を確認してください。
dashboard.statistic=サマリー
dashboard.operations=メンテナンス操作
dashboard.system_status=システム状況
dashboard.operation_name=操作の名称
dashboard.operation_switch=切り替え
@ -2905,12 +2961,12 @@ packages.size=サイズ
packages.published=配布
defaulthooks=デフォルトWebhook
defaulthooks.desc=Webhookは、特定のGiteaイベントのトリガーが発生した際に、自動的にHTTP POSTリクエストをサーバーへ送信するものです。 ここで定義されたWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">Webhooksガイド</a>をご覧下さい。
defaulthooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">Webhooksガイド</a>をご覧下さい。
defaulthooks.add_webhook=デフォルトWebhookの追加
defaulthooks.update_webhook=デフォルトWebhookの更新
systemhooks=システムWebhook
systemhooks.desc=Webhookは、特定のGiteaイベントのトリガーが発生した際に、自動的にHTTP POSTリクエストをサーバーへ送信するものです。 ここで定義したWebhookはシステム内のすべてのリポジトリで呼び出されます。 そのため、パフォーマンスに及ぼす影響を考慮したうえで設定してください。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">Webhooksガイド</a>をご覧下さい。
systemhooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookはシステム内のすべてのリポジトリで呼び出されます。 そのため、パフォーマンスに及ぼす影響を考慮したうえで設定してください。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">Webhooksガイド</a>をご覧下さい。
systemhooks.add_webhook=システムWebhookを追加
systemhooks.update_webhook=システムWebhookを更新
@ -3010,6 +3066,7 @@ auths.tip.nextcloud=新しいOAuthコンシューマーを、インスタンス
auths.tip.dropbox=新しいアプリケーションを https://www.dropbox.com/developers/apps から登録してください。
auths.tip.facebook=新しいアプリケーションを https://developers.facebook.com/apps で登録し、"Facebook Login"を追加してください。
auths.tip.github=新しいOAuthアプリケーションを https://github.com/settings/applications/new から登録してください。
auths.tip.gitlab_new=新しいアプリケーションを https://gitlab.com/-/profile/applications から登録してください。
auths.tip.google_plus=OAuth2クライアント資格情報を、Google APIコンソール https://console.developers.google.com/ から取得してください。
auths.tip.openid_connect=OpenID Connect DiscoveryのURL (<server>/.well-known/openid-configuration) をエンドポイントとして指定してください
auths.tip.twitter=https://dev.twitter.com/apps へアクセスしてアプリケーションを作成し、“Allow this application to be used to Sign in with Twitter”オプションを有効にしてください。
@ -3145,6 +3202,7 @@ config.picture_config=画像とアバターの設定
config.picture_service=画像サービス
config.disable_gravatar=Gravatarが無効
config.enable_federated_avatar=フェデレーテッド・アバター有効
config.open_with_editor_app_help=クローンメニューの「~で開く」に表示するエディタ。 空白のままにするとデフォルトが使用されます。 展開するとデフォルトを確認できます。
config.git_config=Git設定
config.git_disable_diff_highlight=Diffのシンタックスハイライトが無効
@ -3284,9 +3342,9 @@ raw_seconds=秒
raw_minutes=
[dropzone]
default_message=ここにファイルをドロップまたはクリックしてアップロードします。
default_message=ファイルをここにドロップまたはここをクリックしてアップロード
invalid_input_type=この種類のファイルはアップロードできません。
file_too_big=アップロードされたファイルのサイズ ({{filesize}} MB) 最大サイズ ({{maxFilesize}} MB) を超えています。
file_too_big=アップロードされたファイルのサイズ ({{filesize}} MB) は、最大サイズ ({{maxFilesize}} MB) を超えています。
remove_file=ファイル削除
[notification]
@ -3311,7 +3369,7 @@ error.no_committer_account=コミッターのメールアドレスに対応す
error.no_gpg_keys_found=この署名に対応する既知のキーがデータベースに存在しません
error.not_signed_commit=署名されたコミットではありません
error.failed_retrieval_gpg_keys=コミッターのアカウントに登録されたキーを取得できませんでした
error.probable_bad_signature=警告! このIDの鍵はデータベースに登録されていますが、その鍵でコミットの検証が通りません! これは疑わしいコミットです。
error.probable_bad_signature=警告! このIDに該当する鍵がデータベースにありますが、コミットの検証が通りません! これは疑わしいコミットです。
error.probable_bad_default_signature=警告! これはデフォルト鍵のIDですが、デフォルト鍵ではコミットの検証が通りません! これは疑わしいコミットです。
[units]
@ -3324,7 +3382,7 @@ title=パッケージ
desc=リポジトリ パッケージを管理します。
empty=パッケージはまだありません。
empty.documentation=パッケージレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
empty.repo=パッケージはアップロードしたけども、ここに表示されない <a href="%[1]s">パッケージ設定</a>を開いて、パッケージをこのリポジトリにリンクしてください。
empty.repo=パッケージはアップロード済みで、ここに表示されていないですか <a href="%[1]s">パッケージ設定</a>を開いて、パッケージをこのリポジトリにリンクしてください。
registry.documentation=%sレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
filter.type=タイプ
filter.type.all=すべて
@ -3543,6 +3601,7 @@ runs.scheduled=スケジュール済み
runs.pushed_by=pushed by
runs.invalid_workflow_helper=ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s
runs.no_matching_online_runner_helper=ラベルに一致するオンラインのランナーが見つかりません: %s
runs.no_job_without_needs=ワークフローには依存関係のないジョブが少なくとも1つ含まれている必要があります。
runs.actor=アクター
runs.status=ステータス
runs.actors_no_select=すべてのアクター

View File

@ -2693,7 +2693,6 @@ settings=Administratora iestatījumi
dashboard.new_version_hint=Ir pieejama Gitea versija %s, pašreizējā versija %s. Papildus informācija par jauno versiju ir pieejama <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">mājas lapā</a>.
dashboard.statistic=Kopsavilkums
dashboard.operations=Uzturēšanas darbības
dashboard.system_status=Sistēmas statuss
dashboard.operation_name=Darbības nosaukums
dashboard.operation_switch=Pārslēgt

View File

@ -2135,7 +2135,6 @@ last_page=Laatste
total=Totaal: %d
dashboard.statistic=Overzicht
dashboard.operations=Onderhoudswerkzaamheden
dashboard.system_status=Systeemtatus
dashboard.operation_name=Bewerking naam
dashboard.operation_switch=Omschakelen

View File

@ -2010,7 +2010,6 @@ last_page=Ostatnia
total=Ogółem: %d
dashboard.statistic=Podsumowanie
dashboard.operations=Operacje konserwacji
dashboard.system_status=Status strony
dashboard.operation_name=Nazwa operacji
dashboard.operation_switch=Przełącz

View File

@ -2648,7 +2648,6 @@ settings=Configurações de Administrador
dashboard.new_version_hint=Uma nova versão está disponível: %s. Versão atual: %s. Visite <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">o blog</a> para mais informações.
dashboard.statistic=Resumo
dashboard.operations=Operações de manutenção
dashboard.system_status=Status do sistema
dashboard.operation_name=Nome da operação
dashboard.operation_switch=Trocar

View File

@ -1233,6 +1233,8 @@ file_view_rendered=Ver resultado processado
file_view_raw=Ver em bruto
file_permalink=Ligação permanente
file_too_large=O ficheiro é demasiado grande para ser apresentado.
code_preview_line_from_to=Linhas %[1]d até %[2]d em %[3]s
code_preview_line_in=Linha %[1]d em %[2]s
invisible_runes_header=`Este ficheiro contém caracteres Unicode invisíveis`
invisible_runes_description=`Este ficheiro contém caracteres Unicode indistinguíveis para humanos mas que podem ser processados de forma diferente por um computador. Se acha que é intencional, pode ignorar este aviso com segurança. Use o botão Revelar para os mostrar.`
ambiguous_runes_header=`Este ficheiro contém caracteres Unicode ambíguos`
@ -2775,6 +2777,7 @@ teams.invite.by=Convidado(a) por %s
teams.invite.description=Clique no botão abaixo para se juntar à equipa.
[admin]
maintenance=Manutenção
dashboard=Painel de controlo
self_check=Auto-verificação
identity_access=Identidade e acesso
@ -2798,7 +2801,7 @@ settings=Configurações de administração
dashboard.new_version_hint=O Gitea %s está disponível, você está a correr a versão %s. Verifique o <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> para mais detalhes.
dashboard.statistic=Resumo
dashboard.operations=Operações de manutenção
dashboard.maintenance_operations=Operações de manutenção
dashboard.system_status=Estado do sistema
dashboard.operation_name=Nome da operação
dashboard.operation_switch=Comutar
@ -3305,6 +3308,7 @@ notices.op=Op.
notices.delete_success=As notificações do sistema foram eliminadas.
self_check.no_problem_found=Nenhum problema encontrado até agora.
self_check.startup_warnings=Alertas do arranque:
self_check.database_collation_mismatch=Supor que a base de dados usa a colação: %s
self_check.database_collation_case_insensitive=A base de dados está a usar a colação %s, que é insensível à diferença entre maiúsculas e minúsculas. Embora o Gitea possa trabalhar com ela, pode haver alguns casos raros que não funcionem como esperado.
self_check.database_inconsistent_collation_columns=A base de dados está a usar a colação %s, mas estas colunas estão a usar colações diferentes. Isso poderá causar alguns problemas inesperados.

View File

@ -2634,7 +2634,6 @@ total=Всего: %d
dashboard.new_version_hint=Доступна новая версия Gitea %s, вы используете %s. Более подробную информацию читайте в <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">блоге</a>.
dashboard.statistic=Статистика
dashboard.operations=Операции
dashboard.system_status=Состояние системы
dashboard.operation_name=Имя операции
dashboard.operation_switch=Переключить

View File

@ -2024,7 +2024,6 @@ last_page=පසුගිය
total=මුළු: %d
dashboard.statistic=සාරාංශය
dashboard.operations=නඩත්තු මෙහෙයුම්
dashboard.system_status=පද්ධතියේ තත්වය
dashboard.operation_name=මෙහෙයුමේ නම
dashboard.operation_switch=මාරුවන්න

View File

@ -1647,7 +1647,6 @@ last_page=Sista
total=Totalt: %d
dashboard.statistic=Översikt
dashboard.operations=Operationer för underhåll
dashboard.system_status=Status
dashboard.operation_name=Operationsnamn
dashboard.operation_switch=Byt till

View File

@ -2687,7 +2687,6 @@ settings=Yönetici Ayarları
dashboard.new_version_hint=Gitea %s şimdi hazır, %s çalıştırıyorsunuz. Ayrıntılar için <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a>'a bakabilirsiniz.
dashboard.statistic=Özet
dashboard.operations=Bakım İşlemleri
dashboard.system_status=Sistem Durumu
dashboard.operation_name=İşlem Adı
dashboard.operation_switch=Geç

View File

@ -2074,7 +2074,6 @@ last_page=Остання
total=Разом: %d
dashboard.statistic=Підсумок
dashboard.operations=Технічне обслуговування
dashboard.system_status=Статус системи
dashboard.operation_name=Назва операції
dashboard.operation_switch=Перемкнути

Some files were not shown because too many files have changed in this diff Show More