diff options
99 files changed, 2486 insertions, 767 deletions
diff --git a/cmd/doctor.go b/cmd/doctor.go index 73dfeb1db..3f16c6e2a 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -203,7 +203,7 @@ func runDoctor(ctx *cli.Context) error { // Now we can set up our own logger to return information about what the doctor is doing if err := log.NewNamedLogger("doctorouter", - 1000, + 0, "console", "console", fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil { diff --git a/cmd/dump.go b/cmd/dump.go index ea41c0c02..d807cb058 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -22,7 +22,7 @@ import ( "code.gitea.io/gitea/modules/util" "gitea.com/go-chi/session" - archiver "github.com/mholt/archiver/v3" + "github.com/mholt/archiver/v3" "github.com/urfave/cli" ) @@ -439,8 +439,23 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA } } } else { - if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil { - return err + // 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 + } } } } diff --git a/cmd/hook.go b/cmd/hook.go index 8078763b1..73386038b 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -308,6 +308,8 @@ func runHookPostReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() + setup("hooks/post-receive.log", c.Bool("debug")) + // First of all run update-server-info no matter what if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { return fmt.Errorf("Failed to call 'git update-server-info': %v", err) @@ -318,8 +320,6 @@ func runHookPostReceive(c *cli.Context) error { return nil } - setup("hooks/post-receive.log", c.Bool("debug")) - if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if setting.OnlyAllowPushIfGiteaEnvironmentSet { return fail(`Rejecting changes as Gitea environment not set. diff --git a/cmd/web.go b/cmd/web.go index 8c7c02617..43bb0ada9 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/install" + "github.com/felixge/fgprof" "github.com/urfave/cli" ini "gopkg.in/ini.v1" ) @@ -145,6 +146,7 @@ func runWeb(ctx *cli.Context) error { if setting.EnablePprof { go func() { + http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true) log.Info("Starting pprof server on localhost:6060") log.Info("%v", http.ListenAndServe("localhost:6060", nil)) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2fb2c1a56..fb7770a4a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2181,8 +2181,11 @@ PATH = ;RENDER_COMMAND = "asciidoc --out-file=- -" ;; Don't pass the file on STDIN, pass the filename as argument instead. ;IS_INPUT_FILE = false -; Don't filter html tags and attributes if true -;DISABLE_SANITIZER = false +;; How the content will be rendered. +;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] . +;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code. +;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page. +;RENDER_CONTENT_MODE=sanitized ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2249,8 +2252,13 @@ PATH = ;; Enable/Disable user statistics for nodeinfo if federation is enabled ; SHARE_USER_STATISTICS = true ;; +;; Maximum federation request and response size (MB) +; MAX_SIZE = 4 +;; +;; WARNING: Changing the settings below can break federation. +;; ;; HTTP signature algorithms -; ALGORITHMS = rsa-sha256, rsa-sha512 +; ALGORITHMS = rsa-sha256, rsa-sha512, ed25519 ;; ;; HTTP signature digest algorithm ; DIGEST_ALGORITHM = SHA-256 diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 314ecf478..4710fdbe9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1026,13 +1026,16 @@ IS_INPUT_FILE = false command. Multiple extensions needs a comma as splitter. - RENDER\_COMMAND: External command to render all matching extensions. - IS\_INPUT\_FILE: **false** Input is not a standard input but a file param followed `RENDER_COMMAND`. -- DISABLE_SANITIZER: **false** Don't filter html tags and attributes if true. Don't change this to true except you know what that means. +- RENDER_CONTENT_MODE: **sanitized** How the content will be rendered. + - sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in `[markup.sanitizer.*]`. + - no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code. + - iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page. Two special environment variables are passed to the render command: - `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links. - `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths. -If `DISABLE_SANITIZER` is false, Gitea supports customizing the sanitization policy for rendered HTML. The example below will support KaTeX output from pandoc. +If `RENDER_CONTENT_MODE` is `sanitized`, Gitea supports customizing the sanitization policy for rendered HTML. The example below will support KaTeX output from pandoc. ```ini [markup.sanitizer.TeX] @@ -1087,7 +1090,11 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `ENABLED`: **true**: Enable/Disable federation capabilities - `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled -- `ALGORITHMS`: **rsa-sha256, rsa-sha512**: HTTP signature algorithms +- `MAX_SIZE`: **4**: Maximum federation request and response size (MB) + + WARNING: Changing the settings below can break federation. + +- `ALGORITHMS`: **rsa-sha256, rsa-sha512, ed25519**: HTTP signature algorithms - `DIGEST_ALGORITHM`: **SHA-256**: HTTP signature digest algorithm - `GET_HEADERS`: **(request-target), Date**: GET headers for federation requests - `POST_HEADERS`: **(request-target), Date, Digest**: POST headers for federation requests diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index cc6e950fb..ef1504bc9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -318,14 +318,17 @@ IS_INPUT_FILE = false - FILE_EXTENSIONS: 关联的文档的扩展名,多个扩展名用都好分隔。 - RENDER_COMMAND: 工具的命令行命令及参数。 - IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。 -- DISABLE_SANITIZER: **false** 如果为 true 则不过滤 HTML 标签和属性。除非你知道这意味着什么,否则不要设置为 true。 +- RENDER_CONTENT_MODE: **sanitized** 内容如何被渲染。 + - sanitized: 对内容进行净化并渲染到当前页面中,仅有一部分 HTML 标签和属性是被允许的。 + - no-sanitizer: 禁用净化器,把内容渲染到当前页面中。此模式是**不安全**的,如果内容中含有恶意代码,可能会导致 XSS 攻击。 + - iframe: 把内容渲染在一个独立的页面中并使用 iframe 嵌入到当前页面中。使用的 iframe 工作在沙箱模式并禁用了同源请求,JS 代码被安全的从父页面中隔离出去。 以下两个环境变量将会被传递给渲染命令: - `GITEA_PREFIX_SRC`:包含当前的`src`路径的URL前缀,可以被用于链接的前缀。 - `GITEA_PREFIX_RAW`:包含当前的`raw`路径的URL前缀,可以被用于图片的前缀。 -如果 `DISABLE_SANITIZER` 为 false,则 Gitea 支持自定义渲染 HTML 的净化策略。以下例子将用 pandoc 支持 KaTeX 输出。 +如果 `RENDER_CONTENT_MODE` 为 `sanitized`,则 Gitea 支持自定义渲染 HTML 的净化策略。以下例子将用 pandoc 支持 KaTeX 输出。 ```ini [markup.sanitizer.TeX] diff --git a/docs/content/doc/advanced/signing.en-us.md b/docs/content/doc/advanced/signing.en-us.md index aaaeb7103..8ae2f94e9 100644 --- a/docs/content/doc/advanced/signing.en-us.md +++ b/docs/content/doc/advanced/signing.en-us.md @@ -83,8 +83,7 @@ The first option to discuss is the `SIGNING_KEY`. There are three main options: - `none` - this prevents Gitea from signing any commits -- `default` - Gitea will default to the key configured within - `git config` +- `default` - Gitea will default to the key configured within `git config` - `KEYID` - Gitea will sign commits with the gpg key with the ID `KEYID`. In this case you should provide a `SIGNING_NAME` and `SIGNING_EMAIL` to be displayed for this key. @@ -98,6 +97,12 @@ repositories, `SIGNING_KEY=default` could be used to provide different signing keys on a per-repository basis. However, this is clearly not an ideal UI and therefore subject to change. +**Since 1.17**, Gitea runs git in its own home directory `[repository].ROOT` and uses its own config `{[repository].ROOT}/.gitconfig`. +If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`) +or the Gitea internal git config `{[repository].ROOT}/.gitconfig`. +Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[repository].ROOT`. + + ### `INITIAL_COMMIT` This option determines whether Gitea should sign the initial commit @@ -118,7 +123,7 @@ The possible values are: - `never`: Never sign - `pubkey`: Only sign if the user has a public key -- `twofa`: Only sign if the user logs in with two factor authentication +- `twofa`: Only sign if the user logs in with two-factor authentication - `parentsigned`: Only sign if the parent commit is signed. - `always`: Always sign @@ -132,7 +137,7 @@ editor or API CRUD actions. The possible values are: - `never`: Never sign - `pubkey`: Only sign if the user has a public key -- `twofa`: Only sign if the user logs in with two factor authentication +- `twofa`: Only sign if the user logs in with two-factor authentication - `parentsigned`: Only sign if the parent commit is signed. - `always`: Always sign @@ -146,7 +151,7 @@ The possible options are: - `never`: Never sign - `pubkey`: Only sign if the user has a public key -- `twofa`: Only sign if the user logs in with two factor authentication +- `twofa`: Only sign if the user logs in with two-factor authentication - `basesigned`: Only sign if the parent commit in the base repo is signed. - `headsigned`: Only sign if the head commit in the head branch is signed. - `commitssigned`: Only sign if all the commits in the head branch to the merge point are signed. @@ -26,8 +26,10 @@ require ( github.com/editorconfig/editorconfig-core-go/v2 v2.4.4 github.com/emirpasic/gods v1.18.1 github.com/ethantkoenig/rupture v1.0.1 + github.com/felixge/fgprof v0.9.2 github.com/gliderlabs/ssh v0.3.4 - github.com/go-ap/activitypub v0.0.0-20220606130025-d23c21aea068 + github.com/go-ap/activitypub v0.0.0-20220615144428-48208c70483b + github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/cors v1.2.1 github.com/go-enry/go-enry/v2 v2.8.2 @@ -83,6 +85,7 @@ require ( github.com/tstranex/u2f v1.0.0 github.com/unrolled/render v1.4.1 github.com/urfave/cli v1.22.9 + github.com/valyala/fastjson v1.6.3 github.com/xanzy/go-gitlab v0.64.0 github.com/yohcop/openid-go v1.0.0 github.com/yuin/goldmark v1.4.12 @@ -161,8 +164,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fullstorydev/grpcurl v1.8.1 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/go-ap/errors v0.0.0-20220529131844-4c7dbeabb369 // indirect - github.com/go-ap/jsonld v0.0.0-20200327122108-fafac2de2660 // indirect + github.com/go-ap/errors v0.0.0-20220615144307-e8bc4a40ae9f // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect @@ -255,7 +257,6 @@ require ( github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/unknwon/com v1.0.1 // indirect - github.com/valyala/fastjson v1.6.3 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.1 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect @@ -428,6 +428,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/fgprof v0.9.2 h1:tAMHtWMyl6E0BimjVbFt7fieU6FpjttsZN7j0wT5blc= +github.com/felixge/fgprof v0.9.2/go.mod h1:+VNi+ZXtHIQ6wIw6bUT8nXQRefQflWECoFyRealT5sg= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -460,12 +462,12 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-ap/activitypub v0.0.0-20220606130025-d23c21aea068 h1:Xzxc/HjHFAwJTieEWcC1SjBHVBq+Y1xPgk7WWcG3sjs= -github.com/go-ap/activitypub v0.0.0-20220606130025-d23c21aea068/go.mod h1:h5U5YGW+z4mVF3XH6bKfBmsJinRVuKpS23uYqJfGSbo= -github.com/go-ap/errors v0.0.0-20220529131844-4c7dbeabb369 h1:z09Qf9XB5lU3NBZnrsN9VJqrIgwKLrsBVUgrir4zL9A= -github.com/go-ap/errors v0.0.0-20220529131844-4c7dbeabb369/go.mod h1:yENIaXEY5AiFL/qGTBBcPuk40I5SLyZzwrQj2ID5PHQ= -github.com/go-ap/jsonld v0.0.0-20200327122108-fafac2de2660 h1:AUG8+r0Q/zbNUAi5CWVBK5oUhOZDX3Kkr+oWURaJIfU= -github.com/go-ap/jsonld v0.0.0-20200327122108-fafac2de2660/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= +github.com/go-ap/activitypub v0.0.0-20220615144428-48208c70483b h1:+RjYfEfoZdM3wHFs752dlOpGaoRhwRRyQxjajg08LcQ= +github.com/go-ap/activitypub v0.0.0-20220615144428-48208c70483b/go.mod h1:DE3vvc6Didgfd3k7M1Mos6qMDFNmMrxJmYVMHG9h9Io= +github.com/go-ap/errors v0.0.0-20220615144307-e8bc4a40ae9f h1:kJhGo4NApJP0Lt9lkJnfmuTnRWVFbCynY0kiTxpPUR4= +github.com/go-ap/errors v0.0.0-20220615144307-e8bc4a40ae9f/go.mod h1:KHkKFKZvc05lr79+RGoq/zG8YjWi3+FK60Bxd+mpCew= +github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d h1:Z/oRXMlZHjvjIqDma1FrIGL3iE5YL7MUI0bwYEZ6qbA= +github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -777,6 +779,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3 h1:vFrXU7L2gqtlP/ZGijSpaDIc16ZQrZI4FAuYtpQTyQc= github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3/go.mod h1:Pt31oes+eGImORns3McJn8zHefuQl2rG8l6xQjGYB4U= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -900,6 +903,7 @@ github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73t github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= diff --git a/integrations/api_activitypub_person_test.go b/integrations/api_activitypub_person_test.go index 70f1134fa..4898d5e01 100644 --- a/integrations/api_activitypub_person_test.go +++ b/integrations/api_activitypub_person_test.go @@ -38,7 +38,7 @@ func TestActivityPubPerson(t *testing.T) { assert.NoError(t, err) assert.Equal(t, ap.PersonType, person.Type) - assert.Equal(t, username, person.Name.String()) + assert.Equal(t, username, person.PreferredUsername.String()) keyID := person.GetID().String() assert.Regexp(t, fmt.Sprintf("activitypub/user/%s$", username), keyID) assert.Regexp(t, fmt.Sprintf("activitypub/user/%s/outbox$", username), person.Outbox.GetID().String()) @@ -64,7 +64,7 @@ func TestActivityPubMissingPerson(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/activitypub/user/nonexistentuser") resp := MakeRequest(t, req, http.StatusNotFound) - assert.Contains(t, resp.Body.String(), "GetUserByName") + assert.Contains(t, resp.Body.String(), "user redirect does not exist") }) } diff --git a/integrations/integration_test.go b/integrations/integration_test.go index ce21eb2ef..b0004927f 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -174,7 +174,12 @@ func initIntegrationTest() { setting.LoadForTest() setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" _ = util.RemoveAll(repo_module.LocalCopyPath()) + + if err := git.InitOnceWithSync(context.Background()); err != nil { + log.Fatal("git.InitOnceWithSync: %v", err) + } git.CheckLFSVersion() + setting.InitDBConfig() if err := storage.Init(); err != nil { fmt.Printf("Init storage failed: %v", err) @@ -275,7 +280,7 @@ func prepareTestEnv(t testing.TB, skip ...int) func() { assert.NoError(t, unittest.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) + assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) @@ -576,7 +581,7 @@ func resetFixtures(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) + assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) diff --git a/integrations/links_test.go b/integrations/links_test.go index bc87ffad8..d0cf978f6 100644 --- a/integrations/links_test.go +++ b/integrations/links_test.go @@ -22,11 +22,11 @@ func TestLinksNoLogin(t *testing.T) { links := []string{ "/explore/repos", - "/explore/repos?q=test&tab=", + "/explore/repos?q=test", "/explore/users", - "/explore/users?q=test&tab=", + "/explore/users?q=test", "/explore/organizations", - "/explore/organizations?q=test&tab=", + "/explore/organizations?q=test", "/", "/user/sign_up", "/user/login", @@ -81,11 +81,11 @@ func TestNoLoginNotExist(t *testing.T) { func testLinksAsUser(userName string, t *testing.T) { links := []string{ "/explore/repos", - "/explore/repos?q=test&tab=", + "/explore/repos?q=test", "/explore/users", - "/explore/users?q=test&tab=", + "/explore/users?q=test", "/explore/organizations", - "/explore/organizations?q=test&tab=", + "/explore/organizations?q=test", "/", "/user/forgot_password", "/api/swagger", diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index 83c31d801..20a5c903a 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -62,7 +62,6 @@ func initMigrationTest(t *testing.T) func() { assert.True(t, len(setting.RepoRootPath) != 0) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) @@ -83,6 +82,7 @@ func initMigrationTest(t *testing.T) func() { } } + assert.NoError(t, git.InitOnceWithSync(context.Background())) git.CheckLFSVersion() setting.InitDBConfig() setting.NewLogServices(true) diff --git a/models/action.go b/models/action.go index 951328070..78cc93e1a 100644 --- a/models/action.go +++ b/models/action.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm/schemas" ) // ActionType represents the type of an action. @@ -70,25 +71,36 @@ const ( // used in template render. type Action struct { ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX"` // Receiver user id. + UserID int64 // Receiver user id. OpType ActionType - ActUserID int64 `xorm:"INDEX"` // Action user id. - ActUser *user_model.User `xorm:"-"` - RepoID int64 `xorm:"INDEX"` + ActUserID int64 // Action user id. + ActUser *user_model.User `xorm:"-"` + RepoID int64 Repo *repo_model.Repository `xorm:"-"` CommentID int64 `xorm:"INDEX"` Comment *issues_model.Comment `xorm:"-"` - IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsDeleted bool `xorm:"NOT NULL DEFAULT false"` RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsPrivate bool `xorm:"NOT NULL DEFAULT false"` Content string `xorm:"TEXT"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } func init() { db.RegisterModel(new(Action)) } +// TableIndices implements xorm's TableIndices interface +func (a *Action) TableIndices() []*schemas.Index { + actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) + actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") + + repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType) + repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted") + + return []*schemas.Index{actUserIndex, repoIndex} +} + // GetOpType gets the ActionType of this action. func (a *Action) GetOpType() ActionType { return a.OpType diff --git a/models/auth/source.go b/models/auth/source.go index 48af87acd..c152644aa 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -22,15 +22,15 @@ type Type int // Note: new type must append to the end of list to maintain compatibility. const ( - NoType Type = iota - Plain // 1 - LDAP // 2 - SMTP // 3 - PAM // 4 - DLDAP // 5 - OAuth2 // 6 - SSPI // 7 - Federated // 8 + NoType Type = iota + Plain // 1 + LDAP // 2 + SMTP // 3 + PAM // 4 + DLDAP // 5 + OAuth2 // 6 + SSPI // 7 + Federated // 8 ) // String returns the string name of the LoginType diff --git a/models/forgefed/repository.go b/models/forgefed/repository.go new file mode 100644 index 000000000..8c8ae1d31 --- /dev/null +++ b/models/forgefed/repository.go @@ -0,0 +1,71 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package forgefed + +import ( + ap "github.com/go-ap/activitypub" + "github.com/valyala/fastjson" +) + +const ( + RepositoryType ap.ActivityVocabularyType = "Repository" +) + +type Repository struct { + ap.Actor + // Team Collection of actors who have management/push access to the repository + Team ap.Item `jsonld:"team,omitempty"` + // Forks OrderedCollection of repositories that are forks of this repository + Forks ap.Item `jsonld:"forks,omitempty"` +} + +// GetItemByType instantiates a new Repository object if the type matches +// otherwise it defaults to existing activitypub package typer function. +func GetItemByType(typ ap.ActivityVocabularyType) (ap.Item, error) { + if typ == RepositoryType { + return RepositoryNew(""), nil + } + return ap.GetItemByType(typ) +} + +// RepositoryNew initializes a Repository type actor +func RepositoryNew(id ap.ID) *Repository { + a := ap.ActorNew(id, RepositoryType) + o := Repository{Actor: *a} + o.Type = RepositoryType + return &o +} + +func (r Repository) MarshalJSON() ([]byte, error) { + b, err := r.Actor.MarshalJSON() + if len(b) == 0 || err != nil { + return make([]byte, 0), err + } + + b = b[:len(b)-1] + if r.Team != nil { + ap.WriteItemJSONProp(&b, "team", r.Team) + } + if r.Forks != nil { + ap.WriteItemJSONProp(&b, "forks", r.Forks) + } + ap.Write(&b, '}') + return b, nil +} + +func (r *Repository) UnmarshalJSON(data []byte) error { + p := fastjson.Parser{} + val, err := p.ParseBytes(data) + if err != nil { + return err + } + + r.Team = ap.JSONGetItem(val, "team") + r.Forks = ap.JSONGetItem(val, "forks") + + return ap.OnActor(&r.Actor, func(a *ap.Actor) error { + return ap.LoadActor(val, a) + }) +} diff --git a/models/forgefed/repository_test.go b/models/forgefed/repository_test.go new file mode 100644 index 000000000..4bf36a66c --- /dev/null +++ b/models/forgefed/repository_test.go @@ -0,0 +1,167 @@ +package forgefed + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + ap "github.com/go-ap/activitypub" +) + +func Test_GetItemByType(t *testing.T) { + type testtt struct { + typ ap.ActivityVocabularyType + want ap.Item + wantErr error + } + tests := map[string]testtt{ + "invalid type": { + typ: ap.ActivityVocabularyType("invalidtype"), + wantErr: fmt.Errorf("empty ActivityStreams type"), // TODO(marius): this error message needs to be improved in go-ap/activitypub + }, + "Repository": { + typ: RepositoryType, + want: new(Repository), + }, + "Person - fall back": { + typ: ap.PersonType, + want: new(ap.Person), + }, + "Question - fall back": { + typ: ap.QuestionType, + want: new(ap.Question), + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + maybeRepository, err := GetItemByType(tt.typ) + if !reflect.DeepEqual(tt.wantErr, err) { + t.Errorf("GetItemByType() error = \"%+v\", wantErr = \"%+v\" when getting Item for type %q", tt.wantErr, err, tt.typ) + } + if reflect.TypeOf(tt.want) != reflect.TypeOf(maybeRepository) { + t.Errorf("Invalid type received %T, expected %T", maybeRepository, tt.want) + } + }) + } +} + +func Test_RepositoryMarshalJSON(t *testing.T) { + type testPair struct { + item Repository + want []byte + wantErr error + } + + tests := map[string]testPair{ + "empty": { + item: Repository{}, + want: nil, + }, + "with ID": { + item: Repository{ + Actor: ap.Actor{ + ID: "https://example.com/1", + }, + Team: nil, + }, + want: []byte(`{"id":"https://example.com/1"}`), + }, + "with Team as IRI": { + item: Repository{ + Team: ap.IRI("https://example.com/1"), + }, + want: []byte(`{"team":"https://example.com/1"}`), + }, + "with Team as IRIs": { + item: Repository{ + Team: ap.ItemCollection{ + ap.IRI("https://example.com/1"), + ap.IRI("https://example.com/2"), + }, + }, + want: []byte(`{"team":["https://example.com/1","https://example.com/2"]}`), + }, + "with Team as Object": { + item: Repository{ + Team: ap.Object{ID: "https://example.com/1"}, + }, + want: []byte(`{"team":{"id":"https://example.com/1"}}`), + }, + "with Team as slice of Objects": { + item: Repository{ + Team: ap.ItemCollection{ + ap.Object{ID: "https://example.com/1"}, + ap.Object{ID: "https://example.com/2"}, + }, + }, + want: []byte(`{"team":[{"id":"https://example.com/1"},{"id":"https://example.com/2"}]}`), + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got, err := tt.item.MarshalJSON() + if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() { + t.Errorf("MarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %q, want %q", got, tt.want) + } + }) + } +} + +func Test_RepositoryUnmarshalJSON(t *testing.T) { + type testPair struct { + data []byte + want *Repository + wantErr error + } + + tests := map[string]testPair{ + "nil": { + data: nil, + wantErr: fmt.Errorf("cannot parse JSON: %w", fmt.Errorf("cannot parse empty string; unparsed tail: %q", "")), + }, + "empty": { + data: []byte{}, + wantErr: fmt.Errorf("cannot parse JSON: %w", fmt.Errorf("cannot parse empty string; unparsed tail: %q", "")), + }, + "with Type": { + data: []byte(`{"type":"Repository"}`), + want: &Repository{ + Actor: ap.Actor{ + Type: RepositoryType, + }, + }, + }, + "with Type and ID": { + data: []byte(`{"id":"https://example.com/1","type":"Repository"}`), + want: &Repository{ + Actor: ap.Actor{ + ID: "https://example.com/1", + Type: RepositoryType, + }, + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got := new(Repository) + err := got.UnmarshalJSON(tt.data) + if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() { + t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr) + return + } + if tt.want != nil && !reflect.DeepEqual(got, tt.want) { + jGot, _ := json.Marshal(got) + jWant, _ := json.Marshal(tt.want) + t.Errorf("UnmarshalJSON() got = %s, want %s", jGot, jWant) + } + }) + } +} diff --git a/models/git/lfs.go b/models/git/lfs.go index 13b8b234f..ec963cf59 100644 --- a/models/git/lfs.go +++ b/models/git/lfs.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -213,7 +214,7 @@ func LFSObjectAccessible(user *user_model.User, oid string) (bool, error) { count, err := db.GetEngine(db.DefaultContext).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } - cond := repo_model.AccessibleRepositoryCondition(user) + cond := repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid) count, err := db.GetEngine(db.DefaultContext).Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } @@ -244,7 +245,7 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6 newMetas := make([]*LFSMetaObject, 0, len(metas)) cond := builder.In( "`lfs_meta_object`.repository_id", - builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user)), + builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), ) err = sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas) if err != nil { diff --git a/models/issues/issue.go b/models/issues/issue.go index 0f4af3e84..76a0ea7d0 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1430,7 +1430,7 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati cond = cond.And( builder.Or( repo_model.UserOwnedRepoCond(userID), // owned repos - repo_model.UserCollaborationRepoCond(repoIDstr, userID), // collaboration repos + repo_model.UserAccessRepoCond(repoIDstr, userID), // user can access repo in a unit independent way repo_model.UserAssignedRepoCond(repoIDstr, userID), // user has been assigned accessible public repos repo_model.UserMentionedRepoCond(repoIDstr, userID), // user has been mentioned accessible public repos repo_model.UserCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos @@ -1499,7 +1499,7 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i opts.setupSessionNoLimit(sess) - accessCond := repo_model.AccessibleRepositoryCondition(user) + accessCond := repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid) if err := sess.Where(accessCond). Distinct("issue.repo_id"). Table("issue"). diff --git a/models/issues/label.go b/models/issues/label.go index 98e2e4396..9ad488252 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -733,7 +733,7 @@ func DeleteLabelsByRepoID(ctx context.Context, repoID int64) error { // CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore func CountOrphanedLabels() (int64, error) { - noref, err := db.GetEngine(db.DefaultContext).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id") + noref, err := db.GetEngine(db.DefaultContext).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count() if err != nil { return 0, err } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 1bed4e2bc..c843e33eb 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -387,6 +387,8 @@ var migrations = []Migration{ NewMigration("Add auto merge table", addAutoMergeTable), // v215 -> v216 NewMigration("allow to view files in PRs", addReviewViewedFiles), + // v216 -> v217 + NewMigration("Improve Action table indices", improveActionTableIndices), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go index 0c8e74f73..46782f24a 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -66,6 +66,10 @@ func TestMain(m *testing.M) { setting.SetCustomPathAndConf("", "", "") setting.LoadForTest() + if err = git.InitOnceWithSync(context.Background()); err != nil { + fmt.Printf("Unable to InitOnceWithSync: %v\n", err) + os.Exit(1) + } git.CheckLFSVersion() setting.InitDBConfig() setting.NewLogServices(true) @@ -203,7 +207,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En deferFn := PrintCurrentTest(t, ourSkip) assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) + assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) diff --git a/models/migrations/v216.go b/models/migrations/v216.go new file mode 100644 index 000000000..b011c11d9 --- /dev/null +++ b/models/migrations/v216.go @@ -0,0 +1,67 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type improveActionTableIndicesAction struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 // Receiver user id. + OpType int + ActUserID int64 // Action user id. + RepoID int64 + CommentID int64 `xorm:"INDEX"` + IsDeleted bool `xorm:"NOT NULL DEFAULT false"` + RefName string + IsPrivate bool `xorm:"NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +// TableName sets the name of this table +func (a *improveActionTableIndicesAction) TableName() string { + return "action" +} + +// TableIndices implements xorm's TableIndices interface +func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index { + actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) + actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") + + repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType) + repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted") + + return []*schemas.Index{actUserIndex, repoIndex} +} + +func improveActionTableIndices(x *xorm.Engine) error { + { + type Action struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX"` // Receiver user id. + OpType int + ActUserID int64 `xorm:"INDEX"` // Action user id. + RepoID int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` + IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` + RefName string + IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + } + if err := x.Sync2(&Action{}); err != nil { + return err + } + if err := x.DropIndexes(&Action{}); err != nil { + return err + } + } + return x.Sync2(&improveActionTableIndicesAction{}) +} diff --git a/models/org.go b/models/org.go index 009fe758b..849c9b985 100644 --- a/models/org.go +++ b/models/org.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/organization" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "xorm.io/builder" @@ -54,7 +55,7 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { Join("LEFT", builder. Select("id as repo_id, owner_id as repo_owner_id"). From("repository"). - Where(repo_model.AccessibleRepositoryCondition(user)), "`repository`.repo_owner_id = `team`.org_id"). + Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id"). Where("`team_user`.uid = ?", user.ID). GroupBy(groupByStr) diff --git a/models/project/issue.go b/models/project/issue.go index 04efc0e74..6e6a8c574 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -9,6 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" ) // ProjectIssue saves relation from issue to a project @@ -41,6 +42,7 @@ func (p *Project) NumIssues() int { Cols("issue_id"). Count() if err != nil { + log.Error("NumIssues: %v", err) return 0 } return int(c) @@ -54,6 +56,7 @@ func (p *Project) NumClosedIssues() int { Cols("issue_id"). Count() if err != nil { + log.Error("NumClosedIssues: %v", err) return 0 } return int(c) @@ -63,8 +66,11 @@ func (p *Project) NumClosedIssues() int { func (p *Project) NumOpenIssues() int { c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). - Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).Count("issue.id") + Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). + Cols("issue_id"). + Count() if err != nil { + log.Error("NumOpenIssues: %v", err) return 0 } return int(c) diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 1bec35d57..a70fc8efd 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -269,8 +269,8 @@ func UserMentionedRepoCond(id string, userID int64) builder.Cond { ) } -// UserCollaborationRepoCond returns user as collabrators repositories list -func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { +// UserAccessRepoCond returns a condition for selecting all repositories a user has unit independent access to +func UserAccessRepoCond(idStr string, userID int64) builder.Cond { return builder.In(idStr, builder.Select("repo_id"). From("`access`"). Where(builder.And( @@ -280,8 +280,18 @@ func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { ) } -// userOrgTeamRepoCond selects repos that the given user has access to through team membership -func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { +// userCollaborationRepoCond returns a condition for selecting all repositories a user is collaborator in +func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { + return builder.In(idStr, builder.Select("repo_id"). + From("`collaboration`"). + Where(builder.And( + builder.Eq{"`collaboration`.user_id": userID}, + )), + ) +} + +// UserOrgTeamRepoCond selects repos that the given user has access to through team membership +func UserOrgTeamRepoCond(idStr string, userID int64) builder.Cond { return builder.In(idStr, userOrgTeamRepoBuilder(userID)) } @@ -297,7 +307,13 @@ func userOrgTeamRepoBuilder(userID int64) *builder.Builder { func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { return userOrgTeamRepoBuilder(userID). Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). - Where(builder.Eq{"`team_unit`.`type`": unitType}) + Where(builder.Eq{"`team_unit`.`type`": unitType}). + And(builder.Gt{"`team_unit`.`access_mode`": int(perm.AccessModeNone)}) +} + +// userOrgTeamUnitRepoCond returns a condition to select repo ids where user's teams can access the special unit. +func userOrgTeamUnitRepoCond(idStr string, userID int64, unitType unit.Type) builder.Cond { + return builder.In(idStr, userOrgTeamUnitRepoBuilder(userID, unitType)) } // UserOrgUnitRepoCond selects repos that the given user has access to through org and the special unit @@ -350,7 +366,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { if opts.Private { if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { // OK we're in the context of a User - cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) + cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid)) } } else { // Not looking at private organisations and users @@ -395,10 +411,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { builder.Neq{"owner_id": opts.OwnerID}, // 2. But we can see because of: builder.Or( - // A. We have access - UserCollaborationRepoCond("`repository`.id", opts.OwnerID), + // A. We have unit independent access + UserAccessRepoCond("`repository`.id", opts.OwnerID), // B. We are in a team for - userOrgTeamRepoCond("`repository`.id", opts.OwnerID), + UserOrgTeamRepoCond("`repository`.id", opts.OwnerID), // C. Public repositories in organizations that we are member of userOrgPublicRepoCondPrivate(opts.OwnerID), ), @@ -479,7 +495,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { } if opts.Actor != nil && opts.Actor.IsRestricted { - cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) + cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid)) } if opts.Archived != util.OptionalBoolNone { @@ -574,7 +590,7 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c } // AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible -func AccessibleRepositoryCondition(user *user_model.User) builder.Cond { +func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond { cond := builder.NewCond() if user == nil || !user.IsRestricted || user.ID <= 0 { @@ -594,13 +610,24 @@ func AccessibleRepositoryCondition(user *user_model.User) builder.Cond { } if user != nil { + // 2. Be able to see all repositories that we have unit independent access to + // 3. Be able to see all repositories through team membership(s) + if unitType == unit.TypeInvalid { + // Regardless of UnitType + cond = cond.Or( + UserAccessRepoCond("`repository`.id", user.ID), + UserOrgTeamRepoCond("`repository`.id", user.ID), + ) + } else { + // For a specific UnitType + cond = cond.Or( + UserCollaborationRepoCond("`repository`.id", user.ID), + userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType), + ) + } cond = cond.Or( - // 2. Be able to see all repositories that we have access to - UserCollaborationRepoCond("`repository`.id", user.ID), - // 3. Repositories that we directly own + // 4. Repositories that we directly own builder.Eq{"`repository`.owner_id": user.ID}, - // 4. Be able to see all repositories that we are in a team - userOrgTeamRepoCond("`repository`.id", user.ID), // 5. Be able to see all public repos in private organizations that we are an org_user of userOrgPublicRepoCond(user.ID), ) @@ -645,18 +672,18 @@ func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { // AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { // NB: Please note this code needs to still work if user is nil - return builder.Select("id").From("repository").Where(AccessibleRepositoryCondition(user)) + return builder.Select("id").From("repository").Where(AccessibleRepositoryCondition(user, unit.TypeInvalid)) } -// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id -func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { +// FindUserCodeAccessibleRepoIDs finds all at Code level accessible repositories' ID by the user's id +func FindUserCodeAccessibleRepoIDs(user *user_model.User) ([]int64, error) { repoIDs := make([]int64, 0, 10) if err := db.GetEngine(db.DefaultContext). Table("repository"). Cols("id"). - Where(AccessibleRepositoryCondition(user)). + Where(AccessibleRepositoryCondition(user, unit.TypeCode)). Find(&repoIDs); err != nil { - return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) + return nil, fmt.Errorf("FindUserCodeAccesibleRepoIDs: %v", err) } return repoIDs, nil } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index d1a449851..baea46dbc 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -117,9 +117,11 @@ func MainTest(m *testing.M, testOpts *TestOptions) { if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { fatalTestError("util.CopyDir: %v\n", err) } + if err = git.InitOnceWithSync(context.Background()); err != nil { fatalTestError("git.Init: %v\n", err) } + git.CheckLFSVersion() ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { @@ -202,7 +204,7 @@ func PrepareTestEnv(t testing.TB) { assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta") assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) + assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) assert.NoError(t, err) diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go index 109b5dd91..d48ac9305 100644 --- a/models/user/setting_keys.go +++ b/models/user/setting_keys.go @@ -9,4 +9,8 @@ const ( SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types" // SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour" + // UserActivityPubPrivPem is user's private key + UserActivityPubPrivPem = "activitypub.priv_pem" + // UserActivityPubPubPem is user's public key + UserActivityPubPubPem = "activitypub.pub_pem" ) diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go index b04706dd0..738b1e473 100644 --- a/modules/activitypub/client.go +++ b/modules/activitypub/client.go @@ -27,6 +27,12 @@ const ( httpsigExpirationTime = 60 ) +// Gets the current time as an RFC 2616 formatted string +// RFC 2616 requires RFC 1123 dates but with GMT instead of UTC +func CurrentTime() string { + return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT") +} + func containsRequiredHTTPHeaders(method string, headers []string) error { var hasRequestTarget, hasDate, hasDigest bool for _, header := range headers { @@ -97,9 +103,8 @@ func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) return } req.Header.Add("Content-Type", ActivityStreamsContentType) - req.Header.Add("Accept-Charset", "utf-8") - req.Header.Add("Date", strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT")) - + req.Header.Add("Date", CurrentTime()) + req.Header.Add("User-Agent", "Gitea/"+setting.AppVer) signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime) if err != nil { return diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go index 289bc9f32..7fa2b0926 100644 --- a/modules/activitypub/main_test.go +++ b/modules/activitypub/main_test.go @@ -12,6 +12,7 @@ import ( ) func TestMain(m *testing.M) { - testOps := unittest.TestOptions{GiteaRootPath: filepath.Join("..", "..")} - unittest.MainTest(m, &testOps) + unittest.MainTest(m, &unittest.TestOptions{ + GiteaRootPath: filepath.Join("..", ".."), + }) } diff --git a/modules/activitypub/send.go b/modules/activitypub/send.go index cf65660e2..96b64d205 100644 --- a/modules/activitypub/send.go +++ b/modules/activitypub/send.go @@ -9,8 +9,6 @@ import ( "io" "net/http" "net/url" - "strings" - "time" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/httplib" @@ -24,8 +22,7 @@ import ( func Fetch(iri *url.URL) (b []byte, err error) { req := httplib.NewRequest(iri.String(), http.MethodGet) req.Header("Accept", ActivityStreamsContentType) - req.Header("Accept-Charset", "utf-8") - req.Header("Date", strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT")) + req.Header("User-Agent", "Gitea/"+setting.AppVer) resp, err := req.Response() if err != nil { return @@ -36,7 +33,7 @@ func Fetch(iri *url.URL) (b []byte, err error) { err = fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status) return } - b, err = io.ReadAll(resp.Body) + b, err = io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize)) return } @@ -56,7 +53,7 @@ func Send(user *user_model.User, activity *ap.Activity) { for _, to := range activity.To { client, _ := NewClient(user, setting.AppURL+"api/v1/activitypub/user/"+user.Name+"#main-key") resp, _ := client.Post(body, to.GetID().String()) - respBody, _ := io.ReadAll(resp.Body) + respBody, _ := io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize)) log.Debug(string(respBody)) } } diff --git a/modules/activitypub/user_settings.go b/modules/activitypub/user_settings.go index 3289d45c5..2144e7b47 100644 --- a/modules/activitypub/user_settings.go +++ b/modules/activitypub/user_settings.go @@ -8,41 +8,37 @@ import ( user_model "code.gitea.io/gitea/models/user" ) -const ( - userActivitypubPrivpem = "activitypub_privpem" - userActivitypubPubpem = "activitypub_pubpem" -) - -// GetKeyPair function +// GetKeyPair function returns a user's private and public keys func GetKeyPair(user *user_model.User) (pub, priv string, err error) { var settings map[string]*user_model.Setting - if settings, err = user_model.GetUserSettings(user.ID, []string{userActivitypubPrivpem, userActivitypubPubpem}); err != nil { + settings, err = user_model.GetUserSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) + if err != nil { return } else if len(settings) == 0 { if priv, pub, err = GenerateKeyPair(); err != nil { return } - if err = user_model.SetUserSetting(user.ID, userActivitypubPrivpem, priv); err != nil { + if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, priv); err != nil { return } - if err = user_model.SetUserSetting(user.ID, userActivitypubPubpem, pub); err != nil { + if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, pub); err != nil { return } return } else { - priv = settings[userActivitypubPrivpem].SettingValue - pub = settings[userActivitypubPubpem].SettingValue + priv = settings[user_model.UserActivityPubPrivPem].SettingValue + pub = settings[user_model.UserActivityPubPubPem].SettingValue return } } -// GetPublicKey function +// GetPublicKey function returns a user's public key func GetPublicKey(user *user_model.User) (pub string, err error) { pub, _, err = GetKeyPair(user) return } -// GetPrivateKey function +// GetPrivateKey function returns a user's private key func GetPrivateKey(user *user_model.User) (priv string, err error) { _, priv, err = GetKeyPair(user) return diff --git a/modules/context/pagination.go b/modules/context/pagination.go index c0079c295..617b472f0 100644 --- a/modules/context/pagination.go +++ b/modules/context/pagination.go @@ -52,7 +52,6 @@ func (p *Pagination) GetParams() template.URL { func (p *Pagination) SetDefaultParams(ctx *Context) { p.AddParam(ctx, "sort", "SortType") p.AddParam(ctx, "q", "Keyword") - p.AddParam(ctx, "tab", "TabName") // do not add any more uncommon params here! p.AddParam(ctx, "t", "queryType") } diff --git a/modules/csv/csv.go b/modules/csv/csv.go index 0dd54271f..fe0c35096 100644 --- a/modules/csv/csv.go +++ b/modules/csv/csv.go @@ -54,7 +54,7 @@ func CreateReaderAndDetermineDelimiter(ctx *markup.RenderContext, rd io.Reader) func determineDelimiter(ctx *markup.RenderContext, data []byte) rune { extension := ".csv" if ctx != nil { - extension = strings.ToLower(filepath.Ext(ctx.Filename)) + extension = strings.ToLower(filepath.Ext(ctx.RelativePath)) } var delimiter rune diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index b1e928ae9..9d0848ae5 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -230,7 +230,7 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`, } for n, c := range cases { - delimiter := determineDelimiter(&markup.RenderContext{Filename: c.filename}, []byte(decodeSlashes(t, c.csv))) + delimiter := determineDelimiter(&markup.RenderContext{RelativePath: c.filename}, []byte(decodeSlashes(t, c.csv))) assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) } } diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index b23807cca..349a2121c 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -199,6 +199,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find OAuth2AuthorizationCode without existing OAuth2Grant genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant", "oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"), + // find stopwatches without existing user + genericOrphanCheck("Orphaned Stopwatches without existing User", + "stopwatch", "user", "stopwatch.user_id=user.id"), + // find stopwatches without existing issue + genericOrphanCheck("Orphaned Stopwatches without existing Issue", + "stopwatch", "issue", "stopwatch.issue_id=issue.id"), ) for _, c := range consistencyChecks { diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 6055cf723..06d48454f 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -94,7 +94,9 @@ loop: for _, userStopwatches := range usersStopwatches { apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) if err != nil { - log.Error("Unable to APIFormat stopwatches: %v", err) + if !issues_model.IsErrIssueNotExist(err) { + log.Error("Unable to APIFormat stopwatches: %v", err) + } continue } dataBs, err := json.Marshal(apiSWs) diff --git a/modules/git/commit.go b/modules/git/commit.go index 99fbbd0cf..82b5e0b25 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -206,26 +206,17 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { return false, nil } - if err := CheckGitVersionAtLeast("1.8"); err == nil { - _, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path}) - if err == nil { - return true, nil + _, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path}) + if err == nil { + return true, nil + } + var exitError *exec.ExitError + if errors.As(err, &exitError) { + if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 { + return false, nil } - var exitError *exec.ExitError - if errors.As(err, &exitError) { - if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 { - return false, nil - } - } - return false, err - } - - result, _, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunStdString(&RunOpts{Dir: c.repo.Path}) - if err != nil { - return false, err } - - return len(strings.TrimSpace(result)) > 0, nil + return false, err } // CommitsBeforeLimit returns num commits before current revision diff --git a/modules/git/git.go b/modules/git/git.go index 43901b3c1..3a3663995 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -7,35 +7,31 @@ package git import ( "context" + "errors" "fmt" "os" "os/exec" - "path/filepath" "runtime" "strings" "sync" "time" - //"code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "github.com/hashicorp/go-version" ) -var ( - // GitVersionRequired is the minimum Git version required - // At the moment, all code for git 1.x are not changed, if some users want to test with old git client - // or bypass the check, they still have a chance to edit this variable manually. - // If everything works fine, the code for git 1.x could be removed in a separate PR before 1.17 frozen. - GitVersionRequired = "2.0.0" +// GitVersionRequired is the minimum Git version required +const GitVersionRequired = "2.0.0" +var ( // GitExecutable is the command name of git // Could be updated to an absolute path while initialization GitExecutable = "git" - // DefaultContext is the default context to run git commands in - // will be overwritten by InitXxx with HammerContext - DefaultContext = context.Background() + // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx + DefaultContext context.Context // SupportProcReceive version >= 2.29.0 SupportProcReceive bool @@ -128,36 +124,43 @@ func VersionInfo() string { return fmt.Sprintf(format, args...) } +func checkInit() error { + if setting.RepoRootPath == "" { + return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + } + if DefaultContext != nil { + log.Warn("git module has been initialized already, duplicate init should be fixed") + } + return nil +} + // HomeDir is the home dir for git to store the global config file used by Gitea internally func HomeDir() string { if setting.RepoRootPath == "" { - // TODO: now, some unit test code call the git module directly without initialization, which is incorrect. - // at the moment, we just use a temp HomeDir to prevent from conflicting with user's git config - // in the future, the git module should be initialized first before use. - tmpHomeDir := filepath.Join(os.TempDir(), "gitea-temp-home") - //log.Error("Git's HomeDir is empty (RepoRootPath is empty), the git module is not initialized correctly, using a temp HomeDir (%s) temporarily", tmpHomeDir) - return tmpHomeDir + // strict check, make sure the git module is initialized correctly. + // attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users. + // for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons. + log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + return "" } return setting.RepoRootPath } // InitSimple initializes git module with a very simple step, no config changes, no global command arguments. // This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race +// However, in integration test, the sub-command function may be called in the current process, so the InitSimple would be called multiple times, too func InitSimple(ctx context.Context) error { + if err := checkInit(); err != nil { + return err + } + DefaultContext = ctx if setting.Git.Timeout.Default > 0 { defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second } - if err := SetExecutablePath(setting.Git.Path); err != nil { - return err - } - - // force cleanup args - globalCommandArgs = []string{} - - return nil + return SetExecutablePath(setting.Git.Path) } var initOnce sync.Once @@ -166,6 +169,10 @@ var initOnce sync.Once // This method will update the global variables ONLY ONCE (just like git.CheckLFSVersion -- which is not ideal too), // otherwise there will be data-race problem at the moment. func InitOnceWithSync(ctx context.Context) (err error) { + if err = checkInit(); err != nil { + return err + } + initOnce.Do(func() { err = InitSimple(ctx) if err != nil { @@ -231,6 +238,9 @@ func syncGitConfig() (err error) { if err := configSet("gc.writeCommitGraph", "true"); err != nil { return err } + if err := configSet("fetch.writeCommitGraph", "true"); err != nil { + return err + } } if SupportProcReceive { @@ -244,6 +254,19 @@ func syncGitConfig() (err error) { } } + // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user + // however, some docker users and samba users find it difficult to configure their systems so that Gitea's git repositories are owned by the Gitea user. (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.) + // see issue: https://github.com/go-gitea/gitea/issues/19455 + // Fundamentally the problem lies with the uid-gid-mapping mechanism for filesystems in docker on windows (and to a lesser extent samba). + // Docker's configuration mechanism for local filesystems provides no way of setting this mapping and although there is a mechanism for setting this uid through using cifs mounting it is complicated and essentially undocumented + // Thus the owner uid/gid for files on these filesystems will be marked as root. + // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea, + // it is now safe to set "safe.directory=*" for internal usage only. + // Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later + // Although only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - this setting is tolerated by earlier versions + if err := configAddNonExist("safe.directory", "*"); err != nil { + return err + } if runtime.GOOS == "windows" { if err := configSet("core.longpaths", "true"); err != nil { return err diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 38818788f..596a91e80 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -30,10 +30,10 @@ type CheckAttributeOpts struct { func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) { env := []string{} - if len(opts.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { + if len(opts.IndexFile) > 0 { env = append(env, "GIT_INDEX_FILE="+opts.IndexFile) } - if len(opts.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { + if len(opts.WorkTree) > 0 { env = append(env, "GIT_WORK_TREE="+opts.WorkTree) } @@ -56,8 +56,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ } } - // git check-attr --cached first appears in git 1.7.8 - if opts.CachedOnly && CheckGitVersionAtLeast("1.7.8") == nil { + if opts.CachedOnly { cmdArgs = append(cmdArgs, "--cached") } @@ -125,12 +124,12 @@ type CheckAttributeReader struct { func (c *CheckAttributeReader) Init(ctx context.Context) error { cmdArgs := []string{"check-attr", "--stdin", "-z"} - if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { + if len(c.IndexFile) > 0 { cmdArgs = append(cmdArgs, "--cached") c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) } - if len(c.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { + if len(c.WorkTree) > 0 { c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) } @@ -160,17 +159,10 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { return err } - if CheckGitVersionAtLeast("1.8.5") == nil { - lw := new(nulSeparatedAttributeWriter) - lw.attributes = make(chan attributeTriple, 5) - lw.closed = make(chan struct{}) - c.stdOut = lw - } else { - lw := new(lineSeparatedAttributeWriter) - lw.attributes = make(chan attributeTriple, 5) - lw.closed = make(chan struct{}) - c.stdOut = lw - } + lw := new(nulSeparatedAttributeWriter) + lw.attributes = make(chan attributeTriple, 5) + lw.closed = make(chan struct{}) + c.stdOut = lw return nil } @@ -400,3 +392,37 @@ func (wr *lineSeparatedAttributeWriter) Close() error { close(wr.closed) return nil } + +// Create a check attribute reader for the current repository and provided commit ID +func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) { + indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) + if err != nil { + return nil, func() {} + } + + checker := &CheckAttributeReader{ + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, + Repo: repo, + IndexFile: indexFilename, + WorkTree: worktree, + } + ctx, cancel := context.WithCancel(repo.Ctx) + if err := checker.Init(ctx); err != nil { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + } else { + go func() { + err := checker.Run() + if err != nil && err != ctx.Err() { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + } + cancel() + }() + } + deferable := func() { + _ = checker.Close() + cancel() + deleteTemporaryFile() + } + + return checker, deferable +} diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 4b0cc8536..3c7af7300 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -255,13 +255,7 @@ func (repo *Repository) GetDiff(base, head string, w io.Writer) error { // GetDiffBinary generates and returns patch data between given revisions, including binary diffs. func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { - if CheckGitVersionAtLeast("1.7.7") == nil { - return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{ - Dir: repo.Path, - Stdout: w, - }) - } - return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).Run(&RunOpts{ + return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{ Dir: repo.Path, Stdout: w, }) diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go index 3c9f026b7..34b0dc45d 100644 --- a/modules/git/repo_language_stats_gogit.go +++ b/modules/git/repo_language_stats_gogit.go @@ -8,12 +8,10 @@ package git import ( "bytes" - "context" "io" "strings" "code.gitea.io/gitea/modules/analyze" - "code.gitea.io/gitea/modules/log" "github.com/go-enry/go-enry/v2" "github.com/go-git/go-git/v5" @@ -43,33 +41,8 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } - var checker *CheckAttributeReader - - if CheckGitVersionAtLeast("1.7.8") == nil { - indexFilename, workTree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) - if err == nil { - defer deleteTemporaryFile() - checker = &CheckAttributeReader{ - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, - Repo: repo, - IndexFile: indexFilename, - WorkTree: workTree, - } - ctx, cancel := context.WithCancel(DefaultContext) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - } else { - go func() { - err = checker.Run() - if err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - cancel() - } - }() - } - defer cancel() - } - } + checker, deferable := repo.CheckAttributeReader(commitID) + defer deferable() sizes := make(map[string]int64) err = tree.Files().ForEach(func(f *object.File) error { diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index 41b176f81..d237924f9 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -9,7 +9,6 @@ package git import ( "bufio" "bytes" - "context" "io" "math" "strings" @@ -63,36 +62,8 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } - var checker *CheckAttributeReader - - if CheckGitVersionAtLeast("1.7.8") == nil { - indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) - if err == nil { - defer deleteTemporaryFile() - checker = &CheckAttributeReader{ - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, - Repo: repo, - IndexFile: indexFilename, - WorkTree: worktree, - } - ctx, cancel := context.WithCancel(repo.Ctx) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - } else { - go func() { - err = checker.Run() - if err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - cancel() - } - }() - } - defer func() { - _ = checker.Close() - cancel() - }() - } - } + checker, deferable := repo.CheckAttributeReader(commitID) + defer deferable() contentBuf := bytes.Buffer{} var content []byte diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 2e139dadd..2ea3f0187 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -45,11 +45,11 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt _, _ = messageBytes.WriteString(opts.Message) _, _ = messageBytes.WriteString("\n") - if CheckGitVersionAtLeast("1.7.9") == nil && (opts.KeyID != "" || opts.AlwaysSign) { + if opts.KeyID != "" || opts.AlwaysSign { cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID)) } - if CheckGitVersionAtLeast("2.0.0") == nil && opts.NoGPGSign { + if opts.NoGPGSign { cmd.AddArguments("--no-gpg-sign") } diff --git a/modules/log/event.go b/modules/log/event.go index f66ecd179..41bb241da 100644 --- a/modules/log/event.go +++ b/modules/log/event.go @@ -89,8 +89,10 @@ func (l *ChannelledLog) Start() { l.closeLogger() return } + l.emptyQueue() l.loggerProvider.Flush() case <-l.close: + l.emptyQueue() l.closeLogger() return } @@ -111,6 +113,20 @@ func (l *ChannelledLog) LogEvent(event *Event) error { } } +func (l *ChannelledLog) emptyQueue() bool { + for { + select { + case event, ok := <-l.queue: + if !ok { + return false + } + l.loggerProvider.LogEvent(event) + default: + return true + } + } +} + func (l *ChannelledLog) closeLogger() { l.loggerProvider.Flush() l.loggerProvider.Close() @@ -345,18 +361,41 @@ func (m *MultiChannelledLog) Start() { m.closeLoggers() return } + m.emptyQueue() m.rwmutex.RLock() for _, logger := range m.loggers { logger.Flush() } m.rwmutex.RUnlock() case <-m.close: + m.emptyQueue() m.closeLoggers() return } } } +func (m *MultiChannelledLog) emptyQueue() bool { + for { + select { + case event, ok := <-m.queue: + if !ok { + return false + } + m.rwmutex.RLock() + for _, logger := range m.loggers { + err := logger.LogEvent(event) + if err != nil { + fmt.Println(err) + } + } + m.rwmutex.RUnlock() + default: + return true + } + } +} + // LogEvent logs an event to this MultiChannelledLog func (m *MultiChannelledLog) LogEvent(event *Event) error { select { diff --git a/modules/markup/console/console.go b/modules/markup/console/console.go index b59594acb..597593eee 100644 --- a/modules/markup/console/console.go +++ b/modules/markup/console/console.go @@ -33,9 +33,6 @@ func (Renderer) Name() string { return MarkupName } -// NeedPostProcess implements markup.Renderer -func (Renderer) NeedPostProcess() bool { return false } - // Extensions implements markup.Renderer func (Renderer) Extensions() []string { return []string{".sh-session"} @@ -48,11 +45,6 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } } -// SanitizerDisabled disabled sanitize if return true -func (Renderer) SanitizerDisabled() bool { - return false -} - // CanRender implements markup.RendererContentDetector func (Renderer) CanRender(filename string, input io.Reader) bool { buf, err := io.ReadAll(input) diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 17c3fe6f4..5095b8546 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -29,9 +29,6 @@ func (Renderer) Name() string { return "csv" } -// NeedPostProcess implements markup.Renderer -func (Renderer) NeedPostProcess() bool { return false } - // Extensions implements markup.Renderer func (Renderer) Extensions() []string { return []string{".csv", ".tsv"} @@ -46,11 +43,6 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } } -// SanitizerDisabled disabled sanitize if return true -func (Renderer) SanitizerDisabled() bool { - return false -} - func writeField(w io.Writer, element, class, field string) error { if _, err := io.WriteString(w, "<"); err != nil { return err diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index a587abcc3..23dd45ba0 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -34,6 +34,11 @@ type Renderer struct { *setting.MarkupRenderer } +var ( + _ markup.PostProcessRenderer = (*Renderer)(nil) + _ markup.ExternalRenderer = (*Renderer)(nil) +) + // Name returns the external tool name func (p *Renderer) Name() string { return p.MarkupName @@ -56,7 +61,12 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule { // SanitizerDisabled disabled sanitize if return true func (p *Renderer) SanitizerDisabled() bool { - return p.DisableSanitizer + return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe +} + +// DisplayInIFrame represents whether render the content with an iframe +func (p *Renderer) DisplayInIFrame() bool { + return p.RenderContentMode == setting.RenderContentModeIframe } func envMark(envName string) string { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index f6aabc627..a7cf81250 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -5,12 +5,14 @@ package markup_test import ( + "context" "io" "strings" "testing" "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" . "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" @@ -25,14 +27,21 @@ var localMetas = map[string]string{ "repoPath": "../../integrations/gitea-repositories-meta/user13/repo11.git/", } +func TestMain(m *testing.M) { + setting.LoadAllowEmpty() + if err := git.InitSimple(context.Background()); err != nil { + log.Fatal("git init failed, err: %v", err) + } +} + func TestRender_Commits(t *testing.T) { setting.AppURL = TestAppURL test := func(input, expected string) { buffer, err := RenderString(&RenderContext{ - Ctx: git.DefaultContext, - Filename: ".md", - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + RelativePath: ".md", + URLPrefix: TestRepoURL, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -80,9 +89,9 @@ func TestRender_CrossReferences(t *testing.T) { test := func(input, expected string) { buffer, err := RenderString(&RenderContext{ - Filename: "a.md", - URLPrefix: setting.AppSubURL, - Metas: localMetas, + RelativePath: "a.md", + URLPrefix: setting.AppSubURL, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -124,8 +133,8 @@ func TestRender_links(t *testing.T) { test := func(input, expected string) { buffer, err := RenderString(&RenderContext{ - Filename: "a.md", - URLPrefix: TestRepoURL, + RelativePath: "a.md", + URLPrefix: TestRepoURL, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -223,8 +232,8 @@ func TestRender_email(t *testing.T) { test := func(input, expected string) { res, err := RenderString(&RenderContext{ - Filename: "a.md", - URLPrefix: TestRepoURL, + RelativePath: "a.md", + URLPrefix: TestRepoURL, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) @@ -281,8 +290,8 @@ func TestRender_emoji(t *testing.T) { test := func(input, expected string) { expected = strings.ReplaceAll(expected, "&", "&") buffer, err := RenderString(&RenderContext{ - Filename: "a.md", - URLPrefix: TestRepoURL, + RelativePath: "a.md", + URLPrefix: TestRepoURL, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 7ebdfea6c..37e11e606 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -205,12 +205,14 @@ func init() { // Renderer implements markup.Renderer type Renderer struct{} +var _ markup.PostProcessRenderer = (*Renderer)(nil) + // Name implements markup.Renderer func (Renderer) Name() string { return MarkupName } -// NeedPostProcess implements markup.Renderer +// NeedPostProcess implements markup.PostProcessRenderer func (Renderer) NeedPostProcess() bool { return true } // Extensions implements markup.Renderer @@ -223,11 +225,6 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { return []setting.MarkupSanitizerRule{} } -// SanitizerDisabled disabled sanitize if return true -func (Renderer) SanitizerDisabled() bool { - return false -} - // Render implements markup.Renderer func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { return render(ctx, input, output) diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index a069d402b..732fe1a6b 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -5,6 +5,7 @@ package markdown_test import ( + "context" "strings" "testing" @@ -31,6 +32,13 @@ var localMetas = map[string]string{ "repoPath": "../../../integrations/gitea-repositories-meta/user13/repo11.git/", } +func TestMain(m *testing.M) { + setting.LoadAllowEmpty() + if err := git.InitSimple(context.Background()); err != nil { + log.Fatal("git init failed, err: %v", err) + } +} + func TestRender_StandardLinks(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 2f394b992..8c9f3b3da 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -29,12 +29,14 @@ func init() { // Renderer implements markup.Renderer for orgmode type Renderer struct{} +var _ markup.PostProcessRenderer = (*Renderer)(nil) + // Name implements markup.Renderer func (Renderer) Name() string { return "orgmode" } -// NeedPostProcess implements markup.Renderer +// NeedPostProcess implements markup.PostProcessRenderer func (Renderer) NeedPostProcess() bool { return true } // Extensions implements markup.Renderer @@ -47,11 +49,6 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { return []setting.MarkupSanitizerRule{} } -// SanitizerDisabled disabled sanitize if return true -func (Renderer) SanitizerDisabled() bool { - return false -} - // Render renders orgmode rawbytes to HTML func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { htmlWriter := org.NewHTMLWriter() diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 6e4ae4e08..e88fa3118 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "net/url" "path/filepath" "strings" "sync" @@ -43,17 +44,18 @@ type Header struct { // RenderContext represents a render context type RenderContext struct { - Ctx context.Context - Filename string - Type string - IsWiki bool - URLPrefix string - Metas map[string]string - DefaultLink string - GitRepo *git.Repository - ShaExistCache map[string]bool - cancelFn func() - TableOfContents []Header + Ctx context.Context + RelativePath string // relative path from tree root of the branch + Type string + IsWiki bool + URLPrefix string + Metas map[string]string + DefaultLink string + GitRepo *git.Repository + ShaExistCache map[string]bool + cancelFn func() + TableOfContents []Header + InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page } // Cancel runs any cleanup functions that have been registered for this Ctx @@ -88,12 +90,24 @@ func (ctx *RenderContext) AddCancel(fn func()) { type Renderer interface { Name() string // markup format name Extensions() []string - NeedPostProcess() bool SanitizerRules() []setting.MarkupSanitizerRule - SanitizerDisabled() bool Render(ctx *RenderContext, input io.Reader, output io.Writer) error } +// PostProcessRenderer defines an interface for renderers who need post process +type PostProcessRenderer interface { + NeedPostProcess() bool +} + +// PostProcessRenderer defines an interface for external renderers +type ExternalRenderer interface { + // SanitizerDisabled disabled sanitize if return true + SanitizerDisabled() bool + + // DisplayInIFrame represents whether render the content with an iframe + DisplayInIFrame() bool +} + // RendererContentDetector detects if the content can be rendered // by specified renderer type RendererContentDetector interface { @@ -142,7 +156,7 @@ func DetectRendererType(filename string, input io.Reader) string { func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { if ctx.Type != "" { return renderByType(ctx, input, output) - } else if ctx.Filename != "" { + } else if ctx.RelativePath != "" { return renderFile(ctx, input, output) } return errors.New("Render options both filename and type missing") @@ -163,6 +177,27 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } +func renderIFrame(ctx *RenderContext, output io.Writer) error { + // set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight) + // at the moment, only "allow-scripts" is allowed for sandbox mode. + // "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token + // TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read + _, err := io.WriteString(output, fmt.Sprintf(` +<iframe src="%s/%s/%s/render/%s/%s" +name="giteaExternalRender" +onload="this.height=giteaExternalRender.document.documentElement.scrollHeight" +width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden" +sandbox="allow-scripts" +></iframe>`, + setting.AppSubURL, + url.PathEscape(ctx.Metas["user"]), + url.PathEscape(ctx.Metas["repo"]), + ctx.Metas["BranchNameSubURL"], + url.PathEscape(ctx.RelativePath), + )) + return err +} + func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { var wg sync.WaitGroup var err error @@ -175,7 +210,12 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr var pr2 io.ReadCloser var pw2 io.WriteCloser - if !renderer.SanitizerDisabled() { + var sanitizerDisabled bool + if r, ok := renderer.(ExternalRenderer); ok { + sanitizerDisabled = r.SanitizerDisabled() + } + + if !sanitizerDisabled { pr2, pw2 = io.Pipe() defer func() { _ = pr2.Close() @@ -194,7 +234,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr wg.Add(1) go func() { - if renderer.NeedPostProcess() { + if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() { err = PostProcess(ctx, pr, pw2) } else { _, err = io.Copy(pw2, pr) @@ -239,8 +279,15 @@ func (err ErrUnsupportedRenderExtension) Error() string { } func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error { - extension := strings.ToLower(filepath.Ext(ctx.Filename)) + extension := strings.ToLower(filepath.Ext(ctx.RelativePath)) if renderer, ok := extRenderers[extension]; ok { + if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() { + if !ctx.InStandalonePage { + // for an external render, it could only output its content in a standalone page + // otherwise, a <iframe> should be outputted to embed the external rendered page + return renderIFrame(ctx, output) + } + } return render(ctx, renderer, input, output) } return ErrUnsupportedRenderExtension{extension} diff --git a/modules/repository/init.go b/modules/repository/init.go index f5cef3301..e984697cd 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -323,19 +323,17 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi "-m", "Initial commit", } - if git.CheckGitVersionAtLeast("1.7.9") == nil { - sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) - if sign { - args = append(args, "-S"+keyID) - - if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { - // need to set the committer to the KeyID owner - committerName = signer.Name - committerEmail = signer.Email - } - } else if git.CheckGitVersionAtLeast("2.0.0") == nil { - args = append(args, "--no-gpg-sign") + sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) + if sign { + args = append(args, "-S"+keyID) + + if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { + // need to set the committer to the KeyID owner + committerName = signer.Name + committerEmail = signer.Email } + } else { + args = append(args, "--no-gpg-sign") } env = append(env, diff --git a/modules/setting/federation.go b/modules/setting/federation.go index cba1851df..b06d0a921 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -15,6 +15,7 @@ var ( Federation = struct { Enabled bool ShareUserStatistics bool + MaxSize int64 Algorithms []string DigestAlgorithm string GetHeaders []string @@ -22,7 +23,8 @@ var ( }{ Enabled: true, ShareUserStatistics: true, - Algorithms: []string{"rsa-sha256", "rsa-sha512"}, + MaxSize: 4, + Algorithms: []string{"rsa-sha256", "rsa-sha512", "ed25519"}, DigestAlgorithm: "SHA-256", GetHeaders: []string{"(request-target)", "Date"}, PostHeaders: []string{"(request-target)", "Date", "Digest"}, @@ -40,6 +42,9 @@ func newFederationService() { return } + // Get MaxSize in bytes instead of MiB + Federation.MaxSize = 1 << 20 * Federation.MaxSize + HttpsigAlgs = make([]httpsig.Algorithm, len(Federation.Algorithms)) for i, alg := range Federation.Algorithms { HttpsigAlgs[i] = httpsig.Algorithm(alg) diff --git a/modules/setting/markup.go b/modules/setting/markup.go index 5fb6af683..fd41bdd7c 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -20,6 +20,12 @@ var ( MermaidMaxSourceCharacters int ) +const ( + RenderContentModeSanitized = "sanitized" + RenderContentModeNoSanitizer = "no-sanitizer" + RenderContentModeIframe = "iframe" +) + // MarkupRenderer defines the external parser configured in ini type MarkupRenderer struct { Enabled bool @@ -29,7 +35,7 @@ type MarkupRenderer struct { IsInputFile bool NeedPostProcess bool MarkupSanitizerRules []MarkupSanitizerRule - DisableSanitizer bool + RenderContentMode string } // MarkupSanitizerRule defines the policy for whitelisting attributes on @@ -144,13 +150,28 @@ func newMarkupRenderer(name string, sec *ini.Section) { return } + if sec.HasKey("DISABLE_SANITIZER") { + log.Error("Deprecated setting `[markup.*]` `DISABLE_SANITIZER` present. This fallback will be removed in v1.18.0") + } + + renderContentMode := sec.Key("RENDER_CONTENT_MODE").MustString(RenderContentModeSanitized) + if !sec.HasKey("RENDER_CONTENT_MODE") && sec.Key("DISABLE_SANITIZER").MustBool(false) { + renderContentMode = RenderContentModeNoSanitizer // if only the legacy DISABLE_SANITIZER exists, use it + } + if renderContentMode != RenderContentModeSanitized && + renderContentMode != RenderContentModeNoSanitizer && + renderContentMode != RenderContentModeIframe { + log.Error("invalid RENDER_CONTENT_MODE: %q, default to %q", renderContentMode, RenderContentModeSanitized) + renderContentMode = RenderContentModeSanitized + } + ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{ - Enabled: sec.Key("ENABLED").MustBool(false), - MarkupName: name, - FileExtensions: exts, - Command: command, - IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false), - NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true), - DisableSanitizer: sec.Key("DISABLE_SANITIZER").MustBool(false), + Enabled: sec.Key("ENABLED").MustBool(false), + MarkupName: name, + FileExtensions: exts, + Command: command, + IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false), + NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true), + RenderContentMode: renderContentMode, }) } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index b77928fc9..99b197996 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -976,9 +976,6 @@ type remoteAddress struct { func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress { a := remoteAddress{} - if !m.IsMirror { - return a - } remoteURL := m.OriginalURL if remoteURL == "" { diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 6e4a4c97e..fd4d3875e 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -2,6 +2,7 @@ home=Startseite dashboard=Übersicht explore=Erkunden help=Hilfe +logo=Logo sign_in=Anmelden sign_in_with=Anmelden mit sign_out=Abmelden @@ -44,7 +45,9 @@ webauthn_error_unknown=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es webauthn_error_insecure=WebAuthn unterstützt nur sichere Verbindungen. Zum Testen über HTTP kannst du "localhost" oder "127.0.0.1" als Host verwenden webauthn_error_unable_to_process=Der Server konnte deine Anfrage nicht bearbeiten. webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist. +webauthn_error_empty=Du musst einen Namen für diesen Schlüssel festlegen. webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut. +webauthn_u2f_deprecated=Der Schlüssel: '%s' authentifiziert sich über den veralteten U2F-Prozess. Bitte registriere den Schlüssel neu und lösche die alte Registrierung. webauthn_reload=Neu laden repository=Repository @@ -103,6 +106,7 @@ error404=Die Seite, die du gerade versuchst aufzurufen, <strong>existiert entwed never=Niemals +rss_feed=RSS Feed [error] occurred=Ein Fehler ist aufgetreten @@ -143,6 +147,8 @@ path=Pfad sqlite_helper=Dateipfad zur SQLite3 Datenbank.<br>Gebe einen absoluten Pfad an, wenn Gitea als Service gestartet wird. reinstall_error=Du versuchst, in eine bereits existierende Gitea Datenbank zu installieren reinstall_confirm_message=Eine Neuinstallation mit einer bestehenden Gitea-Datenbank kann mehrere Probleme verursachen. In den meisten Fällen solltest du deine vorhandene "app.ini" verwenden, um Gitea auszuführen. Wenn du weist, was du tust, bestätigen die folgenden Angaben: +reinstall_confirm_check_1=Die von der SECRET_KEY in app.ini verschlüsselten Daten können verloren gehen: Benutzer können sich unter Umständen nicht mit 2FA/OTP einloggen & Spiegelungen könnten nicht mehr richtig funktionieren. Durch Ankreuzung dieses Kästchens bestätigst du, dass die aktuelle app.ini Datei den korrekten SECRET_KEY enthält. +reinstall_confirm_check_2=Die Repositories und Einstellungen müssen eventuell neu synchronisiert werden. Durch Ankreuzung dieses Kästchens bestätigst du, dass du die Hooks für die Repositories und authorized_keys Datei manuell neu synchronisierst. Du bestätigst, dass du sicher stellst, dass die Repository- und Spiegel-Einstellungen korrekt sind. reinstall_confirm_check_3=Du bestätigst, dass du absolut sicher bist, dass diese Gitea mit der richtigen app.ini läuft, und du sicher bist, dass du neu installieren musst. Du bestätigst, dass du die oben genannten Risiken anerkennst. err_empty_db_path=Der SQLite3 Datenbankpfad darf nicht leer sein. no_admin_and_disable_registration=Du kannst Selbst-Registrierungen nicht deaktivieren, ohne ein Administratorkonto zu erstellen. @@ -265,6 +271,7 @@ search=Suche code=Code search.fuzzy=Ähnlich search.match=Genau +code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. repo_no_results=Keine passenden Repositories gefunden. user_no_results=Keine passenden Benutzer gefunden. org_no_results=Keine passenden Organisationen gefunden. @@ -278,6 +285,7 @@ register_helper_msg=Hast du bereits ein Konto? Jetzt anmelden! social_register_helper_msg=Hast du bereits ein Konto? Jetzt verknüpfen! disable_register_prompt=Die Registrierung ist deaktiviert. Bitte wende dich an den Administrator. disable_register_mail=E-Mail-Bestätigung bei der Registrierung ist deaktiviert. +manual_activation_only=Kontaktiere den Website-Administrator, um die Aktivierung abzuschließen. remember_me=Dieses Gerät speichern forgot_password_title=Passwort vergessen forgot_password=Passwort vergessen? @@ -435,6 +443,7 @@ size_error=` muss die Größe %s haben.` min_size_error=` muss mindestens %s Zeichen enthalten.` max_size_error=` darf höchstens %s Zeichen enthalten.` email_error=` ist keine gültige E-Mail-Adresse.` +url_error=`'%s' ist keine valide URL.` include_error=` muss den Text „%s“ enthalten.` glob_pattern_error=` Der Glob Pattern ist ungültig: %s.` regex_pattern_error=` regex ist ungültig: %s.` @@ -482,7 +491,9 @@ auth_failed=Authentifizierung fehlgeschlagen: %v still_own_repo=Dein Konto besitzt ein oder mehrere Repositories. Diese müssen zuerst gelöscht oder übertragen werden. still_has_org=Dein Account ist Mitglied in mindestens einer Organisation. Bitte verlasse diese zuerst. +still_own_packages=Ihr Konto besitzt ein oder mehrere Pakete; löschen Sie diese zuerst. org_still_own_repo=Diese Organisation besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese zuerst. +org_still_own_packages=Diese Organisation besitzt noch ein oder mehrere Pakete; lösche diese bitte zuerst. target_branch_not_exist=Der Ziel-Branch existiert nicht. @@ -545,8 +556,11 @@ continue=Weiter cancel=Abbrechen language=Sprache ui=Theme +hidden_comment_types=Ausgeblendeter Kommentartypen +comment_type_group_reference=Verweis auf Mitglieder comment_type_group_label=Label comment_type_group_milestone=Meilenstein +comment_type_group_assignee=Zuweisung comment_type_group_title=Titel comment_type_group_branch=Branch comment_type_group_time_tracking=Zeiterfassung @@ -703,6 +717,9 @@ generate_token_success=Ein neuer Token wurde generiert. Kopiere diesen, da er ni generate_token_name_duplicate=<strong>%s</strong> wurde bereits als Anwendungsname verwendet. Bitte wähle einen neuen Namen. delete_token=Löschen access_token_deletion=Zugriffstoken löschen +access_token_deletion_cancel_action=Abbrechen +access_token_deletion_confirm_action=Löschen +access_token_deletion_desc=Wenn du ein Token löschst, haben die Anwendungen, die es nutzen, keinen Zugriff mehr auf deinen Account. Dies kann nicht rückgängig gemacht werden. Fortfahren? delete_token_success=Der Zugriffstoken wurde gelöscht. Anwendungen die diesen Token genutzt haben, haben nun keinen Zugriff mehr auf deinen Account. manage_oauth2_applications=OAuth2 Anwendungen verwalten @@ -755,6 +772,7 @@ passcode_invalid=Die PIN ist falsch. Probiere es erneut. twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird. twofa_failed_get_secret=Fehler beim Abrufen des Secrets. +webauthn_desc=Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard „<a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn</a>“ unterstützen. webauthn_register_key=Sicherheitsschlüssel hinzufügen webauthn_nickname=Nickname webauthn_delete_key=Sicherheitsschlüssel entfernen @@ -763,6 +781,7 @@ webauthn_delete_key_desc=Wenn du einen Sicherheitsschlüssel entfernst, kannst d manage_account_links=Verknüpfte Accounts verwalten manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft. account_links_not_available=Es sind keine externen Accounts mit diesem Gitea-Account verknüpft. +link_account=Account verbinden remove_account_link=Verknüpften Account entfernen remove_account_link_desc=Wenn du den verknüpften Account entfernst, wirst du darüber nicht mehr auf deinen Gitea-Account zugreifen können. Fortfahren? remove_account_link_success=Der verknüpfte Account wurde entfernt. @@ -843,6 +862,7 @@ default_branch=Standardbranch default_branch_helper=Der default Branch ist der Basisbranch für Pull-Requests und Commits. mirror_prune=Entfernen mirror_prune_desc=Entferne veraltete remote-tracking Referenzen +mirror_interval=Mirror-Intervall. Gültige Zeiteinheiten sind 'h', 'm', sowie 's'. 0 deaktiviert die automatische Synchronisierung. (Minimum: %s) mirror_interval_invalid=Das Spiegel-Intervall ist ungültig. mirror_address=Klonen via URL mirror_address_desc=Gib alle erforderlichen Anmeldedaten im Abschnitt "Authentifizierung" ein. @@ -994,6 +1014,7 @@ tags=Tags issues=Issues pulls=Pull-Requests project_board=Projekte +packages=Pakete labels=Label org_labels_desc=Labels der Organisationsebene, die mit <strong>allen Repositories</strong> in dieser Organisation verwendet werden können org_labels_desc_manage=verwalten @@ -1024,6 +1045,7 @@ line_unicode=`Diese Zeile hat versteckte Unicode-Zeichen` escape_control_characters=Escapen unescape_control_characters=Unescapen file_copy_permalink=Permalink kopieren +view_git_blame=Git Blame ansehen video_not_supported_in_browser=Dein Browser unterstützt das HTML5 'video'-Tag nicht. audio_not_supported_in_browser=Dein Browser unterstützt den HTML5 'audio'-Tag nicht. stored_lfs=Gespeichert mit Git LFS @@ -1102,6 +1124,7 @@ editor.no_commit_to_branch=Kann nicht direkt zum Branch committen, da: editor.user_no_push_to_branch=Benutzer kann nicht in die Branch pushen editor.require_signed_commit=Branch erfordert einen signierten Commit editor.cherry_pick=Cherry-Picke %s von: +editor.revert=%s zurücksetzen auf: commits.desc=Durchsuche die Quellcode-Änderungshistorie. commits.commits=Commits @@ -1124,6 +1147,11 @@ commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck commit.actions=Aktionen commit.revert=Zurücksetzen +commit.revert-header=Setze zurück: %s +commit.revert-content=Branch auswählen, der zurückgesetzt werden soll: +commit.cherry-pick=Cherry-Pick +commit.cherry-pick-header=Cherry-Picke: %s +commit.cherry-pick-content=Branch auswählen, auf dem Cherry-Picked werden soll: ext_issues=Zugriff auf Externe Issues ext_issues.desc=Link zu externem Issuetracker. @@ -1162,6 +1190,7 @@ projects.board.deletion_desc=Beim Löschen eines Projektboards werden alle Eintr projects.board.color=Farbe projects.open=Öffnen projects.close=Schließen +projects.board.assigned_to=Zugewiesen an issues.desc=Verwalte Bug-Reports, Aufgaben und Meilensteine. issues.filter_assignees=Filter @@ -1256,6 +1285,7 @@ issues.filter_sort.moststars=Meiste Favoriten issues.filter_sort.feweststars=Wenigste Favoriten issues.filter_sort.mostforks=Meiste Forks issues.filter_sort.fewestforks=Wenigste Forks +issues.keyword_search_unavailable=Zurzeit ist die Stichwort-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. issues.action_open=Öffnen issues.action_close=Schließen issues.action_label=Label @@ -1351,6 +1381,9 @@ issues.lock.reason=Grund für die Sperrung issues.lock.title=Diskussion zu diesem Issue sperren. issues.unlock.title=Diskussion zu diesem Issue entsperren. issues.comment_on_locked=Du kannst einen gesperrten Issue nicht kommentieren. +issues.delete=Löschen +issues.delete.title=Dieses Issue löschen? +issues.delete.text=Möchtest du dieses Issue wirklich löschen? (Dadurch wird der Inhalt dauerhaft gelöscht. Denke daran, es stattdessen zu schließen, wenn du es archivieren willst) issues.tracker=Zeiterfassung issues.start_tracking_short=Zeiterfassung starten issues.start_tracking=Zeiterfassung starten @@ -1391,6 +1424,8 @@ issues.due_date_remove=hat %[2]s das Fälligkeitsdatum %[1]s entfernt issues.due_date_overdue=Überfällig issues.due_date_invalid=Das Fälligkeitsdatum ist ungültig oder außerhalb des zulässigen Bereichs. Bitte verwende das Format „jjjj-mm-tt“. issues.dependency.title=Abhängigkeiten +issues.dependency.issue_no_dependencies=Keine Abhängigkeiten gesetzt. +issues.dependency.pr_no_dependencies=Keine Abhängigkeiten gesetzt. issues.dependency.add=Abhängigkeit hinzufügen… issues.dependency.cancel=Abbrechen issues.dependency.remove=Entfernen @@ -1429,6 +1464,7 @@ issues.review.add_review_request=hat ein Review von %s %s angefragt issues.review.remove_review_request=hat die Aufforderung zum Review an %s %s entfernt issues.review.remove_review_request_self=hat das Review verweigert %s issues.review.pending=Ausstehend +issues.review.pending.tooltip=Dieser Kommentar ist derzeit nicht für andere Benutzer sichtbar. Um Ihre ausstehenden Kommentare einzureichen, wählen Sie '%s' -> '%s/%s/%s' oben auf der Seite. issues.review.review=Review issues.review.reviewers=Reviewer issues.review.outdated=Veraltet @@ -1447,6 +1483,7 @@ issues.content_history.created=erstellt issues.content_history.delete_from_history=Aus dem Verlauf entfernen issues.content_history.delete_from_history_confirm=Aus dem Verlauf löschen? issues.content_history.options=Optionen +issues.reference_link=Referenz: %s compare.compare_base=Basis compare.compare_head=vergleichen @@ -1455,7 +1492,13 @@ pulls.desc=Pull-Requests und Code-Reviews aktivieren. pulls.new=Neuer Pull-Request pulls.view=Pull-Request ansehen pulls.compare_changes=Neuer Pull-Request +pulls.allow_edits_from_maintainers=Änderungen von Maintainern erlauben +pulls.allow_edits_from_maintainers_desc=Nutzer mit Schreibzugriff auf den Basisbranch können auch auf diesen Branch pushen +pulls.allow_edits_from_maintainers_err=Aktualisieren fehlgeschlagen pulls.compare_changes_desc=Wähle den Ziel- und Quellbranch aus. +pulls.has_viewed_file=Gesehen +pulls.has_changed_since_last_review=Inzwischen geändert +pulls.viewed_files_label=%[1]d / %[2]d Dateien reviewed pulls.compare_base=Ziel pulls.compare_compare=pullen von pulls.switch_comparison_type=Vergleichstyp wechseln @@ -1555,8 +1598,17 @@ pulls.merge_instruction_hint=`Siehe auch die <a class="show-instruction">Anleitu pulls.merge_instruction_step1_desc=Wechsle auf einen neuen Branch in deinem lokalen Repository und teste die Änderungen. pulls.merge_instruction_step2_desc=Führe die Änderungen zusammen und aktualisiere den Stand online auf Gitea. +pulls.auto_merge_button_when_succeed=(Wenn die Checks erfolgreich sind) +pulls.auto_merge_when_succeed=Automergen, sobald alle Checks erfüllt sind +pulls.auto_merge_newly_scheduled=Der Pull-Request wird automatisch gemergt, wenn alle Checks erfolgreich sind. +pulls.auto_merge_has_pending_schedule=%[1]s hat einen Automerge für diesen Pull-Request %[2]s geplant. +pulls.auto_merge_cancel_schedule=Automerge abbrechen +pulls.auto_merge_not_scheduled=Dieser Pull Request hat keinen geplanten Automerge. +pulls.auto_merge_canceled_schedule=Der Automerge dieses Pull-Requests wurde abgebrochen. +pulls.auto_merge_newly_scheduled_comment=`hat einen Automerge für diesen Pull-Request %[1]s geplant` +pulls.auto_merge_canceled_schedule_comment=`hat den Automerge für diesen Pull-Request %[1]s abgebrochen` milestones.new=Neuer Meilenstein milestones.closed=Geschlossen %s @@ -1700,6 +1752,8 @@ search.search_repo=Repository durchsuchen search.fuzzy=Ähnlich search.match=Genau search.results=Suchergebnisse für „%s“ in <a href="%s"> %s</a> +search.code_no_results=Es konnte kein passender Code für deinen Suchbegriff gefunden werden. +search.code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. settings=Einstellungen settings.desc=In den Einstellungen kannst du die Einstellungen des Repositorys anpassen @@ -1750,6 +1804,9 @@ settings.tracker_url_format_error=Das URL-Format des externen Issue-Trackers ist settings.tracker_issue_style=Namenskonvention des externen Issue-Trackers settings.tracker_issue_style.numeric=Numerisch settings.tracker_issue_style.alphanumeric=Alphanumerisch +settings.tracker_issue_style.regexp=Regulärer Ausdruck +settings.tracker_issue_style.regexp_pattern=Regulärer Ausdruck +settings.tracker_issue_style.regexp_pattern_desc=Die erste gecapturte Gruppe wird statt <code>{index}</code> verwendet. settings.tracker_url_format_desc=Du kannst die Platzhalter <code>{user}</code>, <code>{repo}</code>, <code>{index}</code> für den Benutzernamen, den Namen des Repositorys und die Issue-Nummer verwenden. settings.enable_timetracker=Zeiterfassung aktivieren settings.allow_only_contributors_to_track_time=Nur Mitarbeitern erlauben, die Zeiterfassung zu nutzen @@ -1761,12 +1818,15 @@ settings.pulls.allow_rebase_merge_commit=Rebasing mit expliziten merge commits a settings.pulls.allow_squash_commits=Mergen von Commits durch Squash aktivieren settings.pulls.allow_manual_merge=Manuelles Mergen von Pull Requests aktivieren settings.pulls.enable_autodetect_manual_merge=Autoerkennung von manuellen Merges aktivieren (in Ausnahmefällen können Fehleinschätzungen auftreten) +settings.pulls.allow_rebase_update=Update von Pull Request Branches per Rebase erlauben settings.pulls.default_delete_branch_after_merge=Standardmäßig bei Pull-Requests den Branch nach dem Mergen löschen +settings.packages_desc=Repository Packages Registry aktivieren settings.projects_desc=Repository-Projekte aktivieren settings.admin_settings=Administratoreinstellungen settings.admin_enable_health_check=Repository-Health-Checks aktivieren (git fsck) settings.admin_code_indexer=Code-Indexer settings.admin_stats_indexer=Code-Statistik-Indexer +settings.admin_indexer_commit_sha=Zuletzt indexierter SHA settings.admin_indexer_unindexed=Unindiziert settings.reindex_button=Zur Warteschlange für erneutes Indexieren hinzufügen settings.reindex_requested=Erneutes Indexieren angefordert @@ -1918,6 +1978,8 @@ settings.event_pull_request_review=Pull-Request überprüft settings.event_pull_request_review_desc=Pull-Request genehmigt, abgelehnt oder Kommentar hinterlassen. settings.event_pull_request_sync=Pull-Request synchronisiert settings.event_pull_request_sync_desc=Pull-Request synchronisiert. +settings.event_package=Paket +settings.event_package_desc=Paket wurde in einem Repository erstellt oder gelöscht. settings.branch_filter=Branch-Filter settings.branch_filter_desc=Whitelist für Branches für Push-, Erzeugungs- und Löschevents, als glob Pattern beschrieben. Es werden Events für alle Branches gemeldet, falls das Pattern <code>*</code> ist, oder falls es leer ist. Siehe die <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> Dokumentation für die Syntax (Englisch). Beispiele: <code>master</code>, <code>{master,release*}</code>. settings.active=Aktiv @@ -1931,6 +1993,7 @@ settings.hook_type=Hook-Typ settings.slack_token=Token settings.slack_domain=Domain settings.slack_channel=Kanal +settings.add_web_hook_desc=<a target="_blank" rel="noreferrer" href="%s">%s</a> in das Repository integrieren. settings.web_hook_name_gitea=Gitea settings.web_hook_name_gogs=Gogs settings.web_hook_name_slack=Slack @@ -2000,6 +2063,7 @@ settings.require_signed_commits_desc=Pushes auf diesen Branch ablehnen, wenn Com settings.protect_protected_file_patterns=Geschützte Dateimuster (durch Semikolon getrennt '\;'): settings.protect_protected_file_patterns_desc=Geschützte Dateien, die nicht einmal geändert werden können, wenn der Benutzer die Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten, oder zu löschen. Verschiedene Pattern können per Semicolon (';') getrennt werden. Siehe die <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> Dokumentation für die Pattern Syntax (Englisch). Beispiele: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. settings.protect_unprotected_file_patterns=Ungeschützte Dateimuster (durch Semikolon '\;' getrennt): +settings.protect_unprotected_file_patterns_desc=Ungeschützte Dateien können ohne Push-Beschränkung geändert werden, falls der Benutzer Schreibzugriff hat. Mehrere Muster können mit Semikolon getrennt werden ('\;'). Siehe die <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> Dokumentation für Mustersyntax. Beispiele: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. settings.add_protected_branch=Schutz aktivieren settings.delete_protected_branch=Schutz deaktivieren settings.update_protect_branch_success=Branch-Schutz für den Branch „%s“ wurde geändert. @@ -2209,6 +2273,7 @@ branch.renamed=Branch %s wurde in %s umbenannt. tag.create_tag=Tag <strong>%s</strong> erstellen tag.create_tag_operation=Tag erstellen tag.confirm_create_tag=Tag erstellen +tag.create_tag_from=Neuen Tag von '%s' erstellen tag.create_success=Tag "%s" wurde erstellt. @@ -2217,6 +2282,8 @@ topic.done=Fertig topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein. +find_file.go_to_file=Datei suchen +find_file.no_matching=Keine passende Datei gefunden error.csv.too_large=Diese Datei kann nicht gerendert werden, da sie zu groß ist. error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwartetes Zeichen in Zeile %d und Spalte %d enthält. @@ -2354,9 +2421,11 @@ first_page=Erste last_page=Letzte total=Gesamt: %d +dashboard.new_version_hint=Gitea %s ist jetzt verfügbar, die 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.statistic_info=Giteas Datenbank hat <b>%d</b> Benutzer, <b>%d</b> Organisationen, <b>%d</b> öffentliche Schlüssel, <b>%d</b> Repositorys, <b>%d</b> Beobachtungen, <b>%d</b> Favoriten, ~<b>%d</b> Aktionen, <b>%d</b> Zugriffe, <b>%d</b> Issues, <b>%d</b> Kommentare, <b>%d</b> Konten sozialer Netzwerke, <b>%d</b> Gefolgte, <b>%d</b> Mirrors, <b>%d</b> Releases, <b>%d</b> Login-Quellen, <b>%d</b> Webhooks, <b>%d</b> Meilensteine, <b>%d</b> Label, <b>%d</b> Hook-Tasks, <b>%d</b> Teams, <b>%d</b> Aktualisierungs-Tasks, <b>%d</b> Anhänge. dashboard.operation_name=Name der Operation dashboard.operation_switch=Wechseln dashboard.operation_run=Ausführen @@ -2395,6 +2464,7 @@ dashboard.resync_all_hooks=Synchronisiere „pre-receive“-, „update“- und dashboard.reinit_missing_repos=Alle Git-Repositories mit Einträgen neu einlesen dashboard.sync_external_users=Externe Benutzerdaten synchronisieren dashboard.cleanup_hook_task_table=Hook-Task-Tabelle bereinigen +dashboard.cleanup_packages=Veraltete Pakete löschen dashboard.server_uptime=Server-Uptime dashboard.current_goroutine=Aktuelle Goroutinen dashboard.current_memory_usage=Aktuelle Speichernutzung @@ -2426,6 +2496,8 @@ dashboard.last_gc_pause=Letzte GC-Pause dashboard.gc_times=Anzahl GC dashboard.delete_old_actions=Alle alten Aktionen aus der Datenbank löschen dashboard.delete_old_actions.started=Löschen aller alten Aktionen in der Datenbank gestartet. +dashboard.update_checker=Update-Checker +dashboard.delete_old_system_notices=Alle alten Systemmeldungen aus der Datenbank löschen users.user_manage_panel=Benutzerkontenverwaltung users.new_account=Benutzerkonto erstellen @@ -2460,8 +2532,10 @@ users.allow_import_local=Darf lokale Repositories importieren users.allow_create_organization=Darf Organisationen erstellen users.update_profile=Benutzerkonto aktualisieren users.delete_account=Benutzerkonto löschen +users.cannot_delete_self=Du kannst dich nicht selbst löschen users.still_own_repo=Dieser Benutzer besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese zuerst. users.still_has_org=Dieser Nutzer ist Mitglied einer Organisation. Du musst ihn zuerst aus allen Organisationen entfernen. +users.still_own_packages=Dieser Benutzer besitzt noch ein oder mehrere Pakete. Lösche diese Pakete zuerst. users.deletion_success=Der Account wurde gelöscht. users.reset_2fa=2FA zurücksetzen users.list_status_filter.menu_text=Filter @@ -2508,6 +2582,16 @@ repos.forks=Forks repos.issues=Issues repos.size=Größe +packages.package_manage_panel=Paketverwaltung +packages.total_size=Gesamtgröße: %s +packages.owner=Besitzer +packages.creator=Ersteller +packages.name=Name +packages.version=Version +packages.type=Typ +packages.repository=Repository +packages.size=Größe +packages.published=Veröffentlicht defaulthooks=Standard-Webhooks defaulthooks.desc=Webhooks senden automatisch eine HTTP POST Anfrage an einen Server, wenn bestimmte Gitea Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">Webhooks Anleitung</a> (auf englisch). @@ -2551,9 +2635,13 @@ auths.filter=Benutzerfilter auths.admin_filter=Admin-Filter auths.restricted_filter=Eingeschränkte Filter auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Stern ('*'), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen. +auths.verify_group_membership=Gruppenmitgliedschaft in LDAP verifizieren (zum Überspringen leer lassen) auths.group_search_base=Gruppensuche Basisdomainname auths.group_attribute_list_users=Gruppenattribut, welches die die Benutzerliste enthält auths.user_attribute_in_group=Benutzerattribut in der Gruppenliste +auths.map_group_to_team=Ordne LDAP-Gruppen Organisationsteams zu (zum Überspringen leer lassen) +auths.map_group_to_team_removal=Benutzer aus synchronisierten Teams entfernen, wenn der Benutzer nicht zur entsprechenden LDAP-Gruppe gehört +auths.enable_ldap_groups=LDAP-Gruppen aktivieren auths.ms_ad_sa=MS-AD-Suchattribute auths.smtp_auth=SMTP-Authentifizierungstyp auths.smtphost=SMTP-Host @@ -2579,6 +2667,7 @@ auths.oauth2_authURL=Authorisierungs-URL auths.oauth2_profileURL=Profil-URL auths.oauth2_emailURL=E-Mail-URL auths.skip_local_two_fa=Lokale 2FA überspringen +auths.skip_local_two_fa_helper=Leer lassen bedeutet, dass lokale User die 2FA immer noch bestehen müssen, um sich anzumelden auths.oauth2_tenant=Inhaber auths.oauth2_scopes=Zusätzliche Bereiche auths.oauth2_required_claim_name=Benötigter Claim-Name @@ -2771,9 +2860,12 @@ monitor.next=Nächste Ausführung monitor.previous=Letzte Ausführung monitor.execute_times=Ausführungen monitor.process=Laufende Prozesse +monitor.stacktrace=Stacktraces +monitor.goroutines=%d Goroutinen monitor.desc=Beschreibung monitor.start=Startzeit monitor.execute_time=Ausführungszeit +monitor.last_execution_result=Ergebnis monitor.process.cancel=Prozess abbrechen monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen monitor.process.cancel_notices=Abbrechen: <strong>%s</strong>? @@ -2785,6 +2877,7 @@ monitor.queue.type=Typ monitor.queue.exemplar=Beispieltyp monitor.queue.numberworkers=Anzahl der Worker monitor.queue.maxnumberworkers=Maximale Anzahl der Worker +monitor.queue.numberinqueue=Nummer in der Warteschlange monitor.queue.review=Konfiguration überprüfen monitor.queue.review_add=Worker hinzufügen/prüfen monitor.queue.configuration=Erstkonfiguration @@ -2804,6 +2897,12 @@ monitor.queue.pool.flush.title=Warteschlange leeren monitor.queue.pool.flush.desc=Flush fügt einen Worker hinzu, der sich beendet, sobald die Warteschlange leer ist oder in den Timeout läuft. monitor.queue.pool.flush.submit=Füge Flush-Worker hinzu monitor.queue.pool.flush.added=Flush-Worker hinzugefügt für %[1]s +monitor.queue.pool.pause.title=Warteschlange pausieren +monitor.queue.pool.pause.desc=Eine pausierte Warteschlange verarbeitet keine Daten mehr +monitor.queue.pool.pause.submit=Warteschlange pausieren +monitor.queue.pool.resume.title=Warteschlange fortsetzen +monitor.queue.pool.resume.desc=Diese Warteschlange wieder starten +monitor.queue.pool.resume.submit=Warteschlange fortsetzen monitor.queue.settings.title=Pool-Einstellungen monitor.queue.settings.desc=Pools wachsen dynamisch mit einem Boost als Reaktion auf die Blockierung ihrer Workerwarteschlangen. Diese Änderungen wirken sich nicht auf die aktuellen Worker Gruppen aus. @@ -2934,4 +3033,94 @@ error.no_unit_allowed_repo=Du hast keine Berechtigung, um auf irgendeinen Bereic error.unit_not_allowed=Du hast keine Berechtigung, um auf diesen Repository-Bereich zuzugreifen. [packages] +title=Pakete +desc=Repository-Pakete verwalten. +empty=Noch keine Pakete vorhanden. +empty.documentation=Weitere Informationen zur Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">Dokumentation</a>. +filter.type=Typ +filter.type.all=Alle +filter.no_result=Keine Ergebnisse mit diesen Kriterien gefunden. +filter.container.tagged=Getaggt +filter.container.untagged=Nicht getaggt +published_by=%[1]s von <a href="%[2]s">%[3]s</a> veröffentlicht +published_by_in=%[1]s von <a href="%[2]s">%[3]s</a> in <a href="%[4]s"><strong>%[5]s</strong></a> veröffentlicht +installation=Installation +about=Über dieses Paket +requirements=Voraussetzungen +dependencies=Abhängigkeiten +keywords=Schlüsselwörter +details=Details +details.author=Autor +details.project_site=Projektseite +details.license=Lizenz +assets=Dateien +versions=Versionen +versions.on=am +versions.view_all=Alle anzeigen +dependency.id=ID +dependency.version=Version +composer.registry=Setze diese Paketverwaltung in deiner <code>~/.composer/config.json</code> Datei auf: +composer.install=Nutze folgenden Befehl, um das Paket mit Composer zu installieren: +composer.documentation=Weitere Informationen zur Composer-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">Dokumentation</a>. +composer.dependencies=Abhängigkeiten +composer.dependencies.development=Entwicklungsabhängigkeiten +conan.details.repository=Repository +conan.registry=Diese Registry über die Kommandozeile einrichten: +conan.install=Um das Paket mit Conan zu installieren, führe den folgenden Befehl aus: +conan.documentation=Weitere Informationen zur Conan-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">Dokumentation</a>. +container.details.type=Container-Image Typ +container.details.platform=Plattform +container.details.repository_site=Repository-Seite +container.details.documentation_site=Dokumentationsseite +container.pull=Downloade das Container-Image aus der Kommandozeile: +container.documentation=Weitere Informationen zur Container-Imageverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">Dokumentation</a>. +container.multi_arch=Betriebsystem / Architektur +container.layers=Container-Image Ebenen +container.labels=Labels +container.labels.key=Schlüssel +container.labels.value=Wert +generic.download=Downloade das Paket aus der Kommandozeile: +generic.documentation=Weitere Informationen zur generischen Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">Dokumentation</a>. +helm.registry=Diese Paketverwaltung über die Kommandozeile einrichten: +helm.install=Nutze folgenden Befehl, um das Paket zu installieren: +helm.documentation=Weitere Informationen zur Helm-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">Dokumentation</a>. +maven.registry=Setze diese Paketverwaltung in der <code>pom.xml</code> deines Projektes auf: +maven.install=Nimm Folgendes in den <code>dependencies</code> deiner <code>pom.xml</code> auf, um das Paket zu installieren: +maven.install2=Über die Kommandozeile ausführen: +maven.download=Nutze folgendes Kommando, um die Dependency herunterzuladen: +maven.documentation=Weitere Informationen zur Maven-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">Dokumentation</a>. +nuget.registry=Diese Registry über die Kommandozeile einrichten: +nuget.install=Um das Paket mit NuGet zu installieren, führe den folgenden Befehl aus: +nuget.documentation=Weitere Informationen zur NuGet-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">Dokumentation</a>. +nuget.dependency.framework=Zielframework +npm.registry=Setze diese Paketverwaltung in der <code>.npmrc</code> deines Projektes auf: +npm.install=Um das Paket mit npm zu installieren, führe den folgenden Befehl aus: +npm.install2=oder füge es zur package.json-Datei hinzu: +npm.documentation=Weitere Informationen zur npm-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">Dokumentation</a>. +npm.dependencies=Abhängigkeiten +npm.dependencies.development=Entwicklungsabhängigkeiten +npm.dependencies.peer=Peer Abhängigkeiten +npm.dependencies.optional=Optionale Abhängigkeiten +npm.details.tag=Tag +pypi.requires=Erfordert Python +pypi.install=Nutze folgenden Befehl, um das Paket mit pip zu installieren: +pypi.documentation=Weitere Informationen zur PyPI-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">Dokumentation</a>. +rubygems.install=Um das Paket mit gem zu installieren, führe den folgenden Befehl aus: +rubygems.install2=oder füg es zum Gemfile hinzu: +rubygems.dependencies.runtime=Laufzeitabhängigkeiten +rubygems.dependencies.development=Entwicklungsabhängigkeiten +rubygems.required.ruby=Benötigt Ruby Version +rubygems.required.rubygems=Benötigt RubyGem Version +rubygems.documentation=Weitere Informationen zur RubyGems-Paketverwaltung findest du in der <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">Dokumentation</a>. +settings.link=Dieses Paket einem Repository zuweisen +settings.link.description=Wenn du ein Paket mit einem Repository verknüpfst, wird es in der Paketliste des Repositories angezeigt. +settings.link.select=Repository auswählen +settings.link.button=Repository-Link aktualisieren +settings.link.success=Repository-Link wurde erfolgreich aktualisiert. +settings.link.error=Fehler beim Aktualisieren des Repository-Links. +settings.delete=Paket löschen +settings.delete.description=Das Löschen eines Pakets ist dauerhaft und kann nicht rückgängig gemacht werden. +settings.delete.notice=Du bist dabei, %s (%s) zu löschen. Dieser Vorgang ist unwiderruflich. Bist du sicher? +settings.delete.success=Das Paket wurde gelöscht. +settings.delete.error=Löschen des Pakets fehlgeschlagen. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index bd909b03a..d9ed7a8a7 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -2,6 +2,7 @@ home=Inicio dashboard=Panel de control explore=Explorar help=Ayuda +logo=Logotipo sign_in=Iniciar sesión sign_in_with=Iniciar sesión con sign_out=Cerrar sesión @@ -105,6 +106,7 @@ error404=La página a la que está intentando acceder o <strong>no existe</stron never=Nunca +rss_feed=Fuentes RSS [error] occurred=Ha ocurrido un error @@ -441,6 +443,7 @@ size_error=` debe ser de tamaño %s.` min_size_error=` debe contener al menos %s caracteres.` max_size_error=` debe contener como máximo %s caracteres.` email_error=` no es una dirección de correo válida.` +url_error=`'%s' no es un URL válido.` include_error=` debe contener la subcadena '%s'.` glob_pattern_error=` el patrón globo no es válido: %s.` regex_pattern_error=` el patrón de regex no es válido: %s.` @@ -488,7 +491,9 @@ auth_failed=Autenticación fallo: %v still_own_repo=Su cuenta posee uno o más repositorios; elimine o transfiera primero. still_has_org=Su cuenta es miembro de una o más organizaciones; déjalas primero. +still_own_packages=Su cuenta posee uno o más repositorios; elimine o transfiera primero. org_still_own_repo=Esta organización todavía es dueña de uno o más repositorios; elimínelos o transfiéralos primero. +org_still_own_packages=Esta organización todavía es dueña de uno o más repositorios; elimínelos o transfiéralos primero. target_branch_not_exist=La rama de destino no existe @@ -561,6 +566,12 @@ comment_type_group_branch=Rama comment_type_group_time_tracking=Seguimiento de Tiempo comment_type_group_deadline=Fecha límite comment_type_group_dependency=Dependencia +comment_type_group_lock=Estado de bloqueo +comment_type_group_review_request=Revisión solicitada +comment_type_group_pull_request_push=Commits añandidos +comment_type_group_project=Proyecto +comment_type_group_issue_ref=Referencia del incidente +saved_successfully=Tus ajustes se han guardado correctamente. privacy=Privacidad keep_activity_private=Ocultar la actividad de la página del perfil keep_activity_private_popup=Hace la actividad visible sólo para ti y los administradores @@ -706,6 +717,9 @@ generate_token_success=Su nuevo token ha sido generado. Cópielo ahora, ya que n generate_token_name_duplicate=<strong>%s</strong> ya se ha utilizado como nombre de la aplicación. Por favor, utilice una nueva. delete_token=Eliminar access_token_deletion=Eliminar Token de Acceso +access_token_deletion_cancel_action=Cancelar +access_token_deletion_confirm_action=Eliminar +access_token_deletion_desc=Eliminar un token revocará el acceso a su cuenta para las aplicaciones que lo usen. Esto no se puede deshacer. ¿Continuar? delete_token_success=El token ha sido eliminado. Las aplicaciones que lo usen ya no tienen acceso a su cuenta. manage_oauth2_applications=Administrar aplicaciones OAuth2 @@ -767,6 +781,7 @@ webauthn_delete_key_desc=Si elimina una clave de seguridad no podrá utilizarla manage_account_links=Administrar cuentas vinculadas manage_account_links_desc=Estas cuentas externas están vinculadas a su cuenta de Gitea. account_links_not_available=Actualmente no hay cuentas externas vinculadas a su cuenta de Gitea. +link_account=Enlazar cuenta remove_account_link=Eliminar cuenta vinculada remove_account_link_desc=Eliminar una cuenta vinculada revocará su acceso a su cuenta de Gitea. ¿Continuar? remove_account_link_success=La cuenta vinculada ha sido eliminada. @@ -847,6 +862,7 @@ default_branch=Rama por defecto default_branch_helper=La rama por defecto es la rama base para pull requests y commits de código. mirror_prune=Purgar mirror_prune_desc=Eliminar referencias de seguimiento de remotes obsoletas +mirror_interval=Intervalo de replicación (las unidades de tiempo válidas son «h», «m» y «s»). 0 desactiva la sincronización automática. (Intervalo mínimo: %s) mirror_interval_invalid=El intervalo de réplica no es válido. mirror_address=Clonar desde URL mirror_address_desc=Ponga cualquier credencial requerida en la sección de Autorización. @@ -998,6 +1014,7 @@ tags=Etiquetas issues=Incidencias pulls=Pull Requests project_board=Proyectos +packages=Paquetes labels=Etiquetas org_labels_desc=Etiquetas de nivel de la organización que pueden ser utilizadas con <strong>todos los repositorios</strong> bajo esta organización org_labels_desc_manage=gestionar @@ -1028,6 +1045,7 @@ line_unicode=`Esta línea tiene caracteres unicode ocultos` escape_control_characters=Escapar unescape_control_characters=No Escapar file_copy_permalink=Copiar Permalink +view_git_blame=Ver la culpa de Git video_not_supported_in_browser=Su navegador no soporta el tag video de HTML5. audio_not_supported_in_browser=Su navegador no soporta el tag audio de HTML5. stored_lfs=Almacenados con Git LFS @@ -1066,6 +1084,10 @@ editor.add_tmpl=Añadir '<filename>' editor.add=Añadir '%s' editor.update=Actualizar '%s' editor.delete=Eliminar '%s' +editor.patch=Aplicar parche +editor.patching=Parcheando: +editor.fail_to_apply_patch=No se puede aplicar el parche '%s' +editor.new_patch=Nuevo parche editor.commit_message_desc=Añadir una descripción extendida opcional… editor.signoff_desc=Añadir un trailer firmado por el committer al final del mensaje de registro de confirmación. editor.commit_directly_to_this_branch=Hacer commit directamente en la rama <strong class="branch-name">%s</strong>. @@ -1101,6 +1123,8 @@ editor.cannot_commit_to_protected_branch=No se puede hacer commit a la rama prot editor.no_commit_to_branch=No se puede hacer commit directamente a la rama porque: editor.user_no_push_to_branch=El usuario no puede hacer push a la rama editor.require_signed_commit=Esta rama requiere un commit firmado +editor.cherry_pick=Hacer Cherry-pick %s en: +editor.revert=Revertir %s en: commits.desc=Ver el historial de cambios de código fuente. commits.commits=Commits @@ -1121,6 +1145,13 @@ commits.signed_by_untrusted_user_unmatched=Firmado por un usuario no fiable que commits.gpg_key_id=ID de clave GPG commits.ssh_key_fingerprint=Huella clave SSH +commit.actions=Acciones +commit.revert=Revertir +commit.revert-header=Revertir: %s +commit.revert-content=Seleccionar rama en la que revertir: +commit.cherry-pick=Hacer Cherry-pick +commit.cherry-pick-header=Hacer Cherry-pick: %s +commit.cherry-pick-content=Seleccionar rama en la que hacer cherry-pick: ext_issues=Acceso a incidencias externas ext_issues.desc=Enlace a un gestor de incidencias externo. @@ -1159,6 +1190,7 @@ projects.board.deletion_desc=Eliminar un tablón de proyecto mueve todas las inc projects.board.color=Color projects.open=Abrir projects.close=Cerrar +projects.board.assigned_to=Asignado a issues.desc=Organizar los informes de fallos, tareas e hitos. issues.filter_assignees=Filtrar asignado @@ -1253,6 +1285,7 @@ issues.filter_sort.moststars=Mas estrellas issues.filter_sort.feweststars=Menor número de estrellas issues.filter_sort.mostforks=La mayoría de forks issues.filter_sort.fewestforks=Menor número de forks +issues.keyword_search_unavailable=Actualmente la búsqueda por palabra clave no está disponible. Por favor, póngase en contacto con el administrador de su sitio. issues.action_open=Abrir issues.action_close=Cerrar issues.action_label=Etiqueta @@ -1348,6 +1381,9 @@ issues.lock.reason=Motivo del bloqueo issues.lock.title=Bloquear conversación sobre esta incidencia. issues.unlock.title=Desbloquear conversación sobre esta incidencia. issues.comment_on_locked=No puede comentar una incidencia bloqueada. +issues.delete=Eliminar +issues.delete.title=¿Eliminar esta incidencia? +issues.delete.text=¿Realmente quieres eliminar esta incidencia? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo en su lugar, si quieres mantenerlo archivado) issues.tracker=Gestor de tiempo issues.start_tracking_short=Iniciar temporizador issues.start_tracking=Inicio de seguimiento de tiempo @@ -1388,6 +1424,8 @@ issues.due_date_remove=eliminó la fecha de vencimiento %s %s issues.due_date_overdue=Vencido issues.due_date_invalid=La fecha de vencimiento es inválida o está fuera de rango. Por favor utilice el formato 'aaaa-mm-dd'. issues.dependency.title=Dependencias +issues.dependency.issue_no_dependencies=No se han establecido dependencias. +issues.dependency.pr_no_dependencies=No se han establecido dependencias. issues.dependency.add=Añadir dependencia… issues.dependency.cancel=Cancelar issues.dependency.remove=Eliminar @@ -1426,6 +1464,7 @@ issues.review.add_review_request=solicitud de revisión de %s %s issues.review.remove_review_request=solicitud de revisión eliminada para %s %s issues.review.remove_review_request_self=rechazó revisar %s issues.review.pending=Pendiente +issues.review.pending.tooltip=Este comentario no es visible actualmente para otros usuarios. Para enviar sus comentarios pendientes, seleccione '%s' -> '%s/%s/%s' en la parte superior de la página. issues.review.review=Revisar issues.review.reviewers=Revisores issues.review.outdated=Obsoleto @@ -1444,6 +1483,7 @@ issues.content_history.created=creado issues.content_history.delete_from_history=Eliminar del historial issues.content_history.delete_from_history_confirm=¿Eliminar del historial? issues.content_history.options=Opciones +issues.reference_link=Referencia: %s compare.compare_base=base compare.compare_head=comparar @@ -1452,7 +1492,13 @@ pulls.desc=Activar Pull Requests y revisiones de código. pulls.new=Nuevo Pull Request pulls.view=Ver Pull Request pulls.compare_changes=Nuevo pull request +pulls.allow_edits_from_maintainers=Permitir ediciones de mantenedores +pulls.allow_edits_from_maintainers_desc=Los usuarios con acceso de escritura a la rama base también pueden hacer push a esta rama +pulls.allow_edits_from_maintainers_err=Error al Actualizar pulls.compare_changes_desc=Seleccione la rama en la que se fusiona y la rama a recuperar. +pulls.has_viewed_file=Visto +pulls.has_changed_since_last_review=Cambiado desde tu última revisión +pulls.viewed_files_label=%[1]d / %[2]d archivos vistos pulls.compare_base=fusionar en pulls.compare_compare=recuperar de pulls.switch_comparison_type=Cambiar tipo de comparación @@ -1552,8 +1598,17 @@ pulls.merge_instruction_hint=`También puede ver <a class="show-instruction">ins pulls.merge_instruction_step1_desc=Desde el repositorio de su proyecto, revisa una nueva rama y prueba los cambios. pulls.merge_instruction_step2_desc=Combine los cambios y actualice en Gitea. +pulls.auto_merge_button_when_succeed=(cuando las comprobaciones tengan éxito) +pulls.auto_merge_when_succeed=Fusionar automática cuando todas las comprobaciones tengan éxito +pulls.auto_merge_newly_scheduled=El Pull Request se programó para fusionarse cuando todas las comprobaciones tengan éxito. +pulls.auto_merge_has_pending_schedule=%[1]s programó este Pull Request para fusionar automática cuando todas las comprobaciones tengan éxito %[2]s. +pulls.auto_merge_cancel_schedule=Cancelar fusión automática +pulls.auto_merge_not_scheduled=Este Pull Request no está programado para fusionar automática. +pulls.auto_merge_canceled_schedule=Fusión automaticá estaba cancellada para este Pull Request. +pulls.auto_merge_newly_scheduled_comment=`programó este Pull Request para fusionar automática cuando todas las comprobaciones tengan éxito %[1]s` +pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de este Pull Request %[1]s` milestones.new=Nuevo hito milestones.closed=Cerrada %s @@ -1697,6 +1752,8 @@ search.search_repo=Buscar repositorio search.fuzzy=Parcial search.match=Coincidir search.results=Resultados de la búsqueda para "%s" en <a href="%s">%s</a> +search.code_no_results=No se ha encontrado código de fuente que coincida con su término de búsqueda. +search.code_search_unavailable=Actualmente la búsqueda de código no está disponible. Póngase en contacto con el administrador de su sitio. settings=Configuración settings.desc=La configuración es donde puede administrar la configuración del repositorio @@ -1747,6 +1804,9 @@ settings.tracker_url_format_error=El formato de la URL del gestor de incidencias settings.tracker_issue_style=Formato numérico del gestor de incidencias externo settings.tracker_issue_style.numeric=Numérico settings.tracker_issue_style.alphanumeric=Alfanumérico +settings.tracker_issue_style.regexp=Expresión regular +settings.tracker_issue_style.regexp_pattern=Pauta de expresiones regulares +settings.tracker_issue_style.regexp_pattern_desc=Se utilizará el primer grupo capturado en lugar de <code>{index}</code>. settings.tracker_url_format_desc=Utilice los marcadores <code>{user}</code>, <code>{repo}</code> y <code>{index}</code> para designar el usuario, el nombre del repositorio y el índice de incidencia. settings.enable_timetracker=Habilitar gestor de tiempo settings.allow_only_contributors_to_track_time=Deje que solo los colaboradores hagan un seguimiento del tiempo @@ -1758,7 +1818,9 @@ settings.pulls.allow_rebase_merge_commit=Activar Rebase con commits explícitos settings.pulls.allow_squash_commits=Activar Squash en los commits fusionados settings.pulls.allow_manual_merge=Habilitar marcar PR como fusionado manualmente settings.pulls.enable_autodetect_manual_merge=Habilitar la autodetección de los commits fusionado manualmente (Nota: en algunos casos especiales, pueden producirse errores de apreciación) +settings.pulls.allow_rebase_update=Habilitar la actualización de la rama de Pull Request por rebase settings.pulls.default_delete_branch_after_merge=Eliminar por defecto la rama de pull request después de fusionar +settings.packages_desc=Habilitar registro de paquetes de repositorio settings.projects_desc=Activar Proyectos de Repositorio settings.admin_settings=Ajustes de administrador settings.admin_enable_health_check=Activar cheques de estado de salud del repositorio (git fsck) @@ -1916,6 +1978,8 @@ settings.event_pull_request_review=Pull Request revisado settings.event_pull_request_review_desc=Pull request aprobado, rechazado o comentario de revisión. settings.event_pull_request_sync=Pull Request sincronizado settings.event_pull_request_sync_desc=Pull request sincronizado. +settings.event_package=Paquete +settings.event_package_desc=Paquete creado o eliminado en un repositorio. settings.branch_filter=Filtro de rama settings.branch_filter_desc=Lista blanca de rama para eventos de push, creación de rama y eliminación de rama, especificados como patrón globo. Si está vacío o <code>*</code>, se reportan eventos para todas las ramas. Ver <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentación para la sintaxis. Ejemplos: <code>master</code>, <code>{master,release*}</code>. settings.active=Activo @@ -1929,6 +1993,23 @@ settings.hook_type=Tipo de Hook settings.slack_token=Token settings.slack_domain=Dominio settings.slack_channel=Canal +settings.add_web_hook_desc=Integrar <a target="_blank" rel="noreferrer" href="%s">%s</a> en su repositorio. +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_telegram=Telegram +settings.web_hook_name_matrix=Matrix +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite +settings.web_hook_name_feishu=Feishu +settings.web_hook_name_larksuite=Lark Suite +settings.web_hook_name_wechatwork=WeCom (Wechat Work) +settings.web_hook_name_packagist=Packagist +settings.packagist_username=Nombre de usuario Packagist +settings.packagist_api_token=Token de API +settings.packagist_package_url=URL del paquete Packagist settings.deploy_keys=Claves de Implementación settings.add_deploy_key=Añadir Clave de Implementación settings.deploy_key_desc=Las claves de implementación tienen acceso de sólo lectura al repositorio. @@ -2184,11 +2265,15 @@ branch.included_desc=Esta rama forma parte de la predeterminada branch.included=Incluida branch.create_new_branch=Crear rama desde la rama: branch.confirm_create_branch=Crear rama +branch.create_branch_operation=Crear rama branch.new_branch=Crear nueva rama branch.new_branch_from=Crear nueva rama desde '%s' branch.renamed=La rama %s fue renombrada a %s. tag.create_tag=Crear etiqueta <strong>%s</strong> +tag.create_tag_operation=Crear etiqueta +tag.confirm_create_tag=Crear etiqueta +tag.create_tag_from=Crear etiqueta nueva a partir de «%s» tag.create_success=La etiqueta '%s' ha sido creada. @@ -2197,6 +2282,8 @@ topic.done=Hecho topic.count_prompt=No puede seleccionar más de 25 temas topic.format_prompt=Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo. +find_file.go_to_file=Ir al archivo +find_file.no_matching=No se encontró ningún archivo que coincidiese error.csv.too_large=No se puede renderizar este archivo porque es demasiado grande. error.csv.unexpected=No se puede procesar este archivo porque contiene un carácter inesperado en la línea %d y la columna %d. @@ -2334,9 +2421,11 @@ first_page=Primera last_page=Última total=Total: %d +dashboard.new_version_hint=Gitea %s ya está disponible; en este momento ejecuta la versión %s. Consulte el <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> para conocer más. dashboard.statistic=Resumen dashboard.operations=Operaciones de mantenimiento dashboard.system_status=Estado del sistema +dashboard.statistic_info=La base de datos de Gitea contiene <b>%d</b> usuarios, <b>%d</b> organizaciones, <b>%d</b> claves públicas, <b>%d</b> repositorios, <b>%d</b> elementos observados, <b>%d</b> destacados, ~<b>%d</b> acciones, <b>%d</b> accesos, <b>%d</b> incidencias, <b>%d</b> comentarios, <b>%d</b> cuentas sociales, <b>%d</b> seguidos, <b>%d</b> réplicas, <b>%d</b> releases, <b>%d</b> orígenes de autenticación, <b>%d</b> webhooks, <b>%d</b> milestones, <b>%d</b> etiquetas, <b>%d</b> tareas de hook, <b>%d</b> equipos, <b>%d</b> tareas de actualización, <b>%d</b> adjuntos. dashboard.operation_name=Nombre de la operación dashboard.operation_switch=Interruptor dashboard.operation_run=Ejecutar @@ -2375,6 +2464,7 @@ dashboard.resync_all_hooks=Resincronizar los hooks de pre-recepción, actualizac dashboard.reinit_missing_repos=Reiniciar todos los repositorios Git faltantes de los que existen registros dashboard.sync_external_users=Sincronizar datos de usuario externo dashboard.cleanup_hook_task_table=Limpiar tabla hook_task +dashboard.cleanup_packages=Limpieza de paquetes caducados dashboard.server_uptime=Tiempo de actividad del servidor dashboard.current_goroutine=Gorutinas actuales dashboard.current_memory_usage=Uso de memoria actual @@ -2406,6 +2496,8 @@ dashboard.last_gc_pause=Última Pausa por GC dashboard.gc_times=Ejecuciones GC dashboard.delete_old_actions=Eliminar todas las acciones antiguas de la base de datos dashboard.delete_old_actions.started=Eliminar todas las acciones antiguas de la base de datos inicializada. +dashboard.update_checker=Buscador de actualizaciones +dashboard.delete_old_system_notices=Borrar todos los avisos antiguos del sistema de la base de datos users.user_manage_panel=Gestión de cuentas de usuario users.new_account=Crear Cuenta de Usuario @@ -2440,8 +2532,10 @@ users.allow_import_local=Puede importar repositorios locales users.allow_create_organization=Puede crear organizaciones users.update_profile=Actualizar cuenta de usuario users.delete_account=Eliminar Cuenta de Usuario +users.cannot_delete_self=No puedes eliminarte a ti mismo users.still_own_repo=Este usuario todavía posee uno o más depósitos. Eliminar o transferir estos repositorios primero. users.still_has_org=Este usuario es un miembro de una organización. Primero retire el usuario de cualquier organización. +users.still_own_packages=Este usuario todavía posee uno o más paquetes. Elimine estos paquetes primero. users.deletion_success=La cuenta de usuario ha sido eliminada. users.reset_2fa=Reiniciar 2FA users.list_status_filter.menu_text=Filtro @@ -2488,6 +2582,16 @@ repos.forks=Forks repos.issues=Incidencias repos.size=Tamaño +packages.package_manage_panel=Gestión de paquetes +packages.total_size=Tamaño total: %s +packages.owner=Propietario +packages.creator=Creador +packages.name=Nombre +packages.version=Versión +packages.type=Tipo +packages.repository=Repositorio +packages.size=Tamaño +packages.published=Publicado defaulthooks=Webhooks por defecto defaulthooks.desc=Los Webhooks automáticamente hacen peticiones HTTP POST a un servidor cuando ciertos eventos de Gitea se activan. Los ganchos definidos aquí son predeterminados y serán copiados en todos los nuevos repositorios. Leer más en la guía <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">webhooks</a>. @@ -2531,9 +2635,13 @@ auths.filter=Filtro de usuario auths.admin_filter=Filtro de aministrador auths.restricted_filter=Filtro restringido auths.restricted_filter_helper=Dejar en blanco para no establecer ningún usuario como restringido. Utilice un asterisco ('*') para establecer todos los usuarios que no coincidan con el filtro de administración como restringido. +auths.verify_group_membership=Verificar pertenencia al grupo en LDAP (dejar el filtro vacío para saltar) auths.group_search_base=Base DN para la búsqueda de grupos auths.group_attribute_list_users=Atributo del grupo que contiene la lista de usuarios auths.user_attribute_in_group=Atributo de usuario listado en el grupo +auths.map_group_to_team=Mapear grupos LDAP a equipos de la Organización (dejar el campo vacío para saltar) +auths.map_group_to_team_removal=Eliminar usuarios de equipos sincronizados si el usuario no pertenece al grupo LDAP correspondiente +auths.enable_ldap_groups=Habilitar grupos LDAP auths.ms_ad_sa=Atributos de búsqueda de MS AD auths.smtp_auth=Tipo de autenticación SMTP auths.smtphost=Servidor SMTP @@ -2752,9 +2860,12 @@ monitor.next=Siguiente monitor.previous=Anterior monitor.execute_times=Ejecuciones monitor.process=Procesos en ejecución +monitor.stacktrace=Rastros de pila +monitor.goroutines=%d Gorutinas monitor.desc=Descripción monitor.start=Hora de Inicio monitor.execute_time=Tiempo de ejecución +monitor.last_execution_result=Resultado monitor.process.cancel=Cancelar el proceso monitor.process.cancel_desc=Cancelar un proceso puede ocasionar una pérdida de datos monitor.process.cancel_notices=Cancelar: <strong>%s</strong>? @@ -2766,6 +2877,7 @@ monitor.queue.type=Tipo monitor.queue.exemplar=Ejemplo monitor.queue.numberworkers=Número de trabajadores monitor.queue.maxnumberworkers=Número máximo de trabajadores +monitor.queue.numberinqueue=Número en cola monitor.queue.review=Revisar configuración monitor.queue.review_add=Revisar/Añadir trabajadores monitor.queue.configuration=Configuración inicial @@ -2786,7 +2898,10 @@ monitor.queue.pool.flush.desc=Al vaciar la cola se añadirá un worker que termi monitor.queue.pool.flush.submit=Añadir trabajador de vaciado monitor.queue.pool.flush.added=Trabajador de vaciado añadido por %[1]s monitor.queue.pool.pause.title=Pausar cola +monitor.queue.pool.pause.desc=La pausa de una cola evitará que procese datos monitor.queue.pool.pause.submit=Pausar cola +monitor.queue.pool.resume.title=Reanudar cola +monitor.queue.pool.resume.desc=Establecer esta cola para reanudar el trabajo monitor.queue.pool.resume.submit=Reanudar cola monitor.queue.settings.title=Ajustes del grupo @@ -2918,4 +3033,94 @@ error.no_unit_allowed_repo=No tiene permisos para acceder a ninguna sección de error.unit_not_allowed=No tiene permisos para acceder a esta sección del repositorio. [packages] +title=Paquetes +desc=Administrar paquetes del repositorio. +empty=Todavía no hay paquetes. +empty.documentation=Para más información sobre el registro de paquetes, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">la documentación</a>. +filter.type=Tipo +filter.type.all=Todo +filter.no_result=El filtro no produjo ningún resultado. +filter.container.tagged=Etiquetado +filter.container.untagged=Etiqueta eliminada +published_by=Publicado %[1]s por <a href="%[2]s">%[3]s</a> +published_by_in=Publicado %[1]s por <a href="%[2]s">%[3]s</a> en <a href="%[4]s"><strong>%[5]s</strong></a> +installation=Instalación +about=Acerca de este paquete +requirements=Requisitos +dependencies=Dependencias +keywords=Palabras clave +details=Detalles +details.author=Autoría +details.project_site=Sitio del proyecto +details.license=Licencia +assets=Activos +versions=Versiones +versions.on=en +versions.view_all=Ver todo +dependency.id=Id. +dependency.version=Versión +composer.registry=Configura este registro en el archivo <code>~/.composer/config.json</code>: +composer.install=Para instalar el paquete usando Composer, ejecute el siguiente comando: +composer.documentation=Para más información sobre el registro de Composer, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">la documentación</a>. +composer.dependencies=Dependencias +composer.dependencies.development=Dependencias de desarrollo +conan.details.repository=Repositorio +conan.registry=Configurar este registro desde la línea de comandos: +conan.install=Para instalar el paquete usando Conan, ejecuta el siguiente comando: +conan.documentation=Para más información sobre el registro de Conan, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">la documentación</a>. +container.details.type=Tipo de imagen +container.details.platform=Plataforma +container.details.repository_site=Sitio del repositorio +container.details.documentation_site=Sitio de documentación +container.pull=Arrastra la imagen desde la línea de comandos: +container.documentation=Para más información sobre el registro de Container, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">la documentación</a>. +container.multi_arch=SO / Arquitectura +container.layers=Capas de imagen +container.labels=Etiquetas +container.labels.key=Clave +container.labels.value=Valor +generic.download=Descargar paquete desde la línea de comandos: +generic.documentation=Para más información sobre el registro genérico, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">la documentación</a>. +helm.registry=Configurar este registro desde la línea de comandos: +helm.install=Para instalar el paquete, ejecute el siguiente comando: +helm.documentation=Para obtener más información sobre el registro de Helm, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">la documentación</a>. +maven.registry=Configure este registro en su proyecto <code>pom.xml</code> archivo: +maven.install=Para usar el paquete incluya lo siguiente en el bloque <code>dependencias</code> en el archivo <code>pom.xml</code>: +maven.install2=Ejecutar vía línea de comandos: +maven.download=Para descargar la dependencia, ejecute vía línea de comandos: +maven.documentation=Para obtener más información sobre el registro de Maven, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">la documentación</a>. +nuget.registry=Configurar este registro desde la línea de comandos: +nuget.install=Para instalar el paquete usando NuGet, ejecute el siguiente comando: +nuget.documentation=Para obtener más información sobre el registro de NuGet consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">la documentación</a>. +nuget.dependency.framework=Marco de destino +npm.registry=Configura este registro en tu proyecto <code>.npmrc</code> archivo: +npm.install=Para instalar el paquete usando npm, ejecute el siguiente comando: +npm.install2=o añádelo al archivo package.json: +npm.documentation=Para más información sobre el registro de npm, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">la documentación</a>. +npm.dependencies=Dependencias +npm.dependencies.development=Dependencias de desarrollo +npm.dependencies.peer=Dependencias de pares +npm.dependencies.optional=Dependencias opcionales +npm.details.tag=Etiqueta +pypi.requires=Requiere Python +pypi.install=Para instalar el paquete usando pip, ejecute el siguiente comando: +pypi.documentation=Para obtener más información sobre el registro PyPI, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">la documentación</a>. +rubygems.install=Para instalar el paquete usando gem, ejecute el siguiente comando: +rubygems.install2=o añádelo al archivo Gemfile: +rubygems.dependencies.runtime=Dependencias en tiempo de ejecución +rubygems.dependencies.development=Dependencias de desarrollo +rubygems.required.ruby=Requiere versión Ruby +rubygems.required.rubygems=Requiere la versión de RubyGem +rubygems.documentation=Para obtener más información sobre el registro de RubyGems, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">la documentación</a>. +settings.link=Vincular este paquete a un repositorio +settings.link.description=Si enlaza un paquete con un repositorio, el paquete se enumera en la lista de paquetes del repositorio. +settings.link.select=Seleccionar repositorio +settings.link.button=Actualizar enlace de repositorio +settings.link.success=El enlace del repositorio se ha actualizado correctamente. +settings.link.error=Error al actualizar el enlace del repositorio. +settings.delete=Eliminar paquete +settings.delete.description=La eliminación de un paquete es permanente y no se puede deshacer. +settings.delete.notice=Está a punto de eliminar %s (%s). Esta operación es irreversible, ¿está seguro? +settings.delete.success=Se ha eliminado el paquete. +settings.delete.error=No se pudo eliminar el paquete. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 0751883aa..ac3e75edf 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -34,6 +34,7 @@ twofa=Authentification à deux facteurs twofa_scratch=Code de secours pour l'authentification à deux facteurs passcode=Code d'accès +webauthn_insert_key=Insérez votre clé de sécurité repository=Dépôt organization=Organisation @@ -91,6 +92,7 @@ error404=La page que vous essayez d'atteindre <strong>n'existe pas</strong> ou < never=Jamais +rss_feed=Flux RSS [error] missing_csrf=Requête incorrecte: aucun jeton CSRF présent @@ -347,6 +349,7 @@ reset_password.text=Veuillez cliquer sur le lien suivant pour récupérer votre register_success=Inscription réussie +issue_assigned.issue=@%[1]s vous a assigné le ticket %[2]s dans le dépôt %[3]s. issue.x_mentioned_you=<b>@%s</b> vous a mentionné: issue.action.approve=<b>@%[1]s</b> a approuvé cette demande d'ajout. @@ -357,12 +360,17 @@ issue.action.ready_for_review=<b>@%[1]s</b> a marqué cette demande d'ajout prê issue.in_tree_path=Dans %s: release.new.subject=%s publiée dans %s +release.new.text=<b>@%[1]s</b> a publié [2]s dans %[3]s release.title=Titre: %s +release.note=Remarque : release.downloads=Téléchargements : +release.download.zip=Code source (ZIP) +release.download.targz=Code source (TAR.GZ) repo.transfer.subject_to=%s aimerait transférer "%s" à %s repo.transfer.subject_to_you=%s aimerait vous transférer "%s" repo.transfer.to_you=vous +repo.transfer.body=Pour l'accepter ou le rejeter, visitez %s ou ignorez-le. repo.collaborator.added.subject=%s vous a ajouté à %s repo.collaborator.added.text=Vous avez été ajouté en tant que collaborateur du dépôt : @@ -475,6 +483,7 @@ form.name_chars_not_allowed=Le nom d'utilisateur '%s' contient des caractères n [settings] profile=Profil account=Compte +appearance=Apparence password=Mot de passe security=Sécurité avatar=Avatar @@ -498,7 +507,9 @@ website=Site Web location=Localisation update_theme=Mise à jour du thème update_profile=Valider les modifications +update_language=Mettre à jour le langage update_language_not_found=La langue '%s' n'est pas disponible. +update_language_success=Le langage à été mis à jour. update_profile_success=Votre profil a été mis à jour. change_username=Votre nom d'utilisateur a été modifié. change_username_prompt=Remarque : changer votre nom d'utilisateur change également l'URL de votre compte. @@ -507,6 +518,21 @@ continue=Continuer cancel=Annuler language=Langues ui=Thème +comment_type_group_reference=Référence +comment_type_group_label=Étiquette +comment_type_group_milestone=Jalon +comment_type_group_assignee=Assigné à +comment_type_group_title=Titre +comment_type_group_branch=Branche +comment_type_group_time_tracking=Suivi du temps +comment_type_group_deadline=Date d’échéance +comment_type_group_dependency=Dépendance +comment_type_group_lock=Verrouiller le statut +comment_type_group_review_request=Demande de revue +comment_type_group_pull_request_push=Révisions ajoutées +comment_type_group_project=Projet +comment_type_group_issue_ref=Référence du ticket +saved_successfully=Vos paramètres ont été enregistrés avec succès. privacy=Confidentialité keep_activity_private=Masquer l'activité de la page de profil keep_activity_private_popup=Rend l'activité visible uniquement pour vous et les administrateurs @@ -520,6 +546,7 @@ delete_current_avatar=Supprimer l'avatar actuel uploaded_avatar_not_a_image=Le fichier téléversé n'est pas une image. uploaded_avatar_is_too_big=Le fichier téléversé dépasse la taille maximale. update_avatar_success=Votre avatar a été mis à jour. +update_user_avatar_success=L'avatar de l'utilisateur a été mis à jour. change_password=Modifier le mot de passe old_password=Mot de passe actuel @@ -580,6 +607,18 @@ ssh_key_been_used=Cette clef SSH a déjà été ajoutée au serveur. ssh_key_name_used=Une clé SSH avec le même nom existe déjà sur votre compte. ssh_principal_been_used=Ce principal a déjà été ajouté au serveur. gpg_key_id_used=Une clef GPG publique avec le même identifiant existe déjà. +gpg_key_verified=Clé vérifiée +gpg_invalid_token_signature=La clé GPG fournie, la signature et le jeton ne correspondent pas ou le jeton n'est pas à jour. +gpg_token_required=Vous devez fournir une signature pour le jeton ci-dessous +gpg_token=Jeton +gpg_token_help=Vous pouvez générer une signature en utilisant : +gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig +gpg_token_signature=Signature GPG renforcée +key_signature_gpg_placeholder=Commence par '-----BEGIN PGP SIGNATURE-----' +ssh_token_required=Vous devez fournir une signature pour le jeton ci-dessous +ssh_token=Jeton +ssh_token_help=Vous pouvez générer une signature en utilisant : +ssh_token_signature=Signature SSH renforcée subkeys=Sous-clés key_id=Clé ID key_name=Nom de la Clé @@ -702,6 +741,12 @@ email_notifications.onmention=N'envoyer un e-mail que si vous êtes mentionné email_notifications.disable=Désactiver les notifications par email email_notifications.submit=Définir la préférence e-mail +visibility.public=Publique +visibility.public_tooltip=Visible par tous les utilisateurs +visibility.limited=Limitée +visibility.limited_tooltip=Visible uniquement par les utilisateurs connectés +visibility.private=Privée +visibility.private_tooltip=Visible uniquement par les membres de l'organisation [repo] new_repo_helper=Un dépôt contient tous les fichiers du projet, y compris l'historique des révisions. Vous l'avez déjà ailleurs ? <a href="%s">Migrer le dépôt.</a> @@ -724,6 +769,10 @@ fork_repo=Créer une bifurcation du dépôt fork_from=Bifurquer depuis fork_visibility_helper=La visibilité d'un dépôt bifurqué ne peut pas être modifiée. use_template=Utiliser ce modèle +clone_in_vsc=Cloner dans VS Code +download_zip=Télécharger le ZIP +download_tar=Télécharger le TAR.GZ +download_bundle=Télécharger le BUNDLE generate_repo=Générer un dépôt generate_from=Générer depuis repo_desc=Description @@ -742,6 +791,8 @@ readme_helper_desc=Vous pouvez écrire ici une description complète de votre pr auto_init=Initialiser le dépôt (ajoute les fichiers .gitignore, Licence et LISEZMOI) trust_model_helper=Sélectionnez le modèle de confiance pour la vérification des signatures. Les options possibles sont : trust_model_helper_collaborator=Collaborateur: Faire confiance aux signatures des collaborateurs +trust_model_helper_committer=Auteur de la révision: Faire confiance aux signatures qui correspondent à celle de l'auteur de la révision +trust_model_helper_collaborator_committer=Collaborateur+Auteur: Faire confiance aux signatures des collaborateurs qui correspondent à l'auteur de la révision trust_model_helper_default=Valeur par défaut: Utiliser le modèle de confiance par défaut pour cette installation create_repo=Créer un dépôt default_branch=Branche par défaut @@ -777,6 +828,7 @@ delete_preexisting_label=Supprimer delete_preexisting=Supprimer les fichiers préexistants delete_preexisting_content=Supprimer les fichiers dans %s delete_preexisting_success=Supprimer les fichiers non adoptés dans %s +blame_prior=Voir le blame avant cette modification transfer.accept=Accepter le transfert transfer.accept_desc=Transférer à "%s" @@ -844,6 +896,11 @@ migrate.migrate=Migrer depuis %s migrate.migrating=Migration de <b>%s</b> ... migrate.migrating_failed=La migration de <b>%s</b> a échoué. migrate.migrating_failed.error=Erreur: %s +migrate.git.description=Migrer uniquement un dépôt depuis n’importe quel service Git. +migrate.gitlab.description=Migrer les données depuis gitlab.com ou d’autres instances de GitLab. +migrate.gitea.description=Migrer les données depuis gitea.com ou d’autres instances de Gitea. +migrate.gogs.description=Migrer les données depuis notabug.org ou d’autres instances de Gogs. +migrate.onedev.description=Migrer les données depuis code.onedev.io ou d’autre instance de OneDev. migrate.migrating_git=Migration des données Git migrate.migrating_topics=Migration des sujets migrate.migrating_milestones=Migration des jalons @@ -896,6 +953,7 @@ release=Versions releases=Versions tag=Tag released_this=a publié ceci +file.title=%s sur %s file_raw=Brut file_history=Historique file_view_source=Voir le code source @@ -904,6 +962,7 @@ file_view_raw=Voir le Raw file_permalink=Lien permanent file_too_large=Le fichier est trop gros pour être affiché. +file_copy_permalink=Copier le lien permanent video_not_supported_in_browser=Votre navigateur ne supporte pas le tag HTML5 "video". audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5. stored_lfs=Stocké avec Git LFS @@ -912,7 +971,9 @@ commit_graph=Graphique des révisions commit_graph.select=Sélectionner les branches commit_graph.hide_pr_refs=Masquer les demandes d'ajout commit_graph.monochrome=Monochrome +commit_graph.color=Couleur blame=Annotations +download_file=Télécharger le fichier normal_view=Vue normale line=ligne lines=lignes @@ -977,6 +1038,7 @@ editor.require_signed_commit=Cette branche nécessite une révision signée commits.desc=Naviguer dans l'historique des modifications. commits.commits=Révisions commits.no_commits=Pas de révisions en commun. '%s' et '%s' ont des historiques entièrement différents. +commits.nothing_to_compare=Ces branches sont égales. commits.search=Rechercher des révisions… commits.search.tooltip=Vous pouvez préfixer les mots-clés avec "author:", "committer:", "after:", ou "before:", par exemple "revert author:Alice before:2019-04-01". commits.find=Chercher @@ -1025,6 +1087,7 @@ projects.board.set_default=Définir par défaut projects.board.set_default_desc=Définir ce tableau comme valeur par défaut pour les tickets et demande d'ajouts non catégorisés projects.board.delete=Supprimer le tableau projects.board.deletion_desc=La suppression d'un tableau de projet déplace tous les tickets liés dans 'Non catégorisé'. Voulez vous continuer ? +projects.board.color=Couleur projects.open=Ouvrir projects.close=Fermer @@ -1126,6 +1189,7 @@ issues.action_milestone_no_select=Aucun jalon issues.action_assignee=Assigné à issues.action_assignee_no_select=Pas d'assignataire issues.opened_by=créé %[1]s par <a href="%[2]s">%[3]s</a> +issues.opened_by_fake=%[1]s ouvert par %[2]s issues.previous=Page Précédente issues.next=Page Suivante issues.open_title=Ouvert @@ -1254,6 +1318,8 @@ issues.dependency.remove=Supprimer issues.dependency.remove_info=Supprimer cette dépendance issues.dependency.added_dependency=`a ajouté une nouvelle dépendance %s` issues.dependency.removed_dependency=`a supprimé une dépendance %s` +issues.dependency.pr_closing_blockedby=La fermeture de cette demande d’ajout est bloquée par les tickets suivants +issues.dependency.issue_closing_blockedby=La fermeture de ce ticket est bloquée par les tickets suivants issues.dependency.issue_close_blocks=Cette demande d'ajout empêche la clôture des tickets suivants issues.dependency.pr_close_blocks=Cette demande d'ajout empêche la clôture des tickets suivants issues.dependency.issue_close_blocked=Vous devez fermer tous les tickets qui bloquent ce ticket avant de pouvoir le fermer. @@ -1296,6 +1362,11 @@ issues.review.un_resolve_conversation=Conversation non résolue issues.review.resolved_by=marquer cette conversation comme résolue issues.assignee.error=Tous les assignés n'ont pas été ajoutés en raison d'une erreur inattendue. issues.reference_issue.body=Corps +issues.content_history.edited=édité +issues.content_history.created=créé +issues.content_history.delete_from_history=Supprimé de l’historique +issues.content_history.delete_from_history_confirm=Supprimer de l’historique ? +issues.content_history.options=Options compare.compare_base=base compare.compare_head=comparer @@ -1306,6 +1377,7 @@ pulls.compare_changes=Nouvelle demande de fusion pulls.compare_changes_desc=Sélectionnez la branche dans laquelle fusionner et la branche depuis laquelle tirer les modifications. pulls.compare_base=fusionner dans pulls.compare_compare=tirer les modifications depuis +pulls.switch_comparison_type=Changer le type de comparaison pulls.filter_branch=Filtre de branche pulls.no_results=Aucun résultat trouvé. pulls.nothing_to_compare=Ces branches sont identiques. Il n'y a pas besoin de créer une demande de fusion. @@ -1326,6 +1398,9 @@ pulls.manually_merged_as=La demande d'ajout a été fusionnée manuellement en t pulls.is_closed=La demande de fusion a été fermée. pulls.has_merged=La pull request a été fusionnée. pulls.title_wip_desc=`<a href="#">Préfixer le titre par <strong>%s</strong></a> pour empêcher cette demande d'ajout d'être fusionnée par erreur.` +pulls.cannot_merge_work_in_progress=Cette demande d'ajout est marquée comme en cours de chantier. +pulls.still_in_progress=Toujours en cours ? +pulls.add_prefix=Ajouter le préfixe <strong>%s</strong> pulls.remove_prefix=Enlever le préfixe <strong>%s</strong> pulls.data_broken=Cette demande de fusion est impossible par manque d'informations de bifurcation. pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblée. @@ -1545,7 +1620,9 @@ settings.hooks=Déclencheurs Web settings.githooks=Déclencheurs Git settings.basic_settings=Paramètres de base settings.mirror_settings=Réglages Miroir +settings.mirror_settings.mirrored_repository=Dépôt en miroir settings.mirror_settings.direction=Direction +settings.mirror_settings.direction.push=Pousser settings.mirror_settings.last_update=Dernière mise à jour settings.mirror_settings.push_mirror.remote_url=URL du dépôt distant Git settings.sync_mirror=Synchroniser maintenant @@ -1586,6 +1663,7 @@ settings.pulls.allow_rebase_merge_commit=Activer le rebasage avec un commit de f settings.pulls.allow_squash_commits=Activer la concaténation de révisions settings.pulls.allow_manual_merge=Activer le marquage des demandes d'ajout comme fusionnées manuellement settings.pulls.enable_autodetect_manual_merge=Activer la détection automatique de la fusion manuelle (Remarque : dans certains cas particuliers, des erreurs de détection peuvent se produire) +settings.pulls.default_delete_branch_after_merge=Supprimer la branche après la fusion par default settings.projects_desc=Activer les projets de dépôt settings.admin_settings=Paramètres administrateur settings.admin_enable_health_check=Activer les vérifications de santé du dépôt (git fsck) @@ -1613,6 +1691,7 @@ settings.transfer_form_title=Entrez le nom du dépôt pour confirmer : settings.transfer_in_progress=Il y a actuellement un transfert en cours. Veuillez l'annuler si vous souhaitez transférer ce dépôt à un autre utilisateur. settings.transfer_notices_1=- Vous perdrez l'accès à ce dépôt si vous le transférez à un autre utilisateur. settings.transfer_notices_2=- Vous conserverez l'accès à ce dépôt si vous le transférez à une organisation dont vous êtes (co-)propriétaire. +settings.transfer_notices_3=- Si le dépôt est privé et est transféré à un utilisateur individuel, cette action s'assure que l'utilisateur a au moins la permission de lire (et modifie les permissions si nécessaire). settings.transfer_owner=Nouveau propriétaire settings.transfer_perform=Effectuer le transfert settings.transfer_started=Ce dépôt a été marqué pour le transfert et attend la confirmation de "%s" @@ -1885,6 +1964,7 @@ diff.file_image_width=Largeur diff.file_image_height=Hauteur diff.file_byte_size=Taille diff.file_suppressed=Fichier diff supprimé car celui-ci est trop grand +diff.file_suppressed_line_too_long=Diff de fichier supprimé car une ou plusieurs lignes sont trop longues diff.comment.placeholder=Laisser un commentaire diff.comment.markdown_info=Mise en page avec markdown est prise en charge. diff.comment.add_single_comment=Ajouter un commentaire @@ -1911,6 +1991,7 @@ release.new_release=Nouvelle version release.draft=Brouillon release.prerelease=Pré-publication release.stable=Stable +release.compare=Comparer release.edit=Éditer release.ahead.target=à %s depuis cette livraison release.source_code=Code source @@ -1963,6 +2044,10 @@ branch.restore=Restaurer la branche '%s' branch.download=Télécharger la branche '%s' branch.included_desc=Cette branche fait partie de la branche par défaut branch.included=Incluses +branch.create_new_branch=Créer une branche à partir de la branche : +branch.confirm_create_branch=Créer une branche +branch.new_branch=Créer une nouvelle branche +branch.new_branch_from=Créer une nouvelle branche à partir de '%s' @@ -2009,11 +2094,14 @@ settings.repoadminchangeteam=L'administrateur de dépôt peut ajouter et supprim settings.visibility=Visibilité settings.visibility.public=Public settings.visibility.limited=Limité (Visible uniquement aux utilisateurs connectés) +settings.visibility.limited_shortname=Limité settings.visibility.private=Privé (Visible uniquement aux membres de l’organisation) +settings.visibility.private_shortname=Privé settings.update_settings=Valider settings.update_setting_success=Les paramètres de l'organisation ont été mis à jour. settings.change_orgname_prompt=NB: changer le nom de l'organisation changera aussi son URL. +settings.change_orgname_redirect_prompt=L'ancien nom d'utilisateur redirigera jusqu'à ce qu'il soit réclamé. settings.update_avatar_success=L'avatar de l'organisation a été mis à jour. settings.delete=Supprimer l'organisation settings.delete_account=Supprimer cette organisation @@ -2034,12 +2122,15 @@ members.member_role=Rôle du membre : members.owner=Propriétaire members.member=Membre members.remove=Exclure +members.remove.detail=Supprimer %[1]s de %[2]s? members.leave=Quitter +members.leave.detail=Quitter %s? members.invite_desc=Ajouter un nouveau membre à %s : members.invite_now=Envoyer une invitation teams.join=Rejoindre teams.leave=Quitter +teams.leave.detail=Quitter %s? teams.can_create_org_repo=Créer des dépôts teams.can_create_org_repo_helper=Les membres peuvent créer de nouveaux dépôts dans l'organisation. Le créateur obtiendra l'accès administrateur au nouveau dépôt. teams.read_access_helper=Les membres peuvent voir et cloner les dépôts de l'équipe. @@ -2100,15 +2191,31 @@ dashboard.operation_switch=Basculer dashboard.operation_run=Exécuter dashboard.clean_unbind_oauth=Effacer les connexions OAuth associées dashboard.clean_unbind_oauth_success=Toutes les connexions OAuth associées ont été supprimées. +dashboard.task.started=Tâche démarrée: %[1]s +dashboard.task.process=Tâche: %[1]s +dashboard.task.cancelled=Tâche: %[1]s a annulé: %[3]s +dashboard.task.error=Erreur dans la tâche: %[1]s: %[3]s +dashboard.task.finished=Tâche: %[1]s démarrée par %[2]s est terminée +dashboard.task.unknown=Tâche inconnue: %[1]s +dashboard.cron.process=Tâche planifiée: %[1]s +dashboard.cron.cancelled=Tâche planifiée : %s annulée : %[3]s +dashboard.cron.error=Erreur dans la tâche planifiée : %s: %[3]s dashboard.cron.finished=Tâche planifiée : %[1]s a terminé +dashboard.delete_inactive_accounts=Supprimer tous les comptes non actifs +dashboard.delete_inactive_accounts.started=Tâche de suppression de tous les comptes inactifs démarrée. dashboard.delete_repo_archives=Supprimer toutes les archives des dépôts (ZIP, TAR.GZ, etc..) dashboard.delete_repo_archives.started=Tâche de suppression de toutes les archives de dépôts démarrée. dashboard.delete_missing_repos=Supprimer tous les dépôts dont les fichiers Git sont manquants +dashboard.delete_missing_repos.started=Tâche de suppression de tous les dépôts sans fichiers Git démarrée. dashboard.delete_generated_repository_avatars=Supprimer les avatars de dépôt générés +dashboard.update_mirrors=Mettre à jour les miroirs dashboard.repo_health_check=Vérifier l'état de santé de tous les dépôts dashboard.check_repo_stats=Voir les statistiques de tous les dépôts dashboard.archive_cleanup=Supprimer les archives des vieux dépôts +dashboard.deleted_branches_cleanup=Nettoyer les branches supprimées dashboard.git_gc_repos=Collecter les déchets des dépôts +dashboard.resync_all_sshkeys.desc=(Inutile pour le serveur SSH intégré.) +dashboard.resync_all_sshprincipals.desc=(Inutile pour le serveur SSH intégré.) dashboard.resync_all_hooks=Re-synchroniser les déclencheurs Git pre-receive, update et post-receive de tous les dépôts. dashboard.reinit_missing_repos=Réinitialiser tous les dépôts Git manquants pour lesquels un enregistrement existe dashboard.sync_external_users=Synchroniser les données de l’utilisateur externe @@ -2256,6 +2363,8 @@ auths.smtpport=Port SMTP auths.allowed_domains=Domaines autorisés auths.allowed_domains_helper=Laisser ce champ vide autorise tous les domaines. Separez les domaines multiples avec une virgule (","). auths.skip_tls_verify=Ne pas vérifier TLS +auths.force_smtps=Forcer SMTPS +auths.helo_hostname=Nom d’hôte HELO auths.pam_service_name=Nom du Service PAM auths.oauth2_provider=Fournisseur OAuth2 auths.oauth2_clientID=ID du client (clé) @@ -2293,6 +2402,7 @@ auths.tip.twitter=Rendez-vous sur https://dev.twitter.com/apps, créez une appli auths.tip.discord=Enregistrer une nouvelle application sur https://discordapp.com/developers/applications/me auths.tip.gitea=Enregistrez une nouvelle application OAuth2. Un guide peut être trouvé sur https://docs.gitea.io/en-us/oauth2-provider/ auths.tip.yandex=Créez une nouvelle application sur https://oauth.yandex.com/client/new. Sélectionnez les autorisations suivantes dans la section "Yandex API passport" : "Accès à l'adresse e-mail", "Accès à l'avatar de l'utilisateur" et "Accès au nom d'utilisateur, prénom et prénom, genre" +auths.tip.mastodon=Entrez une URL d'instance personnalisée pour l'instance mastodon avec laquelle vous voulez vous authentifier (ou utiliser celle par défaut) auths.edit=Mettre à jour la source d'authentification auths.activated=Cette source d'authentification est activée auths.new_success=L'authentification "%s" a été ajoutée. @@ -2352,6 +2462,7 @@ config.db_path=Emplacement config.service_config=Configuration du service config.register_email_confirm=Exiger la confirmation de l'e-mail lors de l'inscription config.disable_register=Désactiver le formulaire d'inscription +config.allow_only_internal_registration=Autoriser l'inscription uniquement via Gitea lui-même config.allow_only_external_registration=N'autoriser l'inscription qu'à partir des services externes config.enable_openid_signup=Activer l'inscription avec OpenID config.enable_openid_signin=Activer la connexion avec OpenID diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 52f276773..0f66edcab 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -2,6 +2,7 @@ home=Sākums dashboard=Infopanelis explore=Izpētīt help=Palīdzība +logo=Logo sign_in=Pierakstīties sign_in_with=Pierakstīties izmantojot sign_out=Izrakstīties @@ -105,6 +106,7 @@ error404=Lapa, ko vēlaties atvērt, <strong>neeksistē</strong> vai arī <stron never=Nekad +rss_feed=RSS barotne [error] occurred=Radusies kļūda @@ -269,6 +271,7 @@ search=Meklēt code=Kods search.fuzzy=Aptuveni search.match=Precīzi +code_search_unavailable=Pašlaik koda meklēšana nav pieejama. Sazinieties ar lapas administratoru. repo_no_results=Netika atrasts neviens repozitorijs, kas atbilstu kritērijiem. user_no_results=Netika atrasts neviens lietotājs, kas atbilstu kritērijiem. org_no_results=Netika atrasta neviena organizācija, kas atbilstu kritērijiem. @@ -282,6 +285,7 @@ register_helper_msg=Jau ir konts? Pieraksties tagad! social_register_helper_msg=Jau ir konts? Pievienojies! disable_register_prompt=Reģistrācija ir atspējota. Lūdzu, sazinieties ar vietnes administratoru. disable_register_mail=Reģistrācijas e-pasta apstiprināšana ir atspējota. +manual_activation_only=Sazinieties ar lapas administratoru, lai pabeigtu konta aktivizāciju. remember_me=Atcerēties šo ierīci forgot_password_title=Aizmirsu paroli forgot_password=Aizmirsi paroli? @@ -439,6 +443,7 @@ size_error=` jābūt %s simbolus garam.` min_size_error=` jabūt vismaz %s simbolu garumā.` max_size_error=` jabūt ne mazāk kā %s simbolu garumā.` email_error=` nav derīga e-pasta adrese.` +url_error=`'%s' nav korekts URL.` include_error=` ir jāsatur tekstu '%s'.` glob_pattern_error=` glob šablons nav korekts: %s.` regex_pattern_error=` regulārā izteiksme nav korekta: %s.` @@ -486,7 +491,9 @@ auth_failed=Autentifikācija neizdevās: %v still_own_repo=Šis konts ir vismaz viena repozitorija īpašnieks, tos sākumā ir nepieciešams izdzēst vai mainīt to īpašnieku. still_has_org=Jūsu konts ir piesaistīts vismaz vienai organizācijai, sākumā nepieciešams to pamest. +still_own_packages=Jūsu kontam pieder viena vai vairākas pakotnes, tās nepieciešams izdzēst. org_still_own_repo=Organizācijai pieder repozitoriji, tos sākumā ir nepieciešams izdzēst vai mainīt to īpašnieku. +org_still_own_packages=Šai organizācijai pieder viena vai vārākas pakotnes, tās nepieciešams izdzēst. target_branch_not_exist=Mērķa atzars neeksistē @@ -549,6 +556,22 @@ continue=Turpināt cancel=Atcelt language=Valoda ui=Motīvs +hidden_comment_types=Attēlojot paslēpt šauds komentārus: +comment_type_group_reference=Atsauces +comment_type_group_label=Etiķetes +comment_type_group_milestone=Atskaites punktus +comment_type_group_assignee=Atbildīgos +comment_type_group_title=Nosaukuma izmaiņas +comment_type_group_branch=Atzara izmaiņas +comment_type_group_time_tracking=Laika uzskaiti +comment_type_group_deadline=Termiņus +comment_type_group_dependency=Atkarības +comment_type_group_lock=Slēgšanas maiņu +comment_type_group_review_request=Izmaiņu pieprasījumus +comment_type_group_pull_request_push=Pievienotās revīzijas +comment_type_group_project=Projektus +comment_type_group_issue_ref=Problēmu atsauces +saved_successfully=Iestatījumi tika veiksmīgi saglabati. privacy=Privātums keep_activity_private=Nerādīt manu aktivitāti profila lapā keep_activity_private_popup=Savu aktivitāti redzēsiet tikai Jūs un administratori @@ -694,6 +717,9 @@ generate_token_success=Piekļuves talons tika veiksmīgi uzģenerēts! Nokopēji generate_token_name_duplicate=Jau eksistē lietotne ar nosaukumu <strong>%s</strong>. Izmantojiet citu nosaukumu. delete_token=Dzēst access_token_deletion=Dzēst piekļuves talonu +access_token_deletion_cancel_action=Atcelt +access_token_deletion_confirm_action=Dzēst +access_token_deletion_desc=Izdzēšot talonu, tam tiks liegta piekļuve šim kontam. Šī darbība ir neatgriezeniska. Vai turpināt? delete_token_success=Piekļuves talons tika noņemts. Neaizmirstiet atjaunot informāciju lietojumprogrammās, kas izmantoja šo talonu. manage_oauth2_applications=Pārvaldīt OAuth2 lietotnes @@ -755,6 +781,7 @@ webauthn_delete_key_desc=Noņemot drošības atslēgu ar to vairs nebūs iespēj manage_account_links=Pārvaldīt saistītos kontus manage_account_links_desc=Šādi ārējie konti ir piesaistīti Jūsu Gitea kontam. account_links_not_available=Pašlaik nav neviena ārējā konta piesaistīta šim kontam. +link_account=Savienot kontu remove_account_link=Noņemt saistīto kontu remove_account_link_desc=Noņemot saistīto kontu, tam tiks liegta piekļuve Jūsu Gitea kontam. Vai turpināt? remove_account_link_success=Saistītais konts tika noņemts. @@ -835,6 +862,7 @@ default_branch=Noklusējuma atzars default_branch_helper=Noklusētais atzars nosaka pamata atzaru uz kuru tiks veidoti izmaiņu pieprasījumi un koda revīziju iesūtīšana. mirror_prune=Izmest mirror_prune_desc=Izdzēst visas ārējās atsauces, kas ārējā repozitorijā vairs neeksistē +mirror_interval=Spoguļošanas intervāls (derīgas laika vienības ir 'h', 'm', 's'). Norādiet 0, lai atslēgtu automātisku spoguļošanu. (Minimālais intervāls: %s) mirror_interval_invalid=Nekorekts spoguļošanas intervāls. mirror_address=Spoguļa adrese mirror_address_desc=Pieslēgšanās rekvizītus norādiet autorizācijas sadaļā. @@ -986,6 +1014,7 @@ tags=Tagi issues=Problēmas pulls=Izmaiņu pieprasījumi project_board=Projekti +packages=Pakotnes labels=Etiķetes org_labels_desc=Organizācijas līmeņa etiķetes var tikt izmantotas <strong>visiem repozitorijiem</strong> šajā organizācijā org_labels_desc_manage=pārvaldīt @@ -1016,6 +1045,7 @@ line_unicode=`Šajā līnijā ir paslēpti unikoda simboli` escape_control_characters=Kodēt unescape_control_characters=Atkodēt file_copy_permalink=Kopēt saiti +view_git_blame=Aplūkot Git vainīgos video_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 video. audio_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 audio. stored_lfs=Saglabāts Git LFS @@ -1054,6 +1084,10 @@ editor.add_tmpl=Pievienot '<fails>' editor.add=Pievienot '%s' editor.update=Atjaunināt '%s' editor.delete=Dzēst '%s' +editor.patch=Pielietot ielāpu +editor.patching=Pielieto ielāpu: +editor.fail_to_apply_patch=Neizdevās pielietot ielāpu '%s' +editor.new_patch=Jauns ielāps editor.commit_message_desc=Pievienot neobligātu paplašinātu aprakstu… editor.signoff_desc=Pievienot revīzijas žurnāla ziņojuma beigās Signed-off-by ar revīzijas autoru. editor.commit_directly_to_this_branch=Apstiprināt revīzijas izmaiņas atzarā <strong class="branch-name">%s</strong>. @@ -1089,6 +1123,8 @@ editor.cannot_commit_to_protected_branch=Nav atļauts veikt izmaiņas aizsargāt editor.no_commit_to_branch=Nevar apstiprināt revīzijas atzarā: editor.user_no_push_to_branch=Lietotājs nevar iesūtīt izmaiņas šajā atzarā editor.require_signed_commit=Atzarā var iesūtīt tikai parakstītas revīzijas +editor.cherry_pick=Izlasīt %s uz: +editor.revert=Atgriezt %s uz: commits.desc=Pārlūkot pirmkoda izmaiņu vēsturi. commits.commits=Revīzijas @@ -1109,6 +1145,13 @@ commits.signed_by_untrusted_user_unmatched=Parakstījis neuzticams lietotājs, k commits.gpg_key_id=GPG atslēgas ID commits.ssh_key_fingerprint=SSH atslēgas identificējošā zīmju virkne +commit.actions=Darbības +commit.revert=Atgriezt +commit.revert-header=Atgriezt: %s +commit.revert-content=Norādiet atzaru uz kuru atgriezt: +commit.cherry-pick=Izlasīt +commit.cherry-pick-header=Izlasīt: %s +commit.cherry-pick-content=Norādiet atzaru uz kuru izlasīt: ext_issues=Piekļuve ārējām problēmām ext_issues.desc=Saite uz ārējo problēmu sekotāju. @@ -1147,6 +1190,7 @@ projects.board.deletion_desc=Dzēšot projekta dēli visas tam piesaistītās pr projects.board.color=Krāsa projects.open=Aktīvie projects.close=Pabeigtie +projects.board.assigned_to=Piešķirts issues.desc=Organizēt kļūdu ziņojumus, uzdevumus un atskaites punktus. issues.filter_assignees=Filtrēt pēc atbildīgajiem @@ -1241,6 +1285,7 @@ issues.filter_sort.moststars=Visvairāk atzīmētie issues.filter_sort.feweststars=Vismazāk atzīmētie issues.filter_sort.mostforks=Visvairāk atdalītie issues.filter_sort.fewestforks=Vismazāk atdalītie +issues.keyword_search_unavailable=Meklēšana pēc atslēgas vārdiem nav pieejama. Sazinieties ar sistēmas administratoru. issues.action_open=Atvērt issues.action_close=Aizvērt issues.action_label=Etiķete @@ -1336,6 +1381,9 @@ issues.lock.reason=Slēgšanas iemesls issues.lock.title=Slēgt komentēšanu šai problēmai. issues.unlock.title=Atļaut komentēšanu šai problēmai. issues.comment_on_locked=Jūs nevarat komentēt slēgtai problēmai. +issues.delete=Dzēst +issues.delete.title=Dzēst šo problēmu? +issues.delete.text=Vai patiešām vēlaties dzēst šo problemu? (Neatgriezeniski tiks izdzēsts viss saturs. Apsveriet iespēju to aizvērt, ja vēlaties informāciju saglabāt vēsturei) issues.tracker=Laika uzskaite issues.start_tracking_short=Uzsākt taimeri issues.start_tracking=Uzsākt laika uzskaiti @@ -1376,6 +1424,8 @@ issues.due_date_remove=noņēma izpildes termiņu %s %s issues.due_date_overdue=Nokavēts issues.due_date_invalid=Datums līdz nav korekts. Izmantojiet formātu 'gggg-mm-dd'. issues.dependency.title=Atkarības +issues.dependency.issue_no_dependencies=Nav atkarību. +issues.dependency.pr_no_dependencies=Nav atkarību. issues.dependency.add=Pievienot atkarību… issues.dependency.cancel=Atcelt issues.dependency.remove=Noņemt @@ -1414,6 +1464,7 @@ issues.review.add_review_request=pieprasīja recenziju no %s %s issues.review.remove_review_request=noņema recenzijas pieprasījumu no %s %s issues.review.remove_review_request_self=atteicās recenzēt %s issues.review.pending=Nav iesūtīts +issues.review.pending.tooltip=Šis komentārs nav redzams citiem lietotājiem. Lai padarītu neiesūtītos komentārus pieejamus citiem, nospiediet '%s' -> '%s/%s/%s' lapas augšpusē. issues.review.review=Recenzija issues.review.reviewers=Recenzenti issues.review.outdated=Novecojis @@ -1432,6 +1483,7 @@ issues.content_history.created=izveidots issues.content_history.delete_from_history=Dzēst no vēstures issues.content_history.delete_from_history_confirm=Vai dzēst no vēstures? issues.content_history.options=Iespējas +issues.reference_link=Atsaucas uz: %s compare.compare_base=pamata compare.compare_head=salīdzināt @@ -1440,7 +1492,13 @@ pulls.desc=Iespējot izmaiņu pieprasījumus un koda recenzēšanu. pulls.new=Jauns izmaiņu pieprasījums pulls.view=Skatīties izmaiņu pieprasījumu pulls.compare_changes=Jauns izmaiņu pieprasījums +pulls.allow_edits_from_maintainers=Atļaut labojumus no uzturētājiem +pulls.allow_edits_from_maintainers_desc=Lietotāji ar rakstīšanas tiesībām bāzes atzarā, drīkst iesūtīt izmaiņas šajā atzarā +pulls.allow_edits_from_maintainers_err=Atjaunošana neizdevās pulls.compare_changes_desc=Izvēlieties atzaru, kurā sapludināt izmaiņas un atzaru, no kura tās saņemt. +pulls.has_viewed_file=Skatīts +pulls.has_changed_since_last_review=Mainīts kopš pēdējās recenzijas +pulls.viewed_files_label=%[1]d no %[2]d failiem apskatīts pulls.compare_base=pamata pulls.compare_compare=salīdzināmais pulls.switch_comparison_type=Mainīt salīdzināšanas tipu @@ -1540,8 +1598,17 @@ pulls.merge_instruction_hint=`Varat aplūkot arī <a class="show-instruction">ko pulls.merge_instruction_step1_desc=Projekta repozitorijā izveidojiet jaunu jaunu atzaru un pārbaudiet savas izmaiņas. pulls.merge_instruction_step2_desc=Sapludināt izmaiņas un atjaunot tās Gitea. +pulls.auto_merge_button_when_succeed=(Kad pārbaudes veiksmīgas) +pulls.auto_merge_when_succeed=Automātiski sapludināt, kad visas pārbaudes veiksmīgas +pulls.auto_merge_newly_scheduled=Šis izmaiņu pieprasījums tika ieplānots automātiskajai sapludināšanai, kas visas pārbaudes būs veiksmīgas. +pulls.auto_merge_has_pending_schedule=%[1]s ieplānoja šī izmaiņu pieprasījuma automātisko sapludināšanu, kad visas pārbaudes tiks pabeigtas %[2]s. +pulls.auto_merge_cancel_schedule=Atcelt automātisko sapludināšanu +pulls.auto_merge_not_scheduled=Šo izmaiņu pieprasījumu nav ieplānots automātiski sapludināt. +pulls.auto_merge_canceled_schedule=Automātiskā sapludināšana šim izmaiņu pieprasījumam tika atcelta. +pulls.auto_merge_newly_scheduled_comment=`ieplānoja automātisko sapludināšanu šim izmaiņu pieprasījumam, kad visas pārbaudes būs veiksmīgas %[1]s` +pulls.auto_merge_canceled_schedule_comment=`atcēla automātisko sapludināšanu šim izmaiņu pieprasījumam %[1]s` milestones.new=Jauns atskaites punkts milestones.closed=Aizvērts %s @@ -1685,6 +1752,8 @@ search.search_repo=Meklēšana repozitorijā search.fuzzy=Aptuveni search.match=Precīzi search.results=Meklēšanas rezultāti nosacījumam "%s" repozitorijā <a href="%s">%s</a> +search.code_no_results=Netika atrasts pirmkods, kas atbilstu kritērijiem. +search.code_search_unavailable=Pašlaik koda meklēšana nav pieejama. Sazinieties ar lapas administratoru. settings=Iestatījumi settings.desc=Iestatījumi ir vieta, kur varat pārvaldīt repozitorija iestatījumus @@ -1735,6 +1804,9 @@ settings.tracker_url_format_error=Ārējā problēmu sekotāja URL formāts nav settings.tracker_issue_style=Ārējā problēmu sekotāja numura formāts settings.tracker_issue_style.numeric=Cipari settings.tracker_issue_style.alphanumeric=Burti un cipari +settings.tracker_issue_style.regexp=Regulārā izteiksme +settings.tracker_issue_style.regexp_pattern=Regulārās izteiksmes šablons +settings.tracker_issue_style.regexp_pattern_desc=Pirmā iegultā grupa tiks izmantota <code>{index}</code> vietā. settings.tracker_url_format_desc=Jūs varat izmantot <code>{user}</code>, <code>{repo}</code> un <code>{index}</code> lietotājvārdam, repozitorija nosaukumam un problēmas identifikatoram. settings.enable_timetracker=Iespējot laika uzskaiti settings.allow_only_contributors_to_track_time=Atļaut tikai dalībniekiem uzskaitīt laiku @@ -1746,7 +1818,9 @@ settings.pulls.allow_rebase_merge_commit=Iespējot pārbāzēšanu sapludinot re settings.pulls.allow_squash_commits=Iespējot saspiešanu sapludinot revīzijas settings.pulls.allow_manual_merge=Iespējot atzīmēt izmaiņu pieprasījumu kā manuāli sapludinātu settings.pulls.enable_autodetect_manual_merge=Iespējot manuālo sapludināšanas noteikšanu (Piezīme: dažos speciālos gadījumos, tas var nostrādāt nekorekti) +settings.pulls.allow_rebase_update=Iespējot izmaiņu pieprasījuma atjaunošanu ar pārbāzēšanu settings.pulls.default_delete_branch_after_merge=Pēc noklusējuma dzēst izmaiņu pieprasījuma atzaru pēc sapludināšanas +settings.packages_desc=Iespējot repozitorija pakotņu reģistru settings.projects_desc=Iespējot repozitorija projektus settings.admin_settings=Administratora iestatījumi settings.admin_enable_health_check=Iespējot veselības pārbaudi (git fsck) šim repozitorijam @@ -1904,6 +1978,8 @@ settings.event_pull_request_review=Izmaiņu pieprasījums recenzēts settings.event_pull_request_review_desc=Izmaiņu pieprasījums apstiprināts, noraidīts vai atstāts komentārs. settings.event_pull_request_sync=Izmaiņu pieprasījums sinhronizēts settings.event_pull_request_sync_desc=Izmaiņu pieprasījums sinhronizēts. +settings.event_package=Pakotne +settings.event_package_desc=Repozitorijā izveidota vai dzēsta pakotne. settings.branch_filter=Atzaru filtrs settings.branch_filter_desc=Atzaru ierobežojumi izmaiņu iesūtīšanas, zaru izveidošanas vai dzēšanas notikumiem, izmantojot, glob šablonu. Ja norādīts tukšs vai <code>*</code>, tiks nosūtīti notikumi no visiem zariem. Skatieties <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> pieraksta dokumentāciju. Piemērs: <code>master</code>, <code>{master,release*}</code>. settings.active=Aktīvs @@ -1917,6 +1993,23 @@ settings.hook_type=Āķa veids settings.slack_token=Talons settings.slack_domain=Domēns settings.slack_channel=Kanāls +settings.add_web_hook_desc=Integrēt <a target="_blank" rel="noreferrer" href="%s">%s</a> repozitorijā. +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_telegram=Telegram +settings.web_hook_name_matrix=Matrix +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite +settings.web_hook_name_feishu=Feishu +settings.web_hook_name_larksuite=Lark Suite +settings.web_hook_name_wechatwork=WeCom (Wechat Work) +settings.web_hook_name_packagist=Packagist +settings.packagist_username=Packagist lietotāja vārds +settings.packagist_api_token=API talons +settings.packagist_package_url=Packagist pakotnes URL settings.deploy_keys=Izvietot atslēgas settings.add_deploy_key=Pievienot izvietošanas atslēgu settings.deploy_key_desc=Izvietošanas atslēgām ir lasīšanas piekļuve repozitorijam. @@ -2172,11 +2265,15 @@ branch.included_desc=Šis atzars ir daļa no noklusēta atzara branch.included=Iekļauts branch.create_new_branch=Izveidot jaunu atzaru no atzara: branch.confirm_create_branch=Izveidot atzaru +branch.create_branch_operation=Izveidot atzaru branch.new_branch=Izveidot jaunu atzaru branch.new_branch_from=Izveidot jaunu atzaru no '%s' branch.renamed=Atzars %s tika pārsaukts par %s. tag.create_tag=Izveidot tagu <strong>%s</strong> +tag.create_tag_operation=Izveidot tagu +tag.confirm_create_tag=Izveidot tagu +tag.create_tag_from=Izveidot tagu no '%s' tag.create_success=Tags '%s' tika izveidots. @@ -2185,6 +2282,8 @@ topic.done=Gatavs topic.count_prompt=Nevar pievienot vairāk kā 25 tēmas topic.format_prompt=Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara. +find_file.go_to_file=Iet uz failu +find_file.no_matching=Atbilstošs fails netika atrasts error.csv.too_large=Nevar attēlot šo failu, jo tas ir pārāk liels. error.csv.unexpected=Nevar attēlot šo failu, jo tas satur neparedzētu simbolu %d. līnijas %d. kolonnā. @@ -2322,9 +2421,11 @@ first_page=Pirmā last_page=Pēdējā total=Kopā: %d +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.statistic_info=Gitea datu bāze satur <b>%d</b> lietotājus, <b>%d</b> organizācijas, <b>%d</b> publiskās atslēgas, <b>%d</b> repozitorijus, <b>%d</b> vērošanas, <b>%d</b> atzīmētas zvaigznītes, ~<b>%d</b> darbības, <b>%d</b> piekļuves, <b>%d</b> problēmas, <b>%d</b> komentārus, <b>%d</b> sociālos kontus, <b>%d</b> sekošanas, <b>%d</b> spoguļošanas, <b>%d</b> izlaides, <b>%d</b> autorizācijas avotus, <b>%d</b> tīmekļa āķus, <b>%d</b> starpposmus, <b>%d</b> etiķetes, <b>%d</b> āķu uzdevumus, <b>%d</b> komandas, <b>%d</b> labotus uzdevumus, <b>%d</b> pielikumus. dashboard.operation_name=Darbības nosaukums dashboard.operation_switch=Pārslēgt dashboard.operation_run=Palaist @@ -2363,6 +2464,7 @@ dashboard.resync_all_hooks=Pārsinhronizēt pirms-saņemšanas, atjaunošanas un dashboard.reinit_missing_repos=Atkārtoti inicializēt visus pazaudētos Git repozitorijus par kuriem eksistē ieraksti dashboard.sync_external_users=Sinhronizēt ārējo lietotāju datus dashboard.cleanup_hook_task_table=Iztīrīt tīmekļa āķu vēsturi +dashboard.cleanup_packages=Notīrīt novecojušās pakotnes dashboard.server_uptime=Servera darbības laiks dashboard.current_goroutine=Izmantotās Gorutīnas dashboard.current_memory_usage=Pašreiz izmantotā atmiņa @@ -2394,6 +2496,8 @@ dashboard.last_gc_pause=Pedējās GC izpildes laiks dashboard.gc_times=GC reizes dashboard.delete_old_actions=Dzēst visas darbības no datu bāzes dashboard.delete_old_actions.started=Uzsākta visu novecojušo darbību dzēšana no datu bāzes. +dashboard.update_checker=Atjauninājumu pārbaudītājs +dashboard.delete_old_system_notices=Dzēst vecos sistēmas paziņojumus no datubāzes users.user_manage_panel=Lietotāju kontu pārvaldība users.new_account=Izveidot lietotāja kontu @@ -2428,8 +2532,10 @@ users.allow_import_local=Atļauts importēt lokālus repozitorijus users.allow_create_organization=Atļauts veidot organizācijas users.update_profile=Mainīt lietotāja kontu users.delete_account=Dzēst lietotāja kontu +users.cannot_delete_self=Nevar izdzēst sevi users.still_own_repo=Lietotājam pieder repozitoriji, tos sākumā ir nepieciešams izdzēst vai mainīt to īpašnieku. users.still_has_org=Šis lietotājs ir vienas vai vairāku organizāciju biedrs, lietotāju sākumā ir nepieciešams pamest šīs organizācijas vai viņu no tām ir jāizdzēš. +users.still_own_packages=Šim lietotājam pieder viena vai vairākas pakotnes. Tās nepieciešams izdzēst. users.deletion_success=Lietotāja konts veiksmīgi izdzēsts. users.reset_2fa=Noņemt 2FA users.list_status_filter.menu_text=Filtrs @@ -2476,6 +2582,16 @@ repos.forks=Atdalītie repos.issues=Problēmas repos.size=Izmērs +packages.package_manage_panel=Pakotņu pārvaldība +packages.total_size=Kopējais izmērs: %s +packages.owner=Īpašnieks +packages.creator=Izveidotājs +packages.name=Nosaukums +packages.version=Versija +packages.type=Veids +packages.repository=Repozitorijs +packages.size=Izmērs +packages.published=Publicēts defaulthooks=Noklusētie tīmekļa āķi defaulthooks.desc=Tīmekļa āķi ļauj paziņot ārējiem servisiem par noteiktiem notikumiem, kas notiek Gitea. Kad iestāsies kāds notikums, katram ārējā servisa URL tiks nosūtīts POST pieprasījums. Šeit izveidotie tīmekļa āķi tiks pievienoti visiem jaunajajiem repozitorijiem. Lai uzzinātu sīkāk skatieties <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">tīmekļa āķu rokasgrāmatā</a>. @@ -2519,9 +2635,13 @@ auths.filter=Lietotāju filts auths.admin_filter=Administratoru filtrs auths.restricted_filter=Ierobežoto lietotāju filtrs auths.restricted_filter_helper=Atstājiet tukšu, lai nevienam lietotajam neuzstādīt ierobežots pazīmi. Izmantojiet zvaigznīti ('*'), lai uzstādītu visiem lietotājiem, kas neatbilst administratora filtram. +auths.verify_group_membership=Pārbaudīt piederību LDAP grupai (atstājiet filtru tukšu, lai neizmantotu) auths.group_search_base=Grupas pamatnosacījumi auths.group_attribute_list_users=Grupas atribūts, kas satur sarakstu ar lietotājiem auths.user_attribute_in_group=Grupas atribūts, kas nosaka lietotāju +auths.map_group_to_team=Sasaistīt LDAP grupas ar organizācijas komandām (atstājiet tukšu, lai to nedarītu) +auths.map_group_to_team_removal=Noņemt lietotājus no sinhronizētajām komandām, ja lietotājs nav piesaistīts attiecīgajai LDAP grupai +auths.enable_ldap_groups=Iespējot LDAP grupas auths.ms_ad_sa=MS AD meklēšanas atribūti auths.smtp_auth=SMTP autentifikācijas tips auths.smtphost=SMTP resursdators @@ -2740,9 +2860,12 @@ monitor.next=Nākošās izpildes laiks monitor.previous=Pēdējās izpildes laiks monitor.execute_times=Izpildes monitor.process=Darbojošies procesi +monitor.stacktrace=Steka izsekojamība +monitor.goroutines=%d gorutīnas monitor.desc=Apraksts monitor.start=Sākuma laiks monitor.execute_time=Izpildes laiks +monitor.last_execution_result=Rezultāts monitor.process.cancel=Atcelt procesu monitor.process.cancel_desc=Procesa atcelšana var radīt datu zaudējumus monitor.process.cancel_notices=Atcelt: <strong>%s</strong>? @@ -2754,6 +2877,7 @@ monitor.queue.type=Veids monitor.queue.exemplar=Eksemplāra veids monitor.queue.numberworkers=Strādņu skaits monitor.queue.maxnumberworkers=Maksimālais strādņu skaits +monitor.queue.numberinqueue=Skaits rindā monitor.queue.review=Pārbaudīt konfigurāciju monitor.queue.review_add=Pārbaudīt/Pievienot strādņus monitor.queue.configuration=Sākotnējā konfigurācija @@ -2773,6 +2897,12 @@ monitor.queue.pool.flush.title=Izlīdzināšanas rinda monitor.queue.pool.flush.desc=Izlīdzināsana pievienos strādnieku, kas tiks apturēts tiklīdz rinda ir tukša vai tai iestājas noildze. monitor.queue.pool.flush.submit=Pievienot izlīdzināsanas strādnieku monitor.queue.pool.flush.added=Izlīdzināšanas strādnieks pievienots rindai %[1]s +monitor.queue.pool.pause.title=Apturēt rindu +monitor.queue.pool.pause.desc=Apturot rindu, tās dati netiks apstrādāti +monitor.queue.pool.pause.submit=Apturēt rindu +monitor.queue.pool.resume.title=Atsākt rindu +monitor.queue.pool.resume.desc=Noteikt šai rindai atsākt darbu +monitor.queue.pool.resume.submit=Atsākt rindu monitor.queue.settings.title=Pūla iestatījumi monitor.queue.settings.desc=Pūli var dinamiski augt un paildzinātu atbildi uz strādņu rindas bloķēšanu. Šis izmaiņas ietekmēs pašreizējās strādņu grupas. @@ -2903,4 +3033,94 @@ error.no_unit_allowed_repo=Jums nav tiesību aplūkot nevienu šī repozitorija error.unit_not_allowed=Jums nav tiesību piekļūt šai repozitorija sadaļai. [packages] +title=Pakotnes +desc=Pārvaldīt repozitorija pakotnes. +empty=Pašlaik šeit nav nevienas pakotnes. +empty.documentation=Papildus informācija par pakotņu reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">dokumentācijā</a>. +filter.type=Veids +filter.type.all=Visas +filter.no_result=Pēc norādītajiem kritērijiem nekas netika atrasts. +filter.container.tagged=Ar atzīmi +filter.container.untagged=Bez atzīmes +published_by=Publicēja <a href="%[2]s">%[3]s</a> %[1]s +published_by_in=Publicēja <a href="%[2]s">%[3]s</a> %[1]s repozitorijā <a href="%[4]s"><strong>%[5]s</strong></a> +installation=Instalācija +about=Par šo pakotni +requirements=Prasības +dependencies=Atkarības +keywords=Atslēgvārdi +details=Papildu informācija +details.author=Autors +details.project_site=Projekta lapa +details.license=Licence +assets=Resursi +versions=Versijas +versions.on=publicēta +versions.view_all=Parādīt visas +dependency.id=ID +dependency.version=Versija +composer.registry=Pievienojiet šo reģistru savā <code>~/.composer/config.json</code> failā: +composer.install=Lai instalētu Composer pakotni, izpildiet sekojošu komandu: +composer.documentation=Papildus informācija par Composer reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">dokumentācijā</a>. +composer.dependencies=Atkarības +composer.dependencies.development=Izstrādes atkarības +conan.details.repository=Repozitorijs +conan.registry=Konfigurējiet šo reģistru no komandrindas: +conan.install=Lai instalētu Conan pakotni, izpildiet sekojošu komandu: +conan.documentation=Papildus informācija par Conan reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">dokumentācijā</a>. +container.details.type=Attēla formāts +container.details.platform=Platforma +container.details.repository_site=Repozitorija lapa +container.details.documentation_site=Dokumentācijas lapa +container.pull=Atgādājiet šo attēlu no komandrindas: +container.documentation=Papildus informācija par konteineru reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">dokumentācijā</a>. +container.multi_arch=OS / arhitektūra +container.layers=Attēla slāņi +container.labels=Etiķetes +container.labels.key=Atslēga +container.labels.value=Vērtība +generic.download=Lejupielādēt pakotni, izmantojot, komandrindu: +generic.documentation=Papildus informācija par ģenerisku pakotņu reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">dokumentācijā</a>. +helm.registry=Konfigurējiet šo reģistru no komandrindas: +helm.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: +helm.documentation=Papildus informācija par Helm reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">dokumentācijā</a>. +maven.registry=Konfigurējiet šo reģistru sava projekta <code>pom.xml</code> failā: +maven.install=Lai izmantotu pakotni, sadaļā <code>dependencies</code> failā <code>pom.xml</code> ievietojiet sekojošas rindas: +maven.install2=Izpildiet no komandrindas: +maven.download=Izpildiet no komandrindas, lai lejupielādētu šo atkarību: +maven.documentation=Papildus informācija par Maven reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">dokumentācijā</a>. +nuget.registry=Konfigurējiet šo reģistru no komandrindas: +nuget.install=Lai instalētu NuGet pakotni, izpildiet sekojošu komandu: +nuget.documentation=Papildus informācija par NuGet reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">dokumentācijā</a>. +nuget.dependency.framework=Mērķa ietvars +npm.registry=Konfigurējiet šo reģistru sava projekta <code>.npmrc</code> failā: +npm.install=Lai instalētu npm pakotni, izpildiet sekojošu komandu: +npm.install2=vai pievienojiet failā package.json sekojošas rindas: +npm.documentation=Papildus informācija par npm reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">dokumentācijā</a>. +npm.dependencies=Atkarības +npm.dependencies.development=Izstrādes atkarības +npm.dependencies.peer=Netiešās atkarības +npm.dependencies.optional=Neobligātās atkarības +npm.details.tag=Tags +pypi.requires=Nepieciešams Python +pypi.install=Lai instalētu pip pakotni, izpildiet sekojošu komandu: +pypi.documentation=Papildus informācija par PyPI reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">dokumentācijā</a>. +rubygems.install=Lai instalētu gem pakotni, izpildiet sekojošu komandu: +rubygems.install2=vai pievienojiet Gemfile: +rubygems.dependencies.runtime=Izpildlaika atkarības +rubygems.dependencies.development=Izstrādes atkarības +rubygems.required.ruby=Nepieciešamā Ruby versija +rubygems.required.rubygems=Nepieciešamā RubyGem versija +rubygems.documentation=Papildus informācija par RubyGems reģistru pieejama <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">dokumentācijā</a>. +settings.link=Piesaistīt pakotni šim repozitorijam +settings.link.description=Sasaistot pakotni ar repozitoriju, tā tiks attēlota repozitorija pakotņu sarakstā. +settings.link.select=Norādiet repozitoriju +settings.link.button=Atjaunot repozitorija saiti +settings.link.success=Repozitorija saite tika veiksmīgi atjaunota. +settings.link.error=Neizdevās atjaunot repozitorija saiti. +settings.delete=Dzēst pakotni +settings.delete.description=Pakotne tiks neatgriezeniski izdzēsta. +settings.delete.notice=Tiks dzēsts %s (%s). Šī darbība ir neatgriezeniska. Vai vēlaties turpināt? +settings.delete.success=Pakotne tika izdzēsta. +settings.delete.error=Neizdevās izdzēst pakotni. diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index cbd633d13..c65885a1e 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -131,7 +131,7 @@ license_desc=取得 <a target="_blank" rel="noopener noreferrer" href="https://c install=安裝頁面 title=初始組態 docker_helper=如果您在 Docker 中執行 Gitea,請先閱讀<a target="_blank" rel="noopener noreferrer" href="%s">安裝指南</a>再來調整設定。 -require_db_desc=Gitea 需要 MySQL、PostgreSQL、SQLite3、MSSQL、TiDB(MySQL 協定)等其中一項。 +require_db_desc=Gitea 需要 MySQL、PostgreSQL、SQLite3、MSSQL、TiDB (MySQL 協定) 等其中一項。 db_title=資料庫設定 db_type=資料庫類型 host=主機 @@ -213,7 +213,7 @@ confirm_password=確認密碼 admin_email=電子信箱 install_btn_confirm=安裝 Gitea test_git_failed=無法識別「git」命令:%v -sqlite3_not_available=您目前的版本不支援 SQLite3,請從 %s 下載官方的預先編譯版本(不是 gobuild 版本)。 +sqlite3_not_available=您目前的版本不支援 SQLite3,請從 %s 下載官方的預先編譯版本 (不是 gobuild 版本)。 invalid_db_setting=資料庫設定不正確: %v invalid_db_table=資料庫的資料表「%s」無效:%v invalid_repo_path=儲存庫根目錄設定不正確:%v @@ -391,13 +391,13 @@ issue.action.ready_for_review=<b>@%[1]s</b> 標記了此合併請求為準備好 issue.action.new=<b>@%[1]s</b> 建立了 #%[2]d。 issue.in_tree_path=在 %s 中: -release.new.subject=%[2]s 中的 %[1]s 發佈了 -release.new.text=<b>@%[1]s</b> 於 %[3]s 發佈了 %[2]s +release.new.subject=%[2]s 中的 %[1]s 發布了 +release.new.text=<b>@%[1]s</b> 於 %[3]s 發布了 %[2]s release.title=標題:%s release.note=說明: release.downloads=下載: -release.download.zip=原始碼(ZIP) -release.download.targz=原始碼(TAR.GZ) +release.download.zip=原始碼 (ZIP) +release.download.targz=原始碼 (TAR.GZ) repo.transfer.subject_to=%s 想要把「%s」轉移給 %s repo.transfer.subject_to_you=%s 想要把「%s」轉移給您 @@ -491,7 +491,9 @@ auth_failed=授權認證失敗:%v still_own_repo=此帳戶仍然擁有一個或多個儲存庫,您必須先刪除或轉移它們。 still_has_org=此帳戶仍是一個或多個組織的成員,您必須先離開它們。 +still_own_packages=您的帳戶擁有一個或多個套件,請先刪除他們。 org_still_own_repo=該組織仍然是某些儲存庫的擁有者,您必須先轉移或刪除它們才能執行刪除組織! +org_still_own_packages=此組織擁有一個或多個套件,請先刪除他們。 target_branch_not_exist=目標分支不存在 @@ -768,7 +770,7 @@ or_enter_secret=或者輸入密碼: %s then_enter_passcode=然後輸入應用程式中顯示的驗證碼: passcode_invalid=無效的驗證碼,請重試。 twofa_enrolled=您的帳戶已經啟用了兩步驟驗證。請將備用驗證碼 (%s) 保存到一個安全的地方,它只會顯示這麼一次! -twofa_failed_get_secret=取得密鑰(Secret)失敗。 +twofa_failed_get_secret=取得密鑰 (Secret) 失敗。 webauthn_desc=安全金鑰是包含加密密鑰的硬體設備,它們可以用於兩步驟驗證。安全金鑰必須支援 <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> 標準。 webauthn_register_key=新增安全金鑰 @@ -837,7 +839,7 @@ download_bundle=下載 BUNDLE generate_repo=產生儲存庫 generate_from=產生自 repo_desc=儲存庫描述 -repo_desc_helper=輸入簡介(非必要) +repo_desc_helper=輸入簡介 (選用) repo_lang=儲存庫語言 repo_gitignore_helper=選擇 .gitignore 範本 repo_gitignore_helper_desc=從常見語言範本清單中挑選忽略追蹤的檔案。預設情況下各種語言建置工具產生的特殊檔案都包含在 .gitignore 中。 @@ -849,12 +851,12 @@ license_helper_desc=授權條款定義了他人使用您原始碼的允許和禁 readme=讀我檔案 readme_helper=選擇讀我檔案範本。 readme_helper_desc=這是您能為專案撰寫完整描述的地方。 -auto_init=初始化儲存庫(建立 .gitignore、授權條款和讀我檔案) -trust_model_helper=選擇簽署驗證的信任模型。可用的選項: -trust_model_helper_collaborator=協作者:信任協作者的簽署 -trust_model_helper_committer=提交者:信任與提交者相符的簽署 -trust_model_helper_collaborator_committer=協作者 + 提交者:信任協作者同時是提交者的簽署 -trust_model_helper_default=預設:使用此 Gitea 的預設儲存庫信任模式 +auto_init=初始化儲存庫 (加入 .gitignore、授權條款和讀我檔案) +trust_model_helper=選擇簽署驗證的信任模型。可用的選項: +trust_model_helper_collaborator=協作者: 信任協作者的簽署 +trust_model_helper_committer=提交者: 信任與提交者相符的簽署 +trust_model_helper_collaborator_committer=協作者 + 提交者: 信任協作者同時是提交者的簽署 +trust_model_helper_default=預設: 使用此 Gitea 的預設儲存庫信任模式 create_repo=建立儲存庫 default_branch=預設分支 default_branch_helper=預設分支是合併請求和提交程式碼的基礎分支。 @@ -943,7 +945,7 @@ migrate_items_labels=標籤 migrate_items_issues=問題 migrate_items_pullrequests=合併請求 migrate_items_merge_requests=合併請求 -migrate_items_releases=版本發佈 +migrate_items_releases=版本發布 migrate_repo=遷移儲存庫 migrate.clone_address=從 URL 遷移 / Clone migrate.clone_address_desc=現有存儲庫的 HTTP(S) 或 Git Clone URL @@ -974,7 +976,7 @@ migrate.migrating_git=正在遷移 Git 資料 migrate.migrating_topics=正在遷移主題 migrate.migrating_milestones=正在遷移里程碑 migrate.migrating_labels=正在遷移標籤 -migrate.migrating_releases=正在遷移版本發佈 +migrate.migrating_releases=正在遷移版本發布 migrate.migrating_issues=正在遷移問題 migrate.migrating_pulls=正在遷移合併請求 @@ -996,7 +998,7 @@ no_desc=暫無描述 quick_guide=快速幫助 clone_this_repo=Clone 此儲存庫 create_new_repo_command=從命令列建立新儲存庫。 -push_exist_repo=從命令行推送已經建立的儲存庫 +push_exist_repo=從命令列推送已存在的儲存庫 empty_message=此儲存庫未包含任何內容。 broken_message=無法讀取此儲存庫底層的 Git 資料。請聯絡此 Gitea 執行個體的管理員或刪除此儲存庫。 @@ -1012,6 +1014,7 @@ tags=標籤 issues=問題 pulls=合併請求 project_board=專案 +packages=套件 labels=標籤 org_labels_desc=組織層級標籤可用於此組織下的<strong>所有存儲庫</strong>。 org_labels_desc_manage=管理 @@ -1019,10 +1022,10 @@ org_labels_desc_manage=管理 milestones=里程碑 commits=提交歷史 commit=提交 -release=版本發佈 -releases=版本發佈 +release=版本發布 +releases=版本發布 tag=標籤 -released_this=發佈了此版本 +released_this=發布了此版本 file.title=%s 於 %s file_raw=原始文件 file_history=歷史記錄 @@ -1033,10 +1036,10 @@ file_permalink=永久連結 file_too_large=檔案太大,無法顯示。 bidi_bad_header=`此檔案含有未預期的 Bidirectional Unicode 字元!` bidi_bad_description=`此檔案含有未預期的 Bidirectional Unicode 字元,這些字元的處理方式可能和下面呈現的不同。若您是有意且合理的使用,您可以放心地忽略此警告。使用 Escape 按鈕顯示隱藏的字元。` -bidi_bad_description_escaped=`此檔案含有未預期的 Bidirectional Unicode 字元。隱藏的 Unicode 字元已在下面被跳脫(Escaped)。使用 Unescape 按鈕以顯示它們呈現的樣子。` +bidi_bad_description_escaped=`此檔案含有未預期的 Bidirectional Unicode 字元。隱藏的 Unicode 字元已在下面被跳脫 (Escaped)。使用 Unescape 按鈕以顯示它們呈現的樣子。` unicode_header=`此檔案含有隱藏的 Unicode 字元!` unicode_description=`此檔案含有隱藏的 Unicode 字元,這些字元的處理方式可能和下面呈現的不同。若您是有意且合理的使用,您可以放心地忽略此警告。使用 Escape 按鈕顯示隱藏的字元。` -unicode_description_escaped=`此檔案含有隱藏的 Unicode 字元。隱藏的 Unicode 字元已在下面被跳脫(Escaped)。使用 Unescape 按鈕以顯示它們呈現的樣子。` +unicode_description_escaped=`此檔案含有隱藏的 Unicode 字元。隱藏的 Unicode 字元已在下面被跳脫 (Escaped)。使用 Unescape 按鈕以顯示它們呈現的樣子。` line_unicode=`這一行有隱藏的 Unicode 字元` escape_control_characters=Escape @@ -1085,7 +1088,7 @@ editor.patch=套用 Patch editor.patching=正在 Patch: editor.fail_to_apply_patch=無法套用 Patch「%s」 editor.new_patch=新增 Patch -editor.commit_message_desc=(選填)加入詳細說明... +editor.commit_message_desc=(選用) 加入詳細說明... editor.signoff_desc=在提交訊息底部加入提交者的「Signed-off-by」資訊。 editor.commit_directly_to_this_branch=直接提交到 <strong class="branch-name">%s</strong> 分支。 editor.create_new_branch=為此提交建立<strong>新分支</strong>並提出合併請求。 @@ -1155,7 +1158,7 @@ ext_issues.desc=連結到外部問題追蹤器。 projects=專案 projects.desc=在專案看板中管理問題與合併請求。 -projects.description=描述(非必要) +projects.description=描述 (選用) projects.description_placeholder=描述 projects.create=建立專案 projects.title=標題 @@ -1326,7 +1329,7 @@ issues.ref_reopening_from=`<a href="%[3]s">關聯了合併請求 %[4]s 將重新 issues.ref_closed_from=`<a href="%[3]s">關閉了這個問題 %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_reopened_from=`<a href="%[3]s">重新開放了這個問題 %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_from=`自 %[1]s` -issues.poster=發佈者 +issues.poster=發布者 issues.collaborator=協作者 issues.owner=擁有者 issues.re_request_review=再次請求審核 @@ -1380,7 +1383,7 @@ issues.unlock.title=解鎖此問題的對話。 issues.comment_on_locked=您無法在已鎖定的問題上留言。 issues.delete=刪除 issues.delete.title=刪除此問題? -issues.delete.text=您真的要刪除此問題嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。) +issues.delete.text=您真的要刪除此問題嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。) issues.tracker=時間追蹤 issues.start_tracking_short=開始計時 issues.start_tracking=開始時間追蹤 @@ -1438,8 +1441,8 @@ issues.dependency.pr_close_blocked=在您合併以前,您必須先關閉所有 issues.dependency.blocks_short=阻擋 issues.dependency.blocked_by_short=先決於 issues.dependency.remove_header=移除先決條件 -issues.dependency.issue_remove_text=即將從此問題移除先決條件。是否繼續? -issues.dependency.pr_remove_text=即將從此合併請求移除先決條件。是否繼續? +issues.dependency.issue_remove_text=即將從此問題移除先決條件。是否繼續? +issues.dependency.pr_remove_text=即將從此合併請求移除先決條件。是否繼續? issues.dependency.setting=啟用問題及合併請求的先決條件 issues.dependency.add_error_same_issue=您無法將問題設定為自己的先決條件。 issues.dependency.add_error_dep_issue_not_exist=先決條件問題不存在。 @@ -1507,7 +1510,7 @@ pulls.nothing_to_compare_and_allow_empty_pr=這些分支的內容相同,此合 pulls.has_pull_request=`已有介於這些分支間的合併請求:<a href="%[1]s">%[2]s#%[3]d</a>` pulls.create=建立合併請求 pulls.title_desc=請求將 %[1]d 次程式碼提交從 <code>%[2]s</code> 合併至 <code id="branch_target">%[3]s</code> -pulls.merged_title_desc=將 %[1]d 次代碼提交從 <code>%[2]s</code> 合併至 <code>%[3]s</code> %[4]s +pulls.merged_title_desc=將 %[1]d 次提交從 <code>%[2]s</code> 合併至 <code>%[3]s</code> %[4]s pulls.change_target_branch_at=`將目標分支從 <b>%s</b> 更改為 <b>%s</b> %s` pulls.tab_conversation=對話內容 pulls.tab_commits=程式碼提交 @@ -1521,7 +1524,7 @@ pulls.manually_merged_as=此合併請求已被手動合併為 <a rel="nofollow" pulls.is_closed=合併請求已被關閉。 pulls.has_merged=合併請求已合併。 pulls.title_wip_desc=`<a href="#">標題用 <strong>%s</strong> 開頭</a>以避免意外地合併此合併請求。` -pulls.cannot_merge_work_in_progress=此合併請求被標記為還在進行中(WIP)。 +pulls.cannot_merge_work_in_progress=此合併請求被標記為還在進行中 (WIP)。 pulls.still_in_progress=還在進行中嗎? pulls.add_prefix=加入 <strong>%s</strong> 前綴 pulls.remove_prefix=移除 <strong>%s</strong> 前綴 @@ -1618,7 +1621,7 @@ milestones.completeness=%d%% 完成 milestones.create=建立里程碑 milestones.title=標題 milestones.desc=描述 -milestones.due_date=截止日期(可選) +milestones.due_date=截止日期 (選用) milestones.clear=清除 milestones.invalid_due_date_format=截止日期的格式必須為「yyyy-mm-dd」。 milestones.create_success=已建立里程碑「%s」。 @@ -1720,8 +1723,8 @@ activity.unresolved_conv_desc=這些最近更改的問題和合併請求尚未 activity.unresolved_conv_label=開放 activity.title.releases_1=%d 個版本 activity.title.releases_n=%d 個版本 -activity.title.releases_published_by=%[2]s發佈了 %[1]s -activity.published_release_label=已發佈 +activity.title.releases_published_by=%[2]s發布了 %[1]s +activity.published_release_label=已發布 activity.no_git_activity=期間內沒有任何提交動態 activity.git_stats_exclude_merges=不計合併, activity.git_stats_author_1=%d 位作者 @@ -1814,9 +1817,10 @@ settings.pulls.allow_rebase_merge=啟用 Rebase 合併提交 settings.pulls.allow_rebase_merge_commit=啟用 Rebase 顯式合併提交(--no-ff) settings.pulls.allow_squash_commits=啟用 Squash 合併提交 settings.pulls.allow_manual_merge=允許將合併請求標記為手動合併 -settings.pulls.enable_autodetect_manual_merge=啟用自動偵測手動合併(注意:在某些特殊情況下可能發生誤判) +settings.pulls.enable_autodetect_manual_merge=啟用自動偵測手動合併 (注意: 在某些特殊情況下可能發生誤判) settings.pulls.allow_rebase_update=啟用透過 Rebase 更新合併請求分支 settings.pulls.default_delete_branch_after_merge=預設在合併後刪除合併請求分支 +settings.packages_desc=啟用儲存庫套件註冊中心 settings.projects_desc=啟用儲存庫專案 settings.admin_settings=管理員設定 settings.admin_enable_health_check=啟用儲存庫的健康檢查 (git fsck) @@ -1850,7 +1854,7 @@ settings.transfer_form_title=輸入儲存庫名稱以確認: settings.transfer_in_progress=目前正在進行轉移。如果您想要將此儲存庫轉移給其他使用者,請取消他。 settings.transfer_notices_1=- 如果將此儲存庫轉移給個別使用者,您將會失去此儲存庫的存取權。 settings.transfer_notices_2=- 如果將此儲存庫轉移到您(共同)擁有的組織,您將能繼續保有此儲存庫的存取權。 -settings.transfer_notices_3=- 如果此儲存庫為私有儲存庫且將轉移給個別使用者,此動作確保該使用者至少擁有讀取權限(必要時將會修改權限)。 +settings.transfer_notices_3=- 如果此儲存庫為私有儲存庫且將轉移給個別使用者,此動作確保該使用者至少擁有讀取權限 (必要時將會修改權限)。 settings.transfer_owner=新擁有者 settings.transfer_perform=進行轉移 settings.transfer_started=此儲存庫已被標記為待轉移且正在等待「%s」的確認 @@ -1860,13 +1864,13 @@ settings.trust_model=簽署信任模式 settings.trust_model.default=預設信任模式 settings.trust_model.default.desc=使用此 Gitea 的預設儲存庫信任模式。 settings.trust_model.collaborator=協作者 -settings.trust_model.collaborator.long=協作者:信任協作者的簽署 -settings.trust_model.collaborator.desc=此儲存庫協作者的有效簽署將被標記為「受信任」(無論它們是否符合提交者),簽署只符合提交者時將標記為「不受信任」,都不符合時標記為「不符合」。 +settings.trust_model.collaborator.long=協作者: 信任協作者的簽署 +settings.trust_model.collaborator.desc=此儲存庫協作者的有效簽署將被標記為「受信任」(無論它們是否符合提交者),簽署只符合提交者時將標記為「不受信任」,都不符合時標記為「不符合」。 settings.trust_model.committer=提交者 -settings.trust_model.committer.long=提交者:信任與提交者相符的簽署(此選項與 GitHub 相同,這會強制 Gitea 簽署提交並以 Gitea 作為提交者) +settings.trust_model.committer.long=提交者: 信任與提交者相符的簽署 (此選項與 GitHub 相同,這會強制 Gitea 簽署提交並以 Gitea 作為提交者) settings.trust_model.committer.desc=提交者的有效簽署將被標記為「受信任」,否則將被標記為「不符合」。這會強制 Gitea 成為受簽署提交的提交者,實際的提交者將於提交訊息結尾被標記為「Co-authored-by:」和「Co-committed-by:」。預設的 Gitea 金鑰必須符合資料庫中的一位使用者。 settings.trust_model.collaboratorcommitter=協作者+提交者 -settings.trust_model.collaboratorcommitter.long=協作者 + 提交者:信任協作者同時是提交者的簽署 +settings.trust_model.collaboratorcommitter.long=協作者 + 提交者: 信任協作者同時是提交者的簽署 settings.trust_model.collaboratorcommitter.desc=此儲存庫協作者的有效簽署在他同時是提交者時將被標記為「受信任」,簽署只符合提交者時將標記為「不受信任」,都不符合時標記為「不符合」。這會強制 Gitea 成為受簽署提交的提交者,實際的提交者將於提交訊息結尾被標記為「Co-Authored-By:」和「Co-Committed-By:」。預設的 Gitea 金鑰必須符合資料庫中的一位使用者。 settings.wiki_delete=刪除 Wiki 資料 settings.wiki_delete_desc=刪除儲存庫 Wiki 資料是永久的且不可還原。 @@ -1942,8 +1946,8 @@ settings.event_delete=刪除 settings.event_delete_desc=刪除分支或標籤。 settings.event_fork=Fork settings.event_fork_desc=儲存庫已被 fork。 -settings.event_release=版本發佈 -settings.event_release_desc=在儲存庫中發佈、更新或刪除版本。 +settings.event_release=版本發布 +settings.event_release_desc=在儲存庫中發布、更新或刪除版本。 settings.event_push=推送 settings.event_push_desc=推送到儲存庫。 settings.event_repository=儲存庫 @@ -1974,6 +1978,8 @@ settings.event_pull_request_review=合併請求審核 settings.event_pull_request_review_desc=核准、退回或提出審核留言。 settings.event_pull_request_sync=合併請求同步 settings.event_pull_request_sync_desc=合併請求同步。 +settings.event_package=套件 +settings.event_package_desc=套件已在儲存庫中建立或刪除。 settings.branch_filter=分支篩選 settings.branch_filter_desc=推送、建立分支、刪除分支事件的白名單,請使用 glob 比對模式。如果留白或輸入<code>*</code>,所有分支的事件都會被回報。語法參見 <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a>。範例:<code>master</code>, <code>{master,release*}</code>。 settings.active=啟用 @@ -2054,9 +2060,9 @@ settings.dismiss_stale_approvals=捨棄過時的核可 settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的內容,並被推送到此分支時,將捨棄舊的核可。 settings.require_signed_commits=僅接受經簽署的提交 settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。 -settings.protect_protected_file_patterns=受保護的檔案模式(以分號區隔「\;」): +settings.protect_protected_file_patterns=受保護的檔案模式 (以分號區隔「\;」): settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改、刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「\;」分隔多個模式。請於<a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> 文件查看模式格式。範例:<code>.drone.yml</code>, <code>/docs/**/*.txt</code>。 -settings.protect_unprotected_file_patterns=未受保護的檔案模式(以分號區隔「\;」): +settings.protect_unprotected_file_patterns=未受保護的檔案模式 (以分號區隔「\;」): settings.protect_unprotected_file_patterns_desc=當使用者有寫入權限時,可繞過推送限制,直接修改未受保護的檔案。可以用半形分號「\;」分隔多個模式。請於<a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> 文件查看模式格式。範例:<code>.drone.yml</code>, <code>/docs/**/*.txt</code>。 settings.add_protected_branch=啟用保護 settings.delete_protected_branch=停用保護 @@ -2191,20 +2197,20 @@ diff.image.overlay=重疊 diff.has_escaped=這一行有隱藏的 Unicode 字元 releases.desc=追蹤專案版本和檔案下載。 -release.releases=版本發佈 +release.releases=版本發布 release.detail=版本詳情 release.tags=標籤 -release.new_release=發佈新版本 +release.new_release=發布新版本 release.draft=草稿 -release.prerelease=預發佈版本 +release.prerelease=預發布版本 release.stable=穩定 release.compare=比較 release.edit=編輯 release.ahead.commits=<strong>%d</strong> 次提交 -release.ahead.target=在此版本發佈後被加入到 %s +release.ahead.target=在此版本發布後被加入到 %s release.source_code=程式碼 -release.new_subheader=發佈、整理專案的版本。 -release.edit_subheader=發佈、整理專案的版本。 +release.new_subheader=發布、整理專案的版本。 +release.edit_subheader=發布、整理專案的版本。 release.tag_name=標籤名稱 release.target=目標分支 release.tag_helper=新增或選擇一個既有的標籤。 @@ -2213,17 +2219,17 @@ release.content=內容 release.prerelease_desc=標記為 Pre-Release release.prerelease_helper=標記此版本不適合生產使用。 release.cancel=取消 -release.publish=發佈版本 +release.publish=發布版本 release.save_draft=儲存草稿 -release.edit_release=更新發佈 -release.delete_release=刪除發佈 +release.edit_release=更新發布 +release.delete_release=刪除發布 release.delete_tag=刪除標籤 -release.deletion=刪除發佈 -release.deletion_desc=刪除版本發佈只會將它從 Gitea 移除。儲存庫內容和歷史將保持不變,是否繼續? -release.deletion_success=已刪除此版本發佈。 +release.deletion=刪除發布 +release.deletion_desc=刪除版本發布只會將它從 Gitea 移除。儲存庫內容和歷史將保持不變,是否繼續? +release.deletion_success=已刪除此版本發布。 release.deletion_tag_desc=即將從儲存庫移除此標籤。儲存庫內容和歷史將保持不變,是否繼續? release.deletion_tag_success=已刪除此標籤。 -release.tag_name_already_exist=已經存在使用相同標籤的發佈版本。 +release.tag_name_already_exist=已經存在使用相同標籤的發布版本。 release.tag_name_invalid=標籤名稱無效。 release.tag_name_protected=標籤名稱已受保護。 release.tag_already_exist=此標籤名稱已存在。 @@ -2318,9 +2324,9 @@ settings.permission=權限 settings.repoadminchangeteam=儲存庫管理者可增加與移除團隊權限 settings.visibility=瀏覽權限 settings.visibility.public=公開 -settings.visibility.limited=受限(只有登入的使用者才能看到) +settings.visibility.limited=受限 (只有登入的使用者才能看到) settings.visibility.limited_shortname=受限 -settings.visibility.private=私有(只有組織成員才能看到) +settings.visibility.private=私有 (只有組織成員才能看到) settings.visibility.private_shortname=私有 settings.update_settings=更新設定 @@ -2419,7 +2425,7 @@ dashboard.new_version_hint=現已推出 Gitea %s,您正在執行 %s。查看<a dashboard.statistic=摘要 dashboard.operations=維護作業 dashboard.system_status=系統狀態 -dashboard.statistic_info=Gitea 資料庫統計: <b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,~<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 則留言,<b>%d</b> 個社群帳戶,<b>%d</b> 個使用者關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。 +dashboard.statistic_info=Gitea 資料庫統計: <b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,~<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 則留言,<b>%d</b> 個社群帳戶,<b>%d</b> 個使用者追蹤,<b>%d</b> 個鏡像,<b>%d</b> 個版本發布,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。 dashboard.operation_name=作業名稱 dashboard.operation_switch=開關 dashboard.operation_run=執行 @@ -2438,7 +2444,7 @@ dashboard.cron.error=Cron 中的錯誤: %s: %[3]s dashboard.cron.finished=Cron: %[1]s 已完成 dashboard.delete_inactive_accounts=刪除所有未啟用帳戶 dashboard.delete_inactive_accounts.started=刪除所有未啟用帳戶的任務已啟動。 -dashboard.delete_repo_archives=刪除所有儲存庫存檔(ZIP, TAR.GZ, etc..) +dashboard.delete_repo_archives=刪除所有儲存庫存檔 (ZIP, TAR.GZ, etc..) dashboard.delete_repo_archives.started=刪除所有儲存庫存檔的任務已啟動。 dashboard.delete_missing_repos=刪除所有遺失 Git 檔案的儲存庫 dashboard.delete_missing_repos.started=刪除所有遺失 Git 檔案的儲存庫的任務已啟動。 @@ -2448,7 +2454,7 @@ dashboard.repo_health_check=對所有儲存庫進行健康檢查 dashboard.check_repo_stats=檢查所有儲存庫的統計資料 dashboard.archive_cleanup=刪除舊的儲存庫存檔 dashboard.deleted_branches_cleanup=清理已刪除的分支 -dashboard.update_migration_poster_id=更新遷移發佈者 ID +dashboard.update_migration_poster_id=更新遷移發布者 ID dashboard.git_gc_repos=對所有儲存庫進行垃圾回收 dashboard.resync_all_sshkeys=使用 Gitea 的 SSH 金鑰更新「.ssh/authorized_keys」檔案。 dashboard.resync_all_sshkeys.desc=(內建 SSH 伺服器無需使用。) @@ -2458,6 +2464,7 @@ dashboard.resync_all_hooks=重新同步所有儲存庫的 pre-receive、update dashboard.reinit_missing_repos=重新初始化所有記錄存在但遺失的 Git 儲存庫 dashboard.sync_external_users=同步外部使用者資料 dashboard.cleanup_hook_task_table=清理 hook_task 資料表 +dashboard.cleanup_packages=清理已過期的套件 dashboard.server_uptime=服務執行時間 dashboard.current_goroutine=目前的 Goroutines 數量 dashboard.current_memory_usage=目前記憶體使用量 @@ -2528,6 +2535,7 @@ users.delete_account=刪除使用者帳戶 users.cannot_delete_self=您無法刪除您自己 users.still_own_repo=這個使用者還擁有一個或更多的儲存庫。請先刪除或是轉移這些儲存庫。 users.still_has_org=此使用者是組織的成員。請先將他從組織中移除。 +users.still_own_packages=此使用者擁有一個或多個套件,請先刪除這些套件。 users.deletion_success=使用者帳戶已被刪除。 users.reset_2fa=重設兩步驟驗證 users.list_status_filter.menu_text=篩選 @@ -2554,7 +2562,7 @@ emails.updated=信箱已更新 emails.not_updated=電子信箱更新失敗: %v emails.duplicate_active=此信箱已被其他使用者使用 emails.change_email_header=更新電子信箱屬性 -emails.change_email_text=您確定要更新這個電子信箱? +emails.change_email_text=您確定要更新這個電子信箱? orgs.org_manage_panel=組織管理 orgs.name=組織名稱 @@ -2574,6 +2582,16 @@ repos.forks=Fork 數 repos.issues=問題數 repos.size=大小 +packages.package_manage_panel=套件管理 +packages.total_size=總大小: %s +packages.owner=擁有者 +packages.creator=建立者 +packages.name=名稱 +packages.version=版本 +packages.type=類型 +packages.repository=儲存庫 +packages.size=大小 +packages.published=已發布 defaulthooks=預設 Webhook defaulthooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。這裡所定義的 Webhook 是預設的,並且會複製到所有新儲存庫。在 <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">Webhook 指南</a>閱讀更多內容。 @@ -2617,11 +2635,11 @@ auths.filter=使用者篩選器 auths.admin_filter=管理者篩選器 auths.restricted_filter=受限制的篩選器 auths.restricted_filter_helper=留白則不限制任何使用者。使用米字「*」將所有不符合管理員篩選條件的使用者設定為受限。 -auths.verify_group_membership=驗證 LDAP 群組成員資格(篩選器留空以跳過) +auths.verify_group_membership=驗證 LDAP 群組成員資格 (篩選器留空以跳過) auths.group_search_base=群組搜尋的 Base DN auths.group_attribute_list_users=包含使用者清單的群組屬性 auths.user_attribute_in_group=群組中列出的使用者屬性 -auths.map_group_to_team=對應 LDAP 群組到組織團隊(欄位留空以跳過) +auths.map_group_to_team=對應 LDAP 群組到組織團隊 (欄位留空以跳過) auths.map_group_to_team_removal=如果使用者不屬於相對應的 LDAP 群組,將使用者從已同步的團隊移除。 auths.enable_ldap_groups=啟用 LDAP 群組 auths.ms_ad_sa=MS AD 搜尋屬性 @@ -2632,12 +2650,12 @@ auths.allowed_domains=域名白名單 auths.allowed_domains_helper=留白以允許所有域名。以半形逗號「,」分隔多個域名。 auths.skip_tls_verify=忽略 TLS 驗證 auths.force_smtps=強制 SMTPS -auths.force_smtps_helper=SMTPS 總是使用 465 埠。設定此選項以強制 SMTPS 使用其他埠。(除此之外若主機支援的話 STARTTLS 也會使用該埠。) +auths.force_smtps_helper=SMTPS 總是使用 465 埠。設定此選項以強制 SMTPS 使用其他埠。(除此之外若主機支援的話 STARTTLS 也會使用該埠。) auths.helo_hostname=HELO 主機名稱 auths.helo_hostname_helper=用 HELO 傳送的主機名稱。留空以傳送目前的主機名稱。 auths.disable_helo=停用 HELO auths.pam_service_name=PAM 服務名稱 -auths.pam_email_domain=PAM 電子信箱域名(非必要) +auths.pam_email_domain=PAM 電子信箱域名 (選用) auths.oauth2_provider=OAuth2 提供者 auths.oauth2_icon_url=圖示 URL auths.oauth2_clientID=客戶端 ID (金鑰) @@ -2651,23 +2669,23 @@ auths.oauth2_emailURL=電子郵件 URL auths.skip_local_two_fa=跳過本地兩步驟驗證 auths.skip_local_two_fa_helper=保持未設定代表使用兩步驟驗證的本地使用者仍然需要通過兩步驟驗證才能登入 auths.oauth2_tenant=租戶 -auths.oauth2_scopes=額外的授權範圍(Scope) +auths.oauth2_scopes=額外的授權範圍 (Scope) auths.oauth2_required_claim_name=必須填寫 Claim 名稱 auths.oauth2_required_claim_name_helper=填寫此名稱以限制 Claim 中有此名稱的使用者才能從此來源登入 auths.oauth2_required_claim_value=必須填寫 Claim 值 auths.oauth2_required_claim_value_helper=填寫此名稱以限制 Claim 中有此名稱和值的使用者才能從此來源登入 -auths.oauth2_group_claim_name=Claim 名稱提供群組名稱給此來源。(選用) -auths.oauth2_admin_group=管理員使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) -auths.oauth2_restricted_group=受限制使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) +auths.oauth2_group_claim_name=Claim 名稱提供群組名稱給此來源。(選用) +auths.oauth2_admin_group=管理員使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) +auths.oauth2_restricted_group=受限制使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) auths.enable_auto_register=允許授權用戶自動註冊 auths.sspi_auto_create_users=自動建立使用者 auths.sspi_auto_create_users_helper=允許 SSPI 認證方法於使用者首次登入時自動建立新帳戶 auths.sspi_auto_activate_users=自動啟用使用者 auths.sspi_auto_activate_users_helper=允許 SSPI 認證方法自動啟用新使用者 auths.sspi_strip_domain_names=從帳號中移除域名 -auths.sspi_strip_domain_names_helper=勾選後,將從登入名稱中移除域名(例如:「DOMAIN\user」和「user@example.org」都會變成「user」) +auths.sspi_strip_domain_names_helper=勾選後,將從登入名稱中移除域名 (例如:「DOMAIN\user」和「user@example.org」都會變成「user」) auths.sspi_separator_replacement=用來替換 \, / 和 @ 的分隔符號 -auths.sspi_separator_replacement_helper=用來替換下級登入名稱分隔符號的字元(例如:「DOMAIN\user」中的「\」)和使用者主體名稱(例如:「user@example.org」中的「@」)。 +auths.sspi_separator_replacement_helper=用來替換下級登入名稱分隔符號的字元 (例如:「DOMAIN\user」中的「\」) 和使用者主體名稱 (例如:「user@example.org」中的「@」)。 auths.sspi_default_language=使用者預設語言 auths.sspi_default_language_helper=SSPI 認證方法自動建立之使用者的預設語言,留白以自動偵測。 auths.tips=幫助提示 @@ -2686,7 +2704,7 @@ auths.tip.twitter=建立應用程式並確保有啟用「Allow this application auths.tip.discord=註冊新的應用程式。網址:https://discordapp.com/developers/applications/me auths.tip.gitea=註冊新的 OAuth2 應用程式。到 https://docs.gitea.io/en-us/oauth2-provider/ 觀看指南 auths.tip.yandex=建立新的應用程式,從「Yandex.Passport API」區塊選擇「Access to email address」、「Access to user avatar」和「Access to username, first name and surname, gender」。網址:https://oauth.yandex.com/client/new -auths.tip.mastodon=輸入您欲認證的 Mastodon 執行個體的自訂網址(或使用預設值) +auths.tip.mastodon=輸入您欲認證的 Mastodon 執行個體的自訂網址 (或使用預設值) auths.edit=修改認證來源 auths.activated=該認證來源已啟用 auths.new_success=已增加認證「%s」。 @@ -2813,8 +2831,8 @@ config.enable_federated_avatar=啟用 Federated Avatars config.git_config=Git 組態 config.git_disable_diff_highlight=停用比較語法高亮 -config.git_max_diff_lines=差異比較時顯示的最多行數(單檔) -config.git_max_diff_line_characters=差異比較時顯示的最多字元數(單行) +config.git_max_diff_lines=差異比較時顯示的最多行數 (單檔) +config.git_max_diff_line_characters=差異比較時顯示的最多字元數 (單行) config.git_max_diff_files=差異比較時顯示的最多檔案數 config.git_gc_args=GC 參數 config.git_migrate_timeout=遷移逾時 @@ -2952,7 +2970,7 @@ mirror_sync_create=從鏡像同步了新參考 <a href="%[2]s">%[3]s</a> 到 <a mirror_sync_delete=從鏡像同步並從 <a href="%[1]s">%[3]s</a> 刪除了參考 <code>%[2]s</code> approve_pull_request=`核可了 <a href="%[1]s">%[3]s#%[2]s</a>` reject_pull_request=`提出了修改建議 <a href="%[1]s">%[3]s#%[2]s</a>` -publish_release=`發佈了 <a href="%[1]s">%[3]s</a> 的 <a href="%[2]s"> "%[4]s" </a>` +publish_release=`發布了 <a href="%[1]s">%[3]s</a> 的 <a href="%[2]s"> "%[4]s" </a>` review_dismissed=`取消了 <b>%[4]s</b> 對 <a href="%[1]s">%[3]s#%[2]s</a> 的審核` review_dismissed_reason=原因: create_branch=在 <a href="%[1]s">%[4]s</a> 中建立了分支 <a href="%[2]s">%[3]s</a> @@ -3015,6 +3033,94 @@ error.no_unit_allowed_repo=您未被允許存取此儲存庫的任何區域。 error.unit_not_allowed=您未被允許訪問此儲存庫區域 [packages] +title=套件 +desc=管理儲存庫套件。 +empty=目前還沒有套件。 +empty.documentation=關於套件註冊中心的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">說明文件</a>。 +filter.type=類型 +filter.type.all=所有 +filter.no_result=沒有篩選結果。 +filter.container.tagged=已加標籤 +filter.container.untagged=未加標籤 +published_by=發布於 %[1]s 由 <a href="%[2]s">%[3]s</a> +published_by_in=發布於 %[1]s 由 <a href="%[2]s">%[3]s</a> 在 <a href="%[4]s"><strong>%[5]s</strong></a> +installation=安裝 +about=關於此套件 +requirements=需求 +dependencies=相依性 +keywords=關鍵字 +details=詳情 +details.author=作者 +details.project_site=專案網站 +details.license=授權條款 +assets=檔案 +versions=版本 +versions.on=於 +versions.view_all=檢視全部 dependency.id=ID dependency.version=版本 +composer.registry=在您的 <code>~/.composer/config.json</code> 檔設定此註冊中心: +composer.install=執行下列命令以使用 Composer 安裝此套件: +composer.documentation=關於 Composer registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">說明文件</a>。 +composer.dependencies=相依性 +composer.dependencies.development=開發相依性 +conan.details.repository=儲存庫 +conan.registry=透過下列命令設定此註冊中心: +conan.install=執行下列命令以使用 Conan 安裝此套件: +conan.documentation=關於 Conan registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">說明文件</a>。 +container.details.type=映像檔類型 +container.details.platform=平台 +container.details.repository_site=儲存庫網站 +container.details.documentation_site=文件網站 +container.pull=透過下列命令拉取映像檔: +container.documentation=關於 Container registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">說明文件</a>。 +container.multi_arch=作業系統 / 架構 +container.layers=映像檔 Layers +container.labels=標籤 +container.labels.key=鍵 +container.labels.value=值 +generic.download=透過下列命令下載套件: +generic.documentation=關於通用 registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">說明文件</a>。 +helm.registry=透過下列命令設定此註冊中心: +helm.install=執行下列命令安裝此套件: +helm.documentation=關於 Helm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">說明文件</a>。 +maven.registry=在您的 <code>pom.xml</code> 檔設定此註冊中心: +maven.install=若要使用此套件,請在您 <code>pom.xml</code> 檔的 <code>dependencies</code> 段落加入下列內容: +maven.install2=透過下列命令執行: +maven.download=透過下列命令下載相依性: +maven.documentation=關於 Maven registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">說明文件</a>。 +nuget.registry=透過下列命令設定此註冊中心: +nuget.install=執行下列命令以使用 NuGet 安裝此套件: +nuget.documentation=關於 NuGet registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">說明文件</a>。 +nuget.dependency.framework=目標框架 +npm.registry=在您的 <code>.npmrc</code> 檔設定此註冊中心: +npm.install=執行下列命令以使用 npm 安裝此套件: +npm.install2=或將它加到 package.json 檔: +npm.documentation=關於 npm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">說明文件</a>。 +npm.dependencies=相依性 +npm.dependencies.development=開發相依性 +npm.dependencies.peer=Peer 相依性 +npm.dependencies.optional=選用相依性 +npm.details.tag=標籤 +pypi.requires=需要 Python +pypi.install=執行下列命令以使用 pip 安裝此套件: +pypi.documentation=關於 PyPI registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">說明文件</a>。 +rubygems.install=執行下列命令以使用 gem 安裝此套件: +rubygems.install2=或將它加到 Gemfile: +rubygems.dependencies.runtime=執行階段相依性 +rubygems.dependencies.development=開發相依性 +rubygems.required.ruby=需要的 Ruby 版本 +rubygems.required.rubygems=需要的 RubyGem 版本 +rubygems.documentation=關於 RubyGems registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">說明文件</a>。 +settings.link=連結此套件到儲存庫 +settings.link.description=如果您將套件連結到儲存庫,該套件會顯示在儲存庫的套件清單。 +settings.link.select=選擇儲存庫 +settings.link.button=更新儲存庫連結 +settings.link.success=儲存庫連結更新成功。 +settings.link.error=儲存庫連結更新失敗。 +settings.delete=刪除套件 +settings.delete.description=刪除套件是永久且不可還原的。 +settings.delete.notice=您正要刪除 %s (%s),此動作是無法還原的,您確定嗎? +settings.delete.success=已刪除該套件。 +settings.delete.error=刪除套件失敗。 diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go index a1b3d53ed..23d418782 100644 --- a/routers/api/v1/activitypub/person.go +++ b/routers/api/v1/activitypub/person.go @@ -10,6 +10,8 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/forgefed" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/context" @@ -20,11 +22,11 @@ import ( ap "github.com/go-ap/activitypub" ) -// Person function +// Person function returns the Person actor for a user func Person(ctx *context.APIContext) { // swagger:operation GET /activitypub/user/{username} activitypub activitypubPerson // --- - // summary: Returns the person + // summary: Returns the Person actor for a user // produces: // - application/activity+json // parameters: @@ -68,6 +70,8 @@ func Person(ctx *context.APIContext) { person.Following = ap.IRI(link + "/following") person.Followers = ap.IRI(link + "/followers") + person.Liked = ap.IRI(link + "/liked") + person.PublicKey.ID = ap.IRI(link + "#main-key") person.PublicKey.Owner = ap.IRI(link) @@ -78,15 +82,10 @@ func Person(ctx *context.APIContext) { } person.PublicKey.PublicKeyPem = publicKeyPem - binary, err := person.MarshalJSON() - if err != nil { - ctx.Error(http.StatusInternalServerError, "Serialize", err) - return - } - response(ctx, binary) + response(ctx, person) } -// PersonInbox function +// PersonInbox function handles the incoming data for a user inbox func PersonInbox(ctx *context.APIContext) { // swagger:operation POST /activitypub/user/{username}/inbox activitypub activitypubPersonInbox // --- @@ -103,7 +102,7 @@ func PersonInbox(ctx *context.APIContext) { // "204": // "$ref": "#/responses/empty" - body, err := io.ReadAll(ctx.Req.Body) + body, err := io.ReadAll(io.LimitReader(ctx.Req.Body, setting.Federation.MaxSize)) if err != nil { ctx.Error(http.StatusInternalServerError, "Error reading request body", err) } @@ -114,16 +113,18 @@ func PersonInbox(ctx *context.APIContext) { activitypub.Follow(ctx, activity) } else { log.Warn("ActivityStreams type not supported", activity) + ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported") + return } ctx.Status(http.StatusNoContent) } -// PersonOutbox function +// PersonOutbox function returns the user's Outbox OrderedCollection func PersonOutbox(ctx *context.APIContext) { // swagger:operation GET /activitypub/user/{username}/outbox activitypub activitypubPersonOutbox // --- - // summary: Returns the outbox + // summary: Returns the Outbox OrderedCollection // produces: // - application/activity+json // parameters: @@ -160,18 +161,14 @@ func PersonOutbox(ctx *context.APIContext) { } outbox.TotalItems = uint(len(outbox.OrderedItems)) - binary, err := outbox.MarshalJSON() - if err != nil { - ctx.Error(http.StatusInternalServerError, "Serialize", err) - } - response(ctx, binary) + response(ctx, outbox) } -// PersonFollowing function +// PersonFollowing function returns the user's Following Collection func PersonFollowing(ctx *context.APIContext) { // swagger:operation GET /activitypub/user/{username}/following activitypub activitypubPersonFollowing // --- - // summary: Returns the following collection + // summary: Returns the Following Collection // produces: // - application/activity+json // parameters: @@ -201,18 +198,14 @@ func PersonFollowing(ctx *context.APIContext) { following.OrderedItems.Append(person) } - binary, err := following.MarshalJSON() - if err != nil { - ctx.Error(http.StatusInternalServerError, "Serialize", err) - } - response(ctx, binary) + response(ctx, following) } -// PersonFollowers function +// PersonFollowers function returns the user's Followers Collection func PersonFollowers(ctx *context.APIContext) { // swagger:operation GET /activitypub/user/{username}/followers activitypub activitypubPersonFollowers // --- - // summary: Returns the followers collection + // summary: Returns the Followers Collection // produces: // - application/activity+json // parameters: @@ -241,9 +234,45 @@ func PersonFollowers(ctx *context.APIContext) { followers.OrderedItems.Append(person) } - binary, err := followers.MarshalJSON() + response(ctx, followers) +} + +// PersonLiked function returns the user's Liked Collection +func PersonLiked(ctx *context.APIContext) { + // swagger:operation GET /activitypub/user/{username}/followers activitypub activitypubPersonLiked + // --- + // summary: Returns the Liked Collection + // produces: + // - application/activity+json + // parameters: + // - name: username + // in: path + // description: username of the user + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActivityPub" + + link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/") + + repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{ + Actor: ctx.Doer, + Private: ctx.IsSigned, + StarredByID: ctx.ContextUser.ID, + }) if err != nil { - ctx.Error(http.StatusInternalServerError, "Serialize", err) + ctx.Error(http.StatusInternalServerError, "GetUserStarred", err) + return } - response(ctx, binary) + + liked := ap.OrderedCollectionNew(ap.IRI(link)) + liked.TotalItems = uint(count) + + for _, repo := range repos { + repo := forgefed.RepositoryNew(ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + repo.OwnerName + "/" + repo.Name)) + liked.OrderedItems.Append(repo) + } + + response(ctx, liked) } diff --git a/routers/api/v1/activitypub/repo.go b/routers/api/v1/activitypub/repo.go new file mode 100644 index 000000000..f2abb3977 --- /dev/null +++ b/routers/api/v1/activitypub/repo.go @@ -0,0 +1,198 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package activitypub + +import ( + "io" + "net/http" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/forgefed" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/api/v1/utils" + + ap "github.com/go-ap/activitypub" +) + +// Repo function +func Repo(ctx *context.APIContext) { + // swagger:operation GET /activitypub/user/{username}/{reponame} activitypub activitypubRepo + // --- + // summary: Returns the repository + // produces: + // - application/activity+json + // parameters: + // - name: username + // in: path + // description: username of the user + // type: string + // required: true + // - name: reponame + // in: path + // description: name of the repository + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActivityPub" + + link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/") + repo := forgefed.RepositoryNew(ap.IRI(link)) + + repo.Name = ap.NaturalLanguageValuesNew() + err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "Set Name", err) + return + } + + repo.AttributedTo = ap.IRI(strings.TrimSuffix(link, "/"+ctx.Repo.Repository.Name)) + + repo.Summary = ap.NaturalLanguageValuesNew() + err = repo.Summary.Set("en", ap.Content(ctx.Repo.Repository.Description)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "Set Description", err) + return + } + + repo.Inbox = ap.IRI(link + "/inbox") + repo.Outbox = ap.IRI(link + "/outbox") + repo.Followers = ap.IRI(link + "/followers") + repo.Team = ap.IRI(link + "/team") + + response(ctx, repo) +} + +// RepoInbox function +func RepoInbox(ctx *context.APIContext) { + // swagger:operation POST /activitypub/user/{username}/{reponame}/inbox activitypub activitypubRepoInbox + // --- + // summary: Send to the inbox + // produces: + // - application/activity+json + // parameters: + // - name: username + // in: path + // description: username of the user + // type: string + // required: true + // - name: reponame + // in: path + // description: name of the repository + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + + body, err := io.ReadAll(ctx.Req.Body) + if err != nil { + ctx.Error(http.StatusInternalServerError, "Error reading request body", err) + } + + var activity ap.Activity + activity.UnmarshalJSON(body) + if activity.Type == ap.FollowType { + // activitypub.Follow(ctx, activity) + } else { + log.Warn("ActivityStreams type not supported", activity) + } + + ctx.Status(http.StatusNoContent) +} + +// RepoOutbox function +func RepoOutbox(ctx *context.APIContext) { + // swagger:operation GET /activitypub/user/{username}/outbox activitypub activitypubPersonOutbox + // --- + // summary: Returns the outbox + // produces: + // - application/activity+json + // parameters: + // - name: username + // in: path + // description: username of the user + // type: string + // required: true + // - name: reponame + // in: path + // description: name of the repository + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActivityPub" + + link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/") + + feed, err := models.GetFeeds(ctx, models.GetFeedsOptions{ + RequestedUser: ctx.ContextUser, + Actor: ctx.ContextUser, + IncludePrivate: false, + OnlyPerformedBy: true, + IncludeDeleted: false, + Date: ctx.FormString("date"), + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "Couldn't fetch outbox", err) + } + + outbox := ap.OrderedCollectionNew(ap.IRI(link)) + for _, action := range feed { + /*if action.OpType == ExampleType { + activity := ap.ExampleNew() + outbox.OrderedItems.Append(activity) + }*/ + log.Debug(action.Content) + } + outbox.TotalItems = uint(len(outbox.OrderedItems)) + + response(ctx, outbox) +} + +// RepoFollowers function +func RepoFollowers(ctx *context.APIContext) { + // swagger:operation GET /activitypub/user/{username}/{reponame}/followers activitypub activitypubRepoFollowers + // --- + // summary: Returns the followers collection + // produces: + // - application/activity+json + // parameters: + // - name: username + // in: path + // description: username of the user + // type: string + // required: true + // - name: reponame + // in: path + // description: name of the repository + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActivityPub" + + link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/") + + users, err := user_model.GetUserFollowers(ctx.ContextUser, utils.GetListOptions(ctx)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) + return + } + + followers := ap.OrderedCollectionNew(ap.IRI(link)) + followers.TotalItems = uint(len(users)) + + for _, user := range users { + person := ap.PersonNew(ap.IRI(user.Website)) + followers.OrderedItems.Append(person) + } + + response(ctx, followers) +} diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index 075556438..d5b29fddf 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -70,8 +70,8 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er return } -// ReqSignature function -func ReqSignature() func(ctx *gitea_context.APIContext) { +// ReqHTTPSignature function +func ReqHTTPSignature() func(ctx *gitea_context.APIContext) { return func(ctx *gitea_context.APIContext) { if authenticated, err := verifyHTTPSignatures(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "verifyHttpSignatures", err) diff --git a/routers/api/v1/activitypub/response.go b/routers/api/v1/activitypub/response.go index 1aa361982..6f3bdf91c 100644 --- a/routers/api/v1/activitypub/response.go +++ b/routers/api/v1/activitypub/response.go @@ -9,27 +9,20 @@ import ( "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + + ap "github.com/go-ap/activitypub" + "github.com/go-ap/jsonld" ) -func response(ctx *context.APIContext, binary []byte) { - var jsonmap map[string]interface{} - err := json.Unmarshal(binary, &jsonmap) +func response(ctx *context.APIContext, v interface{}) { + binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(v) if err != nil { - ctx.Error(http.StatusInternalServerError, "Unmarshal", err) + ctx.Error(http.StatusInternalServerError, "Marshal", err) return } - - jsonmap["@context"] = []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"} - ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType) ctx.Resp.WriteHeader(http.StatusOK) - binary, err = json.Marshal(jsonmap) - if err != nil { - ctx.Error(http.StatusInternalServerError, "Marshal", err) - return - } if _, err = ctx.Resp.Write(binary); err != nil { log.Error("write to resp err: %v", err) } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 34ce9cc0a..2e976e966 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -647,10 +647,17 @@ func Routes() *web.Route { m.Group("/activitypub", func() { m.Group("/user/{username}", func() { m.Get("", activitypub.Person) - m.Post("/inbox", activitypub.ReqSignature(), activitypub.PersonInbox) + m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) m.Get("/outbox", activitypub.PersonOutbox) m.Get("/following", activitypub.PersonFollowing) m.Get("/followers", activitypub.PersonFollowers) + m.Get("/liked", activitypub.PersonLiked) + m.Group("/{reponame}", func() { + m.Get("", activitypub.Repo) + m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.RepoInbox) + m.Get("/outbox", activitypub.RepoOutbox) + m.Get("/followers", activitypub.RepoFollowers) + }, repoAssignment()) }, context_service.UserAssignmentAPI()) }) } diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index e454b418b..8dfe7e06d 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -345,6 +345,8 @@ func DeleteRelease(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "404": // "$ref": "#/responses/notFound" + // "405": + // "$ref": "#/responses/empty" id := ctx.ParamsInt64(":id") rel, err := models.GetReleaseByID(ctx, id) @@ -358,6 +360,10 @@ func DeleteRelease(ctx *context.APIContext) { return } if err := release_service.DeleteReleaseByID(ctx, id, ctx.Doer, false); err != nil { + if models.IsErrProtectedTagName(err) { + ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag") + return + } ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err) return } diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go index a737bcf1c..73dee73e1 100644 --- a/routers/api/v1/repo/release_tags.go +++ b/routers/api/v1/repo/release_tags.go @@ -92,6 +92,8 @@ func DeleteReleaseByTag(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "404": // "$ref": "#/responses/notFound" + // "405": + // "$ref": "#/responses/empty" tag := ctx.Params(":tag") @@ -111,7 +113,12 @@ func DeleteReleaseByTag(ctx *context.APIContext) { } if err = releaseservice.DeleteReleaseByID(ctx, release.ID, ctx.Doer, false); err != nil { + if models.IsErrProtectedTagName(err) { + ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag") + return + } ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err) + return } ctx.Status(http.StatusNoContent) diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 894291275..433d823c7 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -176,6 +176,8 @@ func CreateTag(ctx *context.APIContext) { // "$ref": "#/responses/Tag" // "404": // "$ref": "#/responses/notFound" + // "405": + // "$ref": "#/responses/empty" // "409": // "$ref": "#/responses/conflict" form := web.GetForm(ctx).(*api.CreateTagOption) @@ -196,6 +198,11 @@ func CreateTag(ctx *context.APIContext) { ctx.Error(http.StatusConflict, "tag exist", err) return } + if models.IsErrProtectedTagName(err) { + ctx.Error(http.StatusMethodNotAllowed, "CreateNewTag", "user not allowed to create protected tag") + return + } + ctx.InternalServerError(err) return } @@ -236,6 +243,8 @@ func DeleteTag(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "404": // "$ref": "#/responses/notFound" + // "405": + // "$ref": "#/responses/empty" // "409": // "$ref": "#/responses/conflict" tagName := ctx.Params("*") @@ -256,7 +265,12 @@ func DeleteTag(ctx *context.APIContext) { } if err = releaseservice.DeleteReleaseByID(ctx, tag.ID, ctx.Doer, true); err != nil { + if models.IsErrProtectedTagName(err) { + ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag") + return + } ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err) + return } ctx.Status(http.StatusNoContent) diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go index 81dbc1fd1..0653f8b0d 100644 --- a/routers/private/hook_proc_receive.go +++ b/routers/private/hook_proc_receive.go @@ -8,8 +8,10 @@ package private import ( "net/http" + repo_model "code.gitea.io/gitea/models/repo" gitea_context "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/agit" @@ -23,8 +25,17 @@ func HookProcReceive(ctx *gitea_context.PrivateContext) { return } - results := agit.ProcReceive(ctx, opts) - if ctx.Written() { + results, err := agit.ProcReceive(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, opts) + if err != nil { + if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { + ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) + } else { + log.Error(err.Error()) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "Err": err.Error(), + }) + } + return } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f64661596..d868b05a4 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -215,7 +215,7 @@ func newAccessTokenResponse(ctx stdContext.Context, grant *auth.OAuth2Grant, ser Nonce: grant.Nonce, } if grant.ScopeContains("profile") { - idToken.Name = user.FullName + idToken.Name = user.GetDisplayName() idToken.PreferredUsername = user.Name idToken.Profile = user.HTMLURL() idToken.Picture = user.AvatarLink() diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 5a09a9510..57f2477db 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/auth/source/oauth2" "github.com/golang-jwt/jwt/v4" @@ -66,6 +67,24 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { // Scopes: openid profile email oidcToken = createAndParseToken(t, grants[0]) + assert.Equal(t, user.Name, oidcToken.Name) + assert.Equal(t, user.Name, oidcToken.PreferredUsername) + assert.Equal(t, user.HTMLURL(), oidcToken.Profile) + assert.Equal(t, user.AvatarLink(), oidcToken.Picture) + assert.Equal(t, user.Website, oidcToken.Website) + assert.Equal(t, user.UpdatedUnix, oidcToken.UpdatedAt) + assert.Equal(t, user.Email, oidcToken.Email) + assert.Equal(t, user.IsActive, oidcToken.EmailVerified) + + // set DefaultShowFullName to true + oldDefaultShowFullName := setting.UI.DefaultShowFullName + setting.UI.DefaultShowFullName = true + defer func() { + setting.UI.DefaultShowFullName = oldDefaultShowFullName + }() + + // Scopes: openid profile email + oidcToken = createAndParseToken(t, grants[0]) assert.Equal(t, user.FullName, oidcToken.Name) assert.Equal(t, user.Name, oidcToken.PreferredUsername) assert.Equal(t, user.HTMLURL(), oidcToken.Profile) diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 3fba2be37..3afb2110d 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -7,9 +7,7 @@ package explore import ( "net/http" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" code_indexer "code.gitea.io/gitea/modules/indexer/code" @@ -44,106 +42,78 @@ func Code(ctx *context.Context) { queryType := ctx.FormTrim("t") isMatch := queryType == "match" - var ( - repoIDs []int64 - err error - isAdmin bool - ) - if ctx.Doer != nil { - isAdmin = ctx.Doer.IsAdmin - } - - // guest user or non-admin user - if ctx.Doer == nil || !isAdmin { - repoIDs, err = repo_model.FindUserAccessibleRepoIDs(ctx.Doer) - if err != nil { - ctx.ServerError("SearchResults", err) - return - } - } - - var ( - total int - searchResults []*code_indexer.Result - searchResultLanguages []*code_indexer.SearchResultLanguages - ) - - // if non-admin login user, we need check UnitTypeCode at first - if ctx.Doer != nil && len(repoIDs) > 0 { - repoMaps, err := repo_model.GetRepositoriesMapByIDs(repoIDs) - if err != nil { - ctx.ServerError("SearchResults", err) - return + if keyword != "" { + var ( + repoIDs []int64 + err error + isAdmin bool + ) + if ctx.Doer != nil { + isAdmin = ctx.Doer.IsAdmin } - rightRepoMap := make(map[int64]*repo_model.Repository, len(repoMaps)) - repoIDs = make([]int64, 0, len(repoMaps)) - for id, repo := range repoMaps { - if models.CheckRepoUnitUser(ctx, repo, ctx.Doer, unit.TypeCode) { - rightRepoMap[id] = repo - repoIDs = append(repoIDs, id) - } - } - - ctx.Data["RepoMaps"] = rightRepoMap - - total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) - if err != nil { - if code_indexer.IsAvailable() { + // guest user or non-admin user + if ctx.Doer == nil || !isAdmin { + repoIDs, err = repo_model.FindUserCodeAccessibleRepoIDs(ctx.Doer) + if err != nil { ctx.ServerError("SearchResults", err) return } - ctx.Data["CodeIndexerUnavailable"] = true - } else { - ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable() } - // if non-login user or isAdmin, no need to check UnitTypeCode - } else if (ctx.Doer == nil && len(repoIDs) > 0) || isAdmin { - total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) - if err != nil { - if code_indexer.IsAvailable() { - ctx.ServerError("SearchResults", err) - return + + var ( + total int + searchResults []*code_indexer.Result + searchResultLanguages []*code_indexer.SearchResultLanguages + ) + + if (len(repoIDs) > 0) || isAdmin { + total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + if err != nil { + if code_indexer.IsAvailable() { + ctx.ServerError("SearchResults", err) + return + } + ctx.Data["CodeIndexerUnavailable"] = true + } else { + ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable() } - ctx.Data["CodeIndexerUnavailable"] = true - } else { - ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable() - } - loadRepoIDs := make([]int64, 0, len(searchResults)) - for _, result := range searchResults { - var find bool - for _, id := range loadRepoIDs { - if id == result.RepoID { - find = true - break + loadRepoIDs := make([]int64, 0, len(searchResults)) + for _, result := range searchResults { + var find bool + for _, id := range loadRepoIDs { + if id == result.RepoID { + find = true + break + } + } + if !find { + loadRepoIDs = append(loadRepoIDs, result.RepoID) } } - if !find { - loadRepoIDs = append(loadRepoIDs, result.RepoID) + + repoMaps, err := repo_model.GetRepositoriesMapByIDs(loadRepoIDs) + if err != nil { + ctx.ServerError("SearchResults", err) + return } - } - repoMaps, err := repo_model.GetRepositoriesMapByIDs(loadRepoIDs) - if err != nil { - ctx.ServerError("SearchResults", err) - return + ctx.Data["RepoMaps"] = repoMaps } - ctx.Data["RepoMaps"] = repoMaps + ctx.Data["Keyword"] = keyword + ctx.Data["Language"] = language + ctx.Data["queryType"] = queryType + ctx.Data["SearchResults"] = searchResults + ctx.Data["SearchResultLanguages"] = searchResultLanguages + ctx.Data["PageIsViewCode"] = true + + pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5) + pager.SetDefaultParams(ctx) + pager.AddParam(ctx, "l", "Language") + ctx.Data["Page"] = pager } - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["queryType"] = queryType - ctx.Data["SearchResults"] = searchResults - ctx.Data["SearchResultLanguages"] = searchResultLanguages - ctx.Data["PageIsViewCode"] = true - - pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5) - pager.SetDefaultParams(ctx) - pager.AddParam(ctx, "l", "Language") - ctx.Data["Page"] = pager - ctx.HTML(http.StatusOK, tplExploreCode) } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 4bd2af4e8..84970a96a 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -373,6 +373,12 @@ func CreateBranch(ctx *context.Context) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName) } if err != nil { + if models.IsErrProtectedTagName(err) { + ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if models.IsErrTagAlreadyExists(err) { e := err.(models.ErrTagAlreadyExists) ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 605594d5a..5c46882f3 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -139,7 +139,7 @@ func setCsvCompareContext(ctx *context.Context) { return csvReader, reader, err } - baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, Filename: diffFile.OldName}, baseCommit) + baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.OldName}, baseCommit) if baseBlobCloser != nil { defer baseBlobCloser.Close() } @@ -151,7 +151,7 @@ func setCsvCompareContext(ctx *context.Context) { return CsvDiffResult{nil, "unable to load file from base commit"} } - headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, Filename: diffFile.Name}, headCommit) + headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.Name}, headCommit) if headBlobCloser != nil { defer headBlobCloser.Close() } diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 68f89b258..97d4441a2 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -99,7 +99,9 @@ func GetActiveStopwatch(ctx *context.Context) { issue, err := issues_model.GetIssueByID(ctx, sw.IssueID) if err != nil || issue == nil { - ctx.ServerError("GetIssueByID", err) + if !issues_model.IsErrIssueNotExist(err) { + ctx.ServerError("GetIssueByID", err) + } return } if err = issue.LoadRepo(ctx); err != nil { diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index fba3ef7a0..666294631 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -519,7 +519,11 @@ func DeleteTag(ctx *context.Context) { func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) { if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil { - ctx.Flash.Error("DeleteReleaseByID: " + err.Error()) + if models.IsErrProtectedTagName(err) { + ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) + } else { + ctx.Flash.Error("DeleteReleaseByID: " + err.Error()) + } } else { if isDelTag { ctx.Flash.Success(ctx.Tr("repo.release.deletion_tag_success")) diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go new file mode 100644 index 000000000..28a6d2f42 --- /dev/null +++ b/routers/web/repo/render.go @@ -0,0 +1,79 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "bytes" + "io" + "net/http" + "path" + + "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/typesniffer" + "code.gitea.io/gitea/modules/util" +) + +// RenderFile renders a file by repos path +func RenderFile(ctx *context.Context) { + blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath) + if err != nil { + if git.IsErrNotExist(err) { + ctx.NotFound("GetBlobByPath", err) + } else { + ctx.ServerError("GetBlobByPath", err) + } + return + } + + dataRc, err := blob.DataAsync() + if err != nil { + ctx.ServerError("DataAsync", err) + return + } + defer dataRc.Close() + + buf := make([]byte, 1024) + n, _ := util.ReadAtMost(dataRc, buf) + buf = buf[:n] + + st := typesniffer.DetectContentType(buf) + isTextFile := st.IsText() + + rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc)) + + if markupType := markup.Type(blob.Name()); markupType == "" { + if isTextFile { + _, err = io.Copy(ctx.Resp, rd) + if err != nil { + ctx.ServerError("Copy", err) + } + return + } + ctx.Error(http.StatusInternalServerError, "Unsupported file type render") + return + } + + treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() + if ctx.Repo.TreePath != "" { + treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + } + + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts") + err = markup.Render(&markup.RenderContext{ + Ctx: ctx, + RelativePath: ctx.Repo.TreePath, + URLPrefix: path.Dir(treeLink), + Metas: ctx.Repo.Repository.ComposeDocumentMetas(), + GitRepo: ctx.Repo.GitRepo, + InStandalonePage: true, + }, rd, ctx.Resp) + if err != nil { + ctx.ServerError("Render", err) + return + } +} diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 01bd2d892..c327f959f 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -356,11 +356,11 @@ func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelin ctx.Data["MarkupType"] = string(markupType) var result strings.Builder err := markup.Render(&markup.RenderContext{ - Ctx: ctx, - Filename: readmeFile.name, - URLPrefix: readmeTreelink, - Metas: ctx.Repo.Repository.ComposeDocumentMetas(), - GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.name), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path). + URLPrefix: readmeTreelink, + Metas: ctx.Repo.Repository.ComposeDocumentMetas(), + GitRepo: ctx.Repo.GitRepo, }, rd, &result) if err != nil { log.Error("Render failed: %v then fallback", err) @@ -528,18 +528,22 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st if !detected { markupType = "" } + metas := ctx.Repo.Repository.ComposeDocumentMetas() + metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() err := markup.Render(&markup.RenderContext{ - Ctx: ctx, - Type: markupType, - Filename: blob.Name(), - URLPrefix: path.Dir(treeLink), - Metas: ctx.Repo.Repository.ComposeDocumentMetas(), - GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + Type: markupType, + RelativePath: ctx.Repo.TreePath, + URLPrefix: path.Dir(treeLink), + Metas: metas, + GitRepo: ctx.Repo.GitRepo, }, rd, &result) if err != nil { ctx.ServerError("Render", err) return } + // to prevent iframe load third-party url + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'") ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) } else if readmeExist && !shouldRenderSource { buf := &bytes.Buffer{} @@ -627,11 +631,11 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ctx.Data["MarkupType"] = markupType var result strings.Builder err := markup.Render(&markup.RenderContext{ - Ctx: ctx, - Filename: blob.Name(), - URLPrefix: path.Dir(treeLink), - Metas: ctx.Repo.Repository.ComposeDocumentMetas(), - GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + RelativePath: ctx.Repo.TreePath, + URLPrefix: path.Dir(treeLink), + Metas: ctx.Repo.Repository.ComposeDocumentMetas(), + GitRepo: ctx.Repo.GitRepo, }, rd, &result) if err != nil { ctx.ServerError("Render", err) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 7cd87eb30..bf2660881 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -282,6 +282,7 @@ func Profile(ctx *context.Context) { pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) + pager.AddParam(ctx, "tab", "TabName") if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" { pager.AddParam(ctx, "language", "Language") } diff --git a/routers/web/web.go b/routers/web/web.go index ad005f74d..374bafbc8 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1161,6 +1161,13 @@ func RegisterRoutes(m *web.Route) { m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload) }, repo.MustBeNotEmpty, reqRepoCodeReader) + m.Group("/render", func() { + m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RenderFile) + m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RenderFile) + m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RenderFile) + m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.RenderFile) + }, repo.MustBeNotEmpty, reqRepoCodeReader) + m.Group("/commits", func() { m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits) m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits) diff --git a/services/agit/agit.go b/services/agit/agit.go index 7666093c5..4359557a1 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -5,15 +5,14 @@ package agit import ( + "context" "fmt" - "net/http" "os" "strings" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" @@ -22,7 +21,7 @@ import ( ) // ProcReceive handle proc receive work -func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []private.HookProcReceiveRefResult { +func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) { // TODO: Add more options? var ( topicBranch string @@ -32,10 +31,9 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva ) results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs)) - repo := ctx.Repo.Repository - gitRepo := ctx.Repo.GitRepo - ownerName := ctx.Repo.Repository.OwnerName - repoName := ctx.Repo.Repository.Name + + ownerName := repo.OwnerName + repoName := repo.Name topicBranch = opts.GitPushOptions["topic"] _, forcePush = opts.GitPushOptions["force-push"] @@ -101,11 +99,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva pr, err := issues_model.GetUnmergedPullRequest(repo.ID, repo.ID, headBranch, baseBranchName, issues_model.PullRequestFlowAGit) if err != nil { if !issues_model.IsErrPullRequestNotExist(err) { - log.Error("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return nil + return nil, fmt.Errorf("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err) } // create a new pull request @@ -115,11 +109,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva if !has || len(title) == 0 { commit, err := gitRepo.GetCommit(opts.NewCommitIDs[i]) if err != nil { - log.Error("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err), - }) - return nil + return nil, fmt.Errorf("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err) } title = strings.Split(commit.CommitMessage, "\n")[0] } @@ -128,11 +118,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva pusher, err := user_model.GetUserByID(opts.UserID) if err != nil { - log.Error("Failed to get user. Error: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to get user. Error: %v", err), - }) - return nil + return nil, fmt.Errorf("Failed to get user. Error: %v", err) } prIssue := &issues_model.Issue{ @@ -158,12 +144,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva } if err := pull_service.NewPullRequest(ctx, repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil { - if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { - ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) - return nil - } - ctx.Error(http.StatusInternalServerError, "NewPullRequest", err.Error()) - return nil + return nil, err } log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) @@ -179,20 +160,12 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva // update exist pull request if err := pr.LoadBaseRepoCtx(ctx); err != nil { - log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Unable to load base repository for PR[%d] Error: %v", pr.ID, err), - }) - return nil + return nil, fmt.Errorf("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) } oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { - log.Error("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err), - }) - return nil + return nil, fmt.Errorf("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err) } if oldCommitID == opts.NewCommitIDs[i] { @@ -208,11 +181,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva if !forcePush { output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1", oldCommitID, "^"+opts.NewCommitIDs[i]).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) if err != nil { - log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, opts.NewCommitIDs[i], repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Fail to detect force push: %v", err), - }) - return nil + return nil, fmt.Errorf("Fail to detect force push: %v", err) } else if len(output) > 0 { results = append(results, private.HookProcReceiveRefResult{ OriginalRef: opts.RefFullNames[i], @@ -226,29 +195,17 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva pr.HeadCommitID = opts.NewCommitIDs[i] if err = pull_service.UpdateRef(ctx, pr); err != nil { - log.Error("Failed to update pull ref. Error: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to update pull ref. Error: %v", err), - }) - return nil + return nil, fmt.Errorf("Failed to update pull ref. Error: %v", err) } pull_service.AddToTaskQueue(pr) pusher, err := user_model.GetUserByID(opts.UserID) if err != nil { - log.Error("Failed to get user. Error: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to get user. Error: %v", err), - }) - return nil + return nil, fmt.Errorf("Failed to get user. Error: %v", err) } err = pr.LoadIssue() if err != nil { - log.Error("Failed to load pull issue. Error: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to load pull issue. Error: %v", err), - }) - return nil + return nil, fmt.Errorf("Failed to load pull issue. Error: %v", err) } comment, err := issues_model.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i]) if err == nil && comment != nil { @@ -266,7 +223,7 @@ func ProcReceive(ctx *context.PrivateContext, opts *private.HookOptions) []priva }) } - return results + return results, nil } // UserNameChanged handle user name change for agit flow pull diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 97daadbc6..37dc0e114 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1417,37 +1417,8 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff } diff.Start = opts.SkipTo - var checker *git.CheckAttributeReader - - if git.CheckGitVersionAtLeast("1.7.8") == nil { - indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(opts.AfterCommitID) - if err == nil { - defer deleteTemporaryFile() - - checker = &git.CheckAttributeReader{ - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, - Repo: gitRepo, - IndexFile: indexFilename, - WorkTree: worktree, - } - ctx, cancel := context.WithCancel(gitRepo.Ctx) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open checker for %s. Error: %v", opts.AfterCommitID, err) - } else { - go func() { - err := checker.Run() - if err != nil && err != ctx.Err() { - log.Error("Unable to open checker for %s. Error: %v", opts.AfterCommitID, err) - } - cancel() - }() - } - defer func() { - _ = checker.Close() - cancel() - }() - } - } + checker, deferable := gitRepo.CheckAttributeReader(opts.AfterCommitID) + defer deferable() for _, diffFile := range diff.Files { diff --git a/services/migrations/dump.go b/services/migrations/dump.go index ad04756d4..21d03b333 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -159,6 +159,9 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp if err != nil { return fmt.Errorf("Clone: %v", err) } + if err := git.WriteCommitGraph(g.ctx, repoPath); err != nil { + return err + } if opts.Wiki { wikiPath := g.wikiPath() @@ -179,6 +182,8 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp if err := os.RemoveAll(wikiPath); err != nil { return fmt.Errorf("Failed to remove %s: %v", wikiPath, err) } + } else if err := git.WriteCommitGraph(g.ctx, wikiPath); err != nil { + return err } } } diff --git a/services/pull/merge.go b/services/pull/merge.go index aff800a1b..e8bb3a1cd 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -276,15 +276,8 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode return "", fmt.Errorf("Unable to write .git/info/sparse-checkout file in tmpBasePath: %v", err) } - var gitConfigCommand func() *git.Command - if git.CheckGitVersionAtLeast("1.8.0") == nil { - gitConfigCommand = func() *git.Command { - return git.NewCommand(ctx, "config", "--local") - } - } else { - gitConfigCommand = func() *git.Command { - return git.NewCommand(ctx, "config") - } + gitConfigCommand := func() *git.Command { + return git.NewCommand(ctx, "config", "--local") } // Switch off LFS process (set required, clean and smudge here also) @@ -366,16 +359,14 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode // Determine if we should sign signArg := "" - if git.CheckGitVersionAtLeast("1.7.9") == nil { - sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) - if sign { - signArg = "-S" + keyID - if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { - committer = signer - } - } else if git.CheckGitVersionAtLeast("2.0.0") == nil { - signArg = "--no-gpg-sign" + sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) + if sign { + signArg = "-S" + keyID + if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { + committer = signer } + } else { + signArg = "--no-gpg-sign" } commitTimeStr := time.Now().Format(time.RFC3339) diff --git a/services/release/release.go b/services/release/release.go index a3d7d4720..6fa966de1 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -294,6 +294,20 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del } if delTag { + protectedTags, err := git_model.GetProtectedTags(rel.RepoID) + if err != nil { + return fmt.Errorf("GetProtectedTags: %v", err) + } + isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID) + if err != nil { + return err + } + if !isAllowed { + return models.ErrProtectedTagName{ + TagName: rel.TagName, + } + } + if stdout, _, err := git.NewCommand(ctx, "tag", "-d", rel.TagName). SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)). RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 97a80a96b..1e60d5561 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -248,34 +248,31 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co args = []string{"commit-tree", treeHash} } - // Determine if we should sign - if git.CheckGitVersionAtLeast("1.7.9") == nil { - var sign bool - var keyID string - var signer *git.Signature - if parent != "" { - sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent) - } else { - sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author) - } - if sign { - args = append(args, "-S"+keyID) - if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { - if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { - // Add trailers - _, _ = messageBytes.WriteString("\n") - _, _ = messageBytes.WriteString("Co-authored-by: ") - _, _ = messageBytes.WriteString(committerSig.String()) - _, _ = messageBytes.WriteString("\n") - _, _ = messageBytes.WriteString("Co-committed-by: ") - _, _ = messageBytes.WriteString(committerSig.String()) - _, _ = messageBytes.WriteString("\n") - } - committerSig = signer + var sign bool + var keyID string + var signer *git.Signature + if parent != "" { + sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent) + } else { + sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author) + } + if sign { + args = append(args, "-S"+keyID) + if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { + if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { + // Add trailers + _, _ = messageBytes.WriteString("\n") + _, _ = messageBytes.WriteString("Co-authored-by: ") + _, _ = messageBytes.WriteString(committerSig.String()) + _, _ = messageBytes.WriteString("\n") + _, _ = messageBytes.WriteString("Co-committed-by: ") + _, _ = messageBytes.WriteString(committerSig.String()) + _, _ = messageBytes.WriteString("\n") } - } else if git.CheckGitVersionAtLeast("2.0.0") == nil { - args = append(args, "--no-gpg-sign") + committerSig = signer } + } else { + args = append(args, "--no-gpg-sign") } if signoff { diff --git a/templates/admin/repo/search.tmpl b/templates/admin/repo/search.tmpl index e51d50936..7b9c44afd 100644 --- a/templates/admin/repo/search.tmpl +++ b/templates/admin/repo/search.tmpl @@ -12,10 +12,10 @@ <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> - <a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a> - <a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a> - <a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a> - <a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a> + <a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a> + <a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a> + <a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a> + <a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a> <a class="{{if eq .SortType "size"}}active{{end}} item" href="{{$.Link}}?sort=size&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.by_size"}}</a> <a class="{{if eq .SortType "reversesize"}}active{{end}} item" href="{{$.Link}}?sort=reversesize&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_by_size"}}</a> </div> diff --git a/templates/explore/code.tmpl b/templates/explore/code.tmpl index 2801cdfaf..6bd8be7af 100644 --- a/templates/explore/code.tmpl +++ b/templates/explore/code.tmpl @@ -3,7 +3,6 @@ {{template "explore/navbar" .}} <div class="ui container"> <form class="ui form ignore-dirty" style="max-width: 100%"> - <input type="hidden" name="tab" value="{{$.TabName}}"> <div class="ui fluid action input"> <input name="q" value="{{.Keyword}}"{{if .CodeIndexerUnavailable }} disabled{{end}} placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> <div class="ui dropdown selection{{if .CodeIndexerUnavailable }} disabled{{end}}"> diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 03f1928e2..1bc831d9d 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -40,7 +40,7 @@ </div> <div class="metas df ac"> {{if .PrimaryLanguage }} - <a href="{{$.Link}}?tab={{$.TabName}}&q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}"> + <a href="{{$.Link}}?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}"> <span class="text grey df ac mr-3"><i class="color-icon mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{ .PrimaryLanguage.Language }}</span> </a> {{end}} diff --git a/templates/explore/repo_search.tmpl b/templates/explore/repo_search.tmpl index 3d45305df..a937cad64 100644 --- a/templates/explore/repo_search.tmpl +++ b/templates/explore/repo_search.tmpl @@ -6,23 +6,22 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> - <a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> - <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> - <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> - <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> - <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> - <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> + <a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> + <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> + <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> + <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> + <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> + <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> {{if not .DisableStars}} - <a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a> - <a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a> + <a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a> + <a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a> {{end}} - <a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a> - <a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&tab={{$.TabName}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a> + <a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a> + <a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&language={{$.Language}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a> </div> </div> </div> <form class="ui form ignore-dirty" style="max-width: 90%"> - <input type="hidden" name="tab" value="{{$.TabName}}"> <input type="hidden" name="sort" value="{{$.SortType}}"> <input type="hidden" name="language" value="{{$.Language}}"> <div class="ui fluid action input"> diff --git a/templates/explore/search.tmpl b/templates/explore/search.tmpl index 2c1687f60..c83c75e27 100644 --- a/templates/explore/search.tmpl +++ b/templates/explore/search.tmpl @@ -6,17 +6,16 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> - <a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> - <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> - <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> - <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> - <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> - <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> + <a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> + <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> + <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> + <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> + <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> + <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> </div> </div> </div> <form class="ui form ignore-dirty" style="max-width: 90%"> - <input type="hidden" name="tab" value="{{$.TabName}}"> <div class="ui fluid action input"> <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> <button class="ui primary button">{{.i18n.Tr "explore.search"}}</button> diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 73c6702a9..1a0323f5d 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -74,7 +74,7 @@ </div> {{end}} <div class="fitted item mx-0"> - <a href="{{.BaseRepo.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button"> + <a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button"> {{.i18n.Tr "repo.find_file.go_to_file"}} </a> </div> diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index d2282f07f..5a23bfa33 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -283,7 +283,7 @@ {{end}} {{end}} - {{if (gt .Issue.PullRequest.CommitsBehind 0)}} + {{if and (gt .Issue.PullRequest.CommitsBehind 0) (not .Issue.IsClosed) (not .Issue.PullRequest.IsChecking) (not .IsPullFilesConflicted) (not .IsPullRequestBroken) (not $canAutoMerge)}} <div class="ui divider"></div> <div class="item item-section"> <div class="item-section-left"> @@ -481,44 +481,6 @@ {{end}} {{end}} - {{if and (gt .Issue.PullRequest.CommitsBehind 0) (not .Issue.IsClosed) (not .Issue.PullRequest.IsChecking) (not .IsPullFilesConflicted) (not .IsPullRequestBroken) (not $canAutoMerge)}} - <div class="item df ac sb"> - <div> - <i class="icon icon-octicon">{{svg "octicon-alert"}}</i> - {{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}} - </div> - <div> - {{if and .UpdateAllowed .UpdateByRebaseAllowed }} - <div class="dib"> - <div class="ui buttons update-button"> - <button class="ui button" data-do="{{.Link}}/update" data-redirect="{{.Link}}"> - <span class="button-text"> - {{$.i18n.Tr "repo.pulls.update_branch"}} - </span> - </button> - - <div class="ui dropdown icon button no-text"> - {{svg "octicon-triangle-down" 14 "dropdown icon"}} - <div class="menu"> - <div class="item active selected" data-do="{{.Link}}/update">{{$.i18n.Tr "repo.pulls.update_branch"}}</div> - <div class="item" data-do="{{.Link}}/update?style=rebase">{{$.i18n.Tr "repo.pulls.update_branch_rebase"}}</div> - </div> - </div> - </div> - </div> - {{end}} - {{if and .UpdateAllowed (not .UpdateByRebaseAllowed)}} - <form action="{{.Link}}/update" method="post"> - {{.CsrfTokenHtml}} - <button class="ui compact button" data-do="update"> - <span class="ui text">{{$.i18n.Tr "repo.pulls.update_branch"}}</span> - </button> - </form> - {{end}} - </div> - </div> - {{end}} - {{if $.StillCanManualMerge}} <div class="ui divider"></div> <div class="ui form manually-merged-fields" style="display: none"> diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index cd3625858..23d700ded 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -31,7 +31,7 @@ "tags": [ "activitypub" ], - "summary": "Returns the person", + "summary": "Returns the Person actor for a user", "operationId": "activitypubPerson", "parameters": [ { @@ -49,6 +49,58 @@ } } }, + "/activitypub/user/{username}/followers": { + "get": { + "produces": [ + "application/activity+json" + ], + "tags": [ + "activitypub" + ], + "summary": "Returns the Liked Collection", + "operationId": "activitypubPersonLiked", + "parameters": [ + { + "type": "string", + "description": "username of the user", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActivityPub" + } + } + } + }, + "/activitypub/user/{username}/following": { + "get": { + "produces": [ + "application/activity+json" + ], + "tags": [ + "activitypub" + ], + "summary": "Returns the Following Collection", + "operationId": "activitypubPersonFollowing", + "parameters": [ + { + "type": "string", + "description": "username of the user", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActivityPub" + } + } + } + }, "/activitypub/user/{username}/inbox": { "post": { "produces": [ @@ -76,15 +128,15 @@ } }, "/activitypub/user/{username}/outbox": { - "post": { + "get": { "produces": [ "application/activity+json" ], "tags": [ "activitypub" ], - "summary": "Send to the inbox", - "operationId": "activitypubPersonInbox", + "summary": "Returns the outbox", + "operationId": "activitypubPersonOutbox", "parameters": [ { "type": "string", @@ -92,16 +144,23 @@ "name": "username", "in": "path", "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "reponame", + "in": "path", + "required": true } ], "responses": { - "204": { - "$ref": "#/responses/empty" + "200": { + "$ref": "#/responses/ActivityPub" } } } }, - "/activitypub/user/{username}/following": { + "/activitypub/user/{username}/{reponame}": { "get": { "produces": [ "application/activity+json" @@ -109,8 +168,8 @@ "tags": [ "activitypub" ], - "summary": "Returns the following collection", - "operationId": "activitypubPersonFollowing", + "summary": "Returns the repository", + "operationId": "activitypubRepo", "parameters": [ { "type": "string", @@ -118,6 +177,13 @@ "name": "username", "in": "path", "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "reponame", + "in": "path", + "required": true } ], "responses": { @@ -127,7 +193,7 @@ } } }, - "/activitypub/user/{username}/followers": { + "/activitypub/user/{username}/{reponame}/followers": { "get": { "produces": [ "application/activity+json" @@ -136,7 +202,7 @@ "activitypub" ], "summary": "Returns the followers collection", - "operationId": "activitypubPersonFollowers", + "operationId": "activitypubRepoFollowers", "parameters": [ { "type": "string", @@ -144,6 +210,13 @@ "name": "username", "in": "path", "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "reponame", + "in": "path", + "required": true } ], "responses": { @@ -153,6 +226,39 @@ } } }, + "/activitypub/user/{username}/{reponame}/inbox": { + "post": { + "produces": [ + "application/activity+json" + ], + "tags": [ + "activitypub" + ], + "summary": "Send to the inbox", + "operationId": "activitypubRepoInbox", + "parameters": [ + { + "type": "string", + "description": "username of the user", + "name": "username", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "reponame", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + } + } + } + }, "/admin/cron": { "get": { "produces": [ @@ -9090,6 +9196,9 @@ }, "404": { "$ref": "#/responses/notFound" + }, + "405": { + "$ref": "#/responses/empty" } } } @@ -9173,6 +9282,9 @@ }, "404": { "$ref": "#/responses/notFound" + }, + "405": { + "$ref": "#/responses/empty" } } }, @@ -9941,6 +10053,9 @@ "404": { "$ref": "#/responses/notFound" }, + "405": { + "$ref": "#/responses/empty" + }, "409": { "$ref": "#/responses/conflict" } @@ -10028,6 +10143,9 @@ "404": { "$ref": "#/responses/notFound" }, + "405": { + "$ref": "#/responses/empty" + }, "409": { "$ref": "#/responses/conflict" } diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js index a911c1739..94fca7a9c 100644 --- a/web_src/js/features/repo-commit.js +++ b/web_src/js/features/repo-commit.js @@ -45,7 +45,12 @@ export function initRepoCommitLastCommitLoader() { $('table#repo-files-table .commit-list').replaceWith(row); return; } - entryMap[$(row).attr('data-entryname')].replaceWith(row); + // there are other <tr> rows in response (eg: <tr class="has-parent">) + // at the moment only the "data-entryname" rows should be processed + const entryName = $(row).attr('data-entryname'); + if (entryName) { + entryMap[entryName].replaceWith(row); + } }); }); } diff --git a/web_src/less/_base.less b/web_src/less/_base.less index bd5754c1e..78f32956e 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -2200,5 +2200,9 @@ table th[data-sortt-desc] { .item { width: initial !important; } + + > .dropdown.item { + position: initial; + } } } |