mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 03:24:39 +02:00
Merge remote-tracking branch 'upstream/main' into limit-repo-size
This commit is contained in:
@@ -20,7 +20,7 @@ IMPORT := code.gitea.io/gitea
|
||||
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||
COMMA := ,
|
||||
|
||||
XGO_VERSION := go-1.20.x
|
||||
@@ -41,7 +41,7 @@ DOCKER_IMAGE ?= gitea/gitea
|
||||
DOCKER_TAG ?= latest
|
||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
|
||||
ifeq ($(HAS_GO), GO)
|
||||
ifeq ($(HAS_GO), yes)
|
||||
GOPATH ?= $(shell $(GO) env GOPATH)
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
// CmdActions represents the available actions sub-commands.
|
||||
CmdActions = cli.Command{
|
||||
Name: "actions",
|
||||
Usage: "",
|
||||
Description: "Commands for managing Gitea Actions",
|
||||
Subcommands: []cli.Command{
|
||||
subcmdActionsGenRunnerToken,
|
||||
},
|
||||
}
|
||||
|
||||
subcmdActionsGenRunnerToken = cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
Action: runGenerateActionsRunnerToken,
|
||||
Aliases: []string{"grt"},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "scope, s",
|
||||
Value: "",
|
||||
Usage: "{owner}[/{repo}] - leave empty for a global runner",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runGenerateActionsRunnerToken(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
scope := c.String("scope")
|
||||
|
||||
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
|
||||
if extra.HasError() {
|
||||
return handleCliResponseExtra(extra)
|
||||
}
|
||||
_, _ = fmt.Printf("%s\n", respText)
|
||||
return nil
|
||||
}
|
||||
+16
-11
@@ -17,7 +17,7 @@ import (
|
||||
var CmdConvert = cli.Command{
|
||||
Name: "convert",
|
||||
Usage: "Convert the database",
|
||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
|
||||
Action: runConvert,
|
||||
}
|
||||
|
||||
@@ -35,17 +35,22 @@ func runConvert(ctx *cli.Context) error {
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
if !setting.Database.Type.IsMySQL() {
|
||||
fmt.Println("This command can only be used with a MySQL database")
|
||||
return nil
|
||||
switch {
|
||||
case setting.Database.Type.IsMySQL():
|
||||
if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
|
||||
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
if err := db.ConvertVarcharToNVarchar(); err != nil {
|
||||
log.Fatal("Failed to convert database from varchar to nvarchar: %v", err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now")
|
||||
default:
|
||||
fmt.Println("This command can only be used with a MySQL or MSSQL database")
|
||||
}
|
||||
|
||||
if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
|
||||
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -551,3 +551,28 @@ Restore-repo restore repository data from disk dir:
|
||||
- `--owner_name lunny`: Restore destination owner name
|
||||
- `--repo_name tango`: Restore destination repository name
|
||||
- `--units <units>`: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.
|
||||
|
||||
### actions generate-runner-token
|
||||
|
||||
Generate a new token for a runner to use to register with the server
|
||||
|
||||
- Options:
|
||||
- `--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner
|
||||
|
||||
To register a global runner:
|
||||
|
||||
```
|
||||
gitea actions generate-runner-token
|
||||
```
|
||||
|
||||
To register a runner for a specific organization, in this case `org`:
|
||||
|
||||
```
|
||||
gitea actions generate-runner-token -s org
|
||||
```
|
||||
|
||||
To register a runner for a specific repo, in this case `username/test-repo`:
|
||||
|
||||
```
|
||||
gitea actions generate-runner-token -s username/test-repo
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@ PORT_TO_REDIRECT = 3080
|
||||
|
||||
[ACME](https://tools.ietf.org/html/rfc8555) 是一种证书颁发机构标准协议,允许您自动请求和续订 SSL/TLS 证书。[Let`s Encrypt](https://letsencrypt.org/) 是使用此标准的免费公开信任的证书颁发机构服务器。仅实施“HTTP-01”和“TLS-ALPN-01”挑战。为了使 ACME 质询通过并验证您的域所有权,“80”端口(“HTTP-01”)或“443”端口(“TLS-ALPN-01”)上 gitea 域的外部流量必须由 gitea 实例提供服务。可能需要设置 [HTTP 重定向](#设置http重定向) 和端口转发才能正确路由外部流量。否则,到端口“80”的正常流量将自动重定向到 HTTPS。**您必须同意**ACME提供商的服务条款(默认为Let's Encrypt的 [服务条款](https://letsencrypt.org/documents/LE-SA-v1.2-2017年11月15日.pdf)。
|
||||
|
||||
实用默认 Let's Encrypt 的最小配置如下:
|
||||
使用默认 Let's Encrypt 的最小配置如下:
|
||||
|
||||
```ini
|
||||
[server]
|
||||
@@ -92,7 +92,7 @@ ACME_EMAIL=email@example.com
|
||||
|
||||
按照 [reverse proxy guide](../reverse-proxies) 的规则设置你的反向代理服务器
|
||||
|
||||
然后,按照下面的想到启用 HTTPS:
|
||||
然后,按照下面的向导启用 HTTPS:
|
||||
|
||||
- [nginx](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
||||
- [apache2/httpd](https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
date: "2021-09-02T16:00:00+08:00"
|
||||
title: "从旧版 Gitea 升级"
|
||||
slug: "upgrade-from-gitea"
|
||||
weight: 100
|
||||
toc: false
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "installation"
|
||||
name: "从旧版 Gitea 升级"
|
||||
weight: 100
|
||||
identifier: "upgrade-from-gitea"
|
||||
---
|
||||
|
||||
# 从旧版 Gitea 升级
|
||||
|
||||
**目录**
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
想要升级 Gitea,只需要下载新版,停止运行旧版,进行数据备份,然后运行新版就好。
|
||||
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
|
||||
如果需要进行数据库迁移,Gitea 会花一些时间完成升级然后继续服务。
|
||||
|
||||
## 为重大变更检查更新日志
|
||||
|
||||
为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。
|
||||
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.io/)
|
||||
并检查重大变更是否会影响你的 Gitea 实例。
|
||||
|
||||
## 降级前的备份
|
||||
|
||||
Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`),
|
||||
这些版本拥有相同的数据库结构,可以自由升级或降级。
|
||||
其他情况 (`a.b.?` -> `a.c.?`)下,
|
||||
新版 Gitea 可能将会将数据库升级到与旧版数据库不同的结构。
|
||||
|
||||
举个例子:
|
||||
|
||||
| 当前 | 目标 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 1.4.0 | 1.4.1 | ✅ |
|
||||
| 1.4.1 | 1.4.0 | ⚠️ 不建议,后果自负!尽管数据库结构可能不会变更,让它可以正常工作。我们强烈建议降级前进行完全的备份。 |
|
||||
| 1.4.x | 1.5.y | ✅ 数据库会被自动升级。你可以直接从 1.4.x 升级到最新的 1.5.y。 |
|
||||
| 1.5.y | 1.4.x | ❌ 数据库已被升级且不可被旧版 Gitea 利用,使用备份来进行降级。 |
|
||||
|
||||
**因为你不能基于升级后的数据库运行旧版 Gitea,所以你应该在数据库升级前完成数据备份。**
|
||||
|
||||
如果你在生产环境下使用 Gitea,你应该在升级前做好备份,哪怕只是小版本的补丁更新。
|
||||
|
||||
备份步骤:
|
||||
|
||||
* 停止 Gitea 实例
|
||||
* 备份数据库
|
||||
* 备份 Gitea 配置文件
|
||||
* 备份 Gitea 在 `APP_DATA_PATH` 中的数据文件
|
||||
* 备份 Gitea 的外部存储 (例如: S3/MinIO 或被使用的其他存储)
|
||||
|
||||
如果你在使用云服务或拥有快照功能的文件系统,
|
||||
最好对 Gitea 的数据盘及相关资料存储进行一次快照。
|
||||
|
||||
## 从 Docker 升级
|
||||
|
||||
* `docker pull` 拉取 Gitea 的最新发布版。
|
||||
* 停止运行中的实例,备份数据。
|
||||
* 使用 `docker` 或 `docker-compose` 启动更新的 Gitea Docker 容器.
|
||||
|
||||
## 从包升级
|
||||
|
||||
* 停止运行中的实例,备份数据。
|
||||
* 使用你的包管理器更新 Gitea 到最新版本。
|
||||
* 启动 Gitea 实例。
|
||||
|
||||
## 从二进制升级
|
||||
|
||||
* 下载最新的 Gitea 二进制文件到临时文件夹中。
|
||||
* 停止运行中的实例,备份数据。
|
||||
* 将旧的 Gitea 二进制文件覆盖成新的。
|
||||
* 启动 Gitea 实例。
|
||||
|
||||
在 Linux 系统上自动执行以上步骤的脚本可在 [Gitea 的 source tree 中找到 `contrib/upgrade.sh` 来获取](https://github.com/go-gitea/gitea/blob/main/contrib/upgrade.sh).
|
||||
|
||||
## 小心你的自定义模板
|
||||
|
||||
Gitea 的模板结构与变量可能会随着各个版本的发布发生变化,如果你使用了自定义模板,
|
||||
你得注意你的模板与你使用的 Gitea 版本的兼容性。
|
||||
|
||||
如果自定义模板与 Gitea 版本不兼容,你可能会得到:
|
||||
`50x` 服务器错误,页面元素丢失或故障,莫名其妙的页面布局,等等…
|
||||
移除或更新不兼容的模板,Gitea Web 才可以正常工作。
|
||||
@@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.`
|
||||
cmd.CmdDocs,
|
||||
cmd.CmdDumpRepository,
|
||||
cmd.CmdRestoreRepository,
|
||||
cmd.CmdActions,
|
||||
}
|
||||
// Now adjust these commands to add our global configuration options
|
||||
|
||||
|
||||
@@ -69,3 +69,12 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us
|
||||
OrderBy("timestamp").
|
||||
Find(&hdata)
|
||||
}
|
||||
|
||||
// GetTotalContributionsInHeatmap returns the total number of contributions in a heatmap
|
||||
func GetTotalContributionsInHeatmap(hdata []*UserHeatmapData) int64 {
|
||||
var total int64
|
||||
for _, v := range hdata {
|
||||
total += v.Contributions
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
@@ -42,6 +42,33 @@ func ConvertUtf8ToUtf8mb4() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql
|
||||
func ConvertVarcharToNVarchar() error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
res, err := sess.QuerySliceString(`SELECT 'ALTER TABLE ' + OBJECT_NAME(SC.object_id) + ' MODIFY SC.name NVARCHAR(' + CONVERT(VARCHAR(5),SC.max_length) + ')'
|
||||
FROM SYS.columns SC
|
||||
JOIN SYS.types ST
|
||||
ON SC.system_type_id = ST.system_type_id
|
||||
AND SC.user_type_id = ST.user_type_id
|
||||
WHERE ST.name ='varchar'`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range res {
|
||||
if len(row) == 1 {
|
||||
if _, err = sess.Exec(row[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Cell2Int64 converts a xorm.Cell type to int64,
|
||||
// and handles possible irregular cases.
|
||||
func Cell2Int64(val xorm.Cell) int64 {
|
||||
|
||||
+15
-1
@@ -41,6 +41,18 @@ const (
|
||||
|
||||
// UserTypeOrganization defines an organization
|
||||
UserTypeOrganization
|
||||
|
||||
// UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||
UserTypeUserReserved
|
||||
|
||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||
UserTypeOrganizationReserved
|
||||
|
||||
// UserTypeBot defines a bot user
|
||||
UserTypeBot
|
||||
|
||||
// UserTypeRemoteUser defines a remote user for federated users
|
||||
UserTypeRemoteUser
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -312,6 +324,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO
|
||||
Select("`user`.*").
|
||||
Join("LEFT", "follow", "`user`.id=follow.user_id").
|
||||
Where("follow.follow_id=?", u.ID).
|
||||
And("`user`.type=?", UserTypeIndividual).
|
||||
And(isUserVisibleToViewerCond(viewer))
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
@@ -333,6 +346,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO
|
||||
Select("`user`.*").
|
||||
Join("LEFT", "follow", "`user`.id=follow.follow_id").
|
||||
Where("follow.user_id=?", u.ID).
|
||||
And("`user`.type=?", UserTypeIndividual).
|
||||
And(isUserVisibleToViewerCond(viewer))
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
@@ -959,7 +973,7 @@ func GetUserByName(ctx context.Context, name string) (*User, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, ErrUserNotExist{0, name, 0}
|
||||
}
|
||||
u := &User{LowerName: strings.ToLower(name)}
|
||||
u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
|
||||
has, err := db.GetEngine(ctx).Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
githubEventIssueComment = "issue_comment"
|
||||
githubEventRelease = "release"
|
||||
githubEventPullRequestComment = "pull_request_comment"
|
||||
githubEventGollum = "gollum"
|
||||
)
|
||||
|
||||
// canGithubEventMatch check if the input Github event can match any Gitea event.
|
||||
@@ -29,6 +30,10 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
|
||||
case githubEventRegistryPackage:
|
||||
return triggedEvent == webhook_module.HookEventPackage
|
||||
|
||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum
|
||||
case githubEventGollum:
|
||||
return triggedEvent == webhook_module.HookEventWiki
|
||||
|
||||
case githubEventIssues:
|
||||
switch triggedEvent {
|
||||
case webhook_module.HookEventIssues,
|
||||
|
||||
@@ -119,8 +119,6 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType
|
||||
webhook_module.HookEventCreate,
|
||||
webhook_module.HookEventDelete,
|
||||
webhook_module.HookEventFork,
|
||||
// FIXME: `wiki` event should match `gollum` event
|
||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum
|
||||
webhook_module.HookEventWiki:
|
||||
if len(evt.Acts()) != 0 {
|
||||
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
|
||||
|
||||
@@ -92,6 +92,13 @@ func TestDetectMatched(t *testing.T) {
|
||||
yamlOn: "on:\n registry_package:\n types: [updated]",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "HookEventWiki(wiki) matches githubEventGollum(gollum)",
|
||||
triggedEvent: webhook_module.HookEventWiki,
|
||||
payload: nil,
|
||||
yamlOn: "on: gollum",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -34,7 +34,7 @@ func nodeToTable(meta *yaml.Node) ast.Node {
|
||||
|
||||
func mappingNodeToTable(meta *yaml.Node) ast.Node {
|
||||
table := east.NewTable()
|
||||
alignments := []east.Alignment{}
|
||||
alignments := make([]east.Alignment, 0, len(meta.Content)/2)
|
||||
for i := 0; i < len(meta.Content); i += 2 {
|
||||
alignments = append(alignments, east.AlignNone)
|
||||
}
|
||||
|
||||
@@ -34,16 +34,17 @@ type ASTTransformer struct{}
|
||||
// Transform transforms the given AST tree.
|
||||
func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
|
||||
firstChild := node.FirstChild()
|
||||
createTOC := false
|
||||
tocMode := ""
|
||||
ctx := pc.Get(renderContextKey).(*markup.RenderContext)
|
||||
rc := pc.Get(renderConfigKey).(*RenderConfig)
|
||||
|
||||
tocList := make([]markup.Header, 0, 20)
|
||||
if rc.yamlNode != nil {
|
||||
metaNode := rc.toMetaNode()
|
||||
if metaNode != nil {
|
||||
node.InsertBefore(node, firstChild, metaNode)
|
||||
}
|
||||
createTOC = rc.TOC
|
||||
ctx.TableOfContents = make([]markup.Header, 0, 100)
|
||||
tocMode = rc.TOC
|
||||
}
|
||||
|
||||
attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
|
||||
@@ -59,15 +60,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value)))
|
||||
}
|
||||
}
|
||||
text := n.Text(reader.Source())
|
||||
txt := n.Text(reader.Source())
|
||||
header := markup.Header{
|
||||
Text: util.BytesToReadOnlyString(text),
|
||||
Text: util.BytesToReadOnlyString(txt),
|
||||
Level: v.Level,
|
||||
}
|
||||
if id, found := v.AttributeString("id"); found {
|
||||
header.ID = util.BytesToReadOnlyString(id.([]byte))
|
||||
}
|
||||
ctx.TableOfContents = append(ctx.TableOfContents, header)
|
||||
tocList = append(tocList, header)
|
||||
case *ast.Image:
|
||||
// Images need two things:
|
||||
//
|
||||
@@ -201,14 +202,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
return ast.WalkContinue, nil
|
||||
})
|
||||
|
||||
if createTOC && len(ctx.TableOfContents) > 0 {
|
||||
lang := rc.Lang
|
||||
if len(lang) == 0 {
|
||||
lang = setting.Langs[0]
|
||||
}
|
||||
tocNode := createTOCNode(ctx.TableOfContents, lang)
|
||||
if tocNode != nil {
|
||||
showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main"
|
||||
showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar
|
||||
if len(tocList) > 0 && (showTocInMain || showTocInSidebar) {
|
||||
if showTocInMain {
|
||||
tocNode := createTOCNode(tocList, rc.Lang, nil)
|
||||
node.InsertBefore(node, firstChild, tocNode)
|
||||
} else {
|
||||
tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"})
|
||||
ctx.SidebarTocNode = tocNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +375,11 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.
|
||||
func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
var err error
|
||||
if entering {
|
||||
_, err = w.WriteString("<details>")
|
||||
if _, err = w.WriteString("<details"); err != nil {
|
||||
return ast.WalkStop, err
|
||||
}
|
||||
html.RenderAttributes(w, node, nil)
|
||||
_, err = w.WriteString(">")
|
||||
} else {
|
||||
_, err = w.WriteString("</details>")
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
converter goldmark.Markdown
|
||||
once = sync.Once{}
|
||||
specMarkdown goldmark.Markdown
|
||||
specMarkdownOnce sync.Once
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -56,7 +56,7 @@ func (l *limitWriter) Write(data []byte) (int, error) {
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, fmt.Errorf("Rendered content too large - truncating render")
|
||||
return n, fmt.Errorf("rendered content too large - truncating render")
|
||||
}
|
||||
n, err := l.w.Write(data)
|
||||
l.sum += int64(n)
|
||||
@@ -73,10 +73,10 @@ func newParserContext(ctx *markup.RenderContext) parser.Context {
|
||||
return pc
|
||||
}
|
||||
|
||||
// actualRender renders Markdown to HTML without handling special links.
|
||||
func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
once.Do(func() {
|
||||
converter = goldmark.New(
|
||||
// SpecializedMarkdown sets up the Gitea specific markdown extensions
|
||||
func SpecializedMarkdown() goldmark.Markdown {
|
||||
specMarkdownOnce.Do(func() {
|
||||
specMarkdown = goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
extension.NewTable(
|
||||
extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)),
|
||||
@@ -139,13 +139,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
||||
)
|
||||
|
||||
// Override the original Tasklist renderer!
|
||||
converter.Renderer().AddOptions(
|
||||
specMarkdown.Renderer().AddOptions(
|
||||
renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewHTMLRenderer(), 10),
|
||||
),
|
||||
)
|
||||
})
|
||||
return specMarkdown
|
||||
}
|
||||
|
||||
// actualRender renders Markdown to HTML without handling special links.
|
||||
func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
converter := SpecializedMarkdown()
|
||||
lw := &limitWriter{
|
||||
w: output,
|
||||
limit: setting.UI.MaxDisplayFileSize * 3,
|
||||
@@ -174,7 +179,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
||||
buf = giteautil.NormalizeEOL(buf)
|
||||
|
||||
rc := &RenderConfig{
|
||||
Meta: "table",
|
||||
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
|
||||
Icon: "table",
|
||||
Lang: "",
|
||||
}
|
||||
|
||||
@@ -7,32 +7,42 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// RenderConfig represents rendering configuration for this file
|
||||
type RenderConfig struct {
|
||||
Meta string
|
||||
Meta markup.RenderMetaMode
|
||||
Icon string
|
||||
TOC bool
|
||||
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
||||
Lang string
|
||||
yamlNode *yaml.Node
|
||||
}
|
||||
|
||||
func renderMetaModeFromString(s string) markup.RenderMetaMode {
|
||||
switch strings.TrimSpace(strings.ToLower(s)) {
|
||||
case "none":
|
||||
return markup.RenderMetaAsNone
|
||||
case "table":
|
||||
return markup.RenderMetaAsTable
|
||||
default: // "details"
|
||||
return markup.RenderMetaAsDetails
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implement yaml.v3 UnmarshalYAML
|
||||
func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||
if rc == nil {
|
||||
rc = &RenderConfig{
|
||||
Meta: "table",
|
||||
Icon: "table",
|
||||
Lang: "",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rc.yamlNode = value
|
||||
|
||||
type commonRenderConfig struct {
|
||||
TOC bool `yaml:"include_toc"`
|
||||
TOC string `yaml:"include_toc"`
|
||||
Lang string `yaml:"lang"`
|
||||
}
|
||||
var basic commonRenderConfig
|
||||
@@ -54,58 +64,45 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||
|
||||
if err := value.Decode(&stringBasic); err == nil {
|
||||
if stringBasic.Gitea != "" {
|
||||
switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) {
|
||||
case "none":
|
||||
rc.Meta = "none"
|
||||
case "table":
|
||||
rc.Meta = "table"
|
||||
default: // "details"
|
||||
rc.Meta = "details"
|
||||
}
|
||||
rc.Meta = renderMetaModeFromString(stringBasic.Gitea)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type giteaControl struct {
|
||||
type yamlRenderConfig struct {
|
||||
Meta *string `yaml:"meta"`
|
||||
Icon *string `yaml:"details_icon"`
|
||||
TOC *bool `yaml:"include_toc"`
|
||||
TOC *string `yaml:"include_toc"`
|
||||
Lang *string `yaml:"lang"`
|
||||
}
|
||||
|
||||
type complexGiteaConfig struct {
|
||||
Gitea *giteaControl `yaml:"gitea"`
|
||||
}
|
||||
var complex complexGiteaConfig
|
||||
if err := value.Decode(&complex); err != nil {
|
||||
return fmt.Errorf("unable to decode into complexRenderConfig %w", err)
|
||||
type yamlRenderConfigWrapper struct {
|
||||
Gitea *yamlRenderConfig `yaml:"gitea"`
|
||||
}
|
||||
|
||||
if complex.Gitea == nil {
|
||||
var cfg yamlRenderConfigWrapper
|
||||
if err := value.Decode(&cfg); err != nil {
|
||||
return fmt.Errorf("unable to decode into yamlRenderConfigWrapper %w", err)
|
||||
}
|
||||
|
||||
if cfg.Gitea == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if complex.Gitea.Meta != nil {
|
||||
switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) {
|
||||
case "none":
|
||||
rc.Meta = "none"
|
||||
case "table":
|
||||
rc.Meta = "table"
|
||||
default: // "details"
|
||||
rc.Meta = "details"
|
||||
}
|
||||
if cfg.Gitea.Meta != nil {
|
||||
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
|
||||
}
|
||||
|
||||
if complex.Gitea.Icon != nil {
|
||||
rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon))
|
||||
if cfg.Gitea.Icon != nil {
|
||||
rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon))
|
||||
}
|
||||
|
||||
if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" {
|
||||
rc.Lang = *complex.Gitea.Lang
|
||||
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
|
||||
rc.Lang = *cfg.Gitea.Lang
|
||||
}
|
||||
|
||||
if complex.Gitea.TOC != nil {
|
||||
rc.TOC = *complex.Gitea.TOC
|
||||
if cfg.Gitea.TOC != nil {
|
||||
rc.TOC = *cfg.Gitea.TOC
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -116,9 +113,9 @@ func (rc *RenderConfig) toMetaNode() ast.Node {
|
||||
return nil
|
||||
}
|
||||
switch rc.Meta {
|
||||
case "table":
|
||||
case markup.RenderMetaAsTable:
|
||||
return nodeToTable(rc.yamlNode)
|
||||
case "details":
|
||||
case markup.RenderMetaAsDetails:
|
||||
return nodeToDetails(rc.yamlNode, rc.Icon)
|
||||
default:
|
||||
return nil
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"toc", &RenderConfig{
|
||||
TOC: true,
|
||||
TOC: "true",
|
||||
Meta: "table",
|
||||
Icon: "table",
|
||||
Lang: "",
|
||||
@@ -68,7 +68,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"tocfalse", &RenderConfig{
|
||||
TOC: false,
|
||||
TOC: "false",
|
||||
Meta: "table",
|
||||
Icon: "table",
|
||||
Lang: "",
|
||||
@@ -78,7 +78,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
"toclang", &RenderConfig{
|
||||
Meta: "table",
|
||||
Icon: "table",
|
||||
TOC: true,
|
||||
TOC: "true",
|
||||
Lang: "testlang",
|
||||
}, `
|
||||
include_toc: true
|
||||
@@ -120,7 +120,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
"complex2", &RenderConfig{
|
||||
Lang: "two",
|
||||
Meta: "table",
|
||||
TOC: true,
|
||||
TOC: "true",
|
||||
Icon: "smiley",
|
||||
}, `
|
||||
lang: one
|
||||
@@ -155,7 +155,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
|
||||
}
|
||||
if got.TOC != tt.expected.TOC {
|
||||
t.Errorf("TOC Expected %t Got %t", tt.expected.TOC, got.TOC)
|
||||
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ import (
|
||||
"github.com/yuin/goldmark/ast"
|
||||
)
|
||||
|
||||
func createTOCNode(toc []markup.Header, lang string) ast.Node {
|
||||
func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]string) ast.Node {
|
||||
details := NewDetails()
|
||||
summary := NewSummary()
|
||||
|
||||
for k, v := range detailsAttrs {
|
||||
details.SetAttributeString(k, []byte(v))
|
||||
}
|
||||
|
||||
summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc"))))
|
||||
details.AppendChild(details, summary)
|
||||
ul := ast.NewList('-')
|
||||
@@ -40,7 +44,7 @@ func createTOCNode(toc []markup.Header, lang string) ast.Node {
|
||||
}
|
||||
li := ast.NewListItem(currentLevel * 2)
|
||||
a := ast.NewLink()
|
||||
a.Destination = []byte(fmt.Sprintf("#%s", url.PathEscape(header.ID)))
|
||||
a.Destination = []byte(fmt.Sprintf("#%s", url.QueryEscape(header.ID)))
|
||||
a.AppendChild(a, ast.NewString([]byte(header.Text)))
|
||||
li.AppendChild(li, a)
|
||||
ul.AppendChild(ul, li)
|
||||
|
||||
@@ -16,6 +16,16 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
)
|
||||
|
||||
type RenderMetaMode string
|
||||
|
||||
const (
|
||||
RenderMetaAsDetails RenderMetaMode = "details" // default
|
||||
RenderMetaAsNone RenderMetaMode = "none"
|
||||
RenderMetaAsTable RenderMetaMode = "table"
|
||||
)
|
||||
|
||||
type ProcessorHelper struct {
|
||||
@@ -63,7 +73,8 @@ type RenderContext struct {
|
||||
GitRepo *git.Repository
|
||||
ShaExistCache map[string]bool
|
||||
cancelFn func()
|
||||
TableOfContents []Header
|
||||
SidebarTocNode ast.Node
|
||||
RenderMetaAs RenderMetaMode
|
||||
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// Email structure holds a data for sending general emails
|
||||
type GenerateTokenRequest struct {
|
||||
Scope string
|
||||
}
|
||||
|
||||
// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function
|
||||
func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) {
|
||||
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
|
||||
|
||||
req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{
|
||||
Scope: scope,
|
||||
})
|
||||
|
||||
resp, extra := requestJSONResp(req, &responseText{})
|
||||
return resp.Text, extra
|
||||
}
|
||||
@@ -129,7 +129,8 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
|
||||
url.PathEscape(branch),
|
||||
)
|
||||
req := newInternalRequest(ctx, reqURL, "POST")
|
||||
return requestJSONUserMsg(req, "")
|
||||
_, extra := requestJSONResp(req, &responseText{})
|
||||
return extra
|
||||
}
|
||||
|
||||
// SSHLog sends ssh error log response
|
||||
|
||||
@@ -30,8 +30,8 @@ type OrganizationPermissions struct {
|
||||
// CreateOrgOption options for creating an organization
|
||||
type CreateOrgOption struct {
|
||||
// required: true
|
||||
UserName string `json:"username" binding:"Required"`
|
||||
FullName string `json:"full_name"`
|
||||
UserName string `json:"username" binding:"Required;Username;MaxSize(40)"`
|
||||
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
|
||||
Location string `json:"location" binding:"MaxSize(50)"`
|
||||
@@ -45,7 +45,7 @@ type CreateOrgOption struct {
|
||||
|
||||
// EditOrgOption options for editing an organization
|
||||
type EditOrgOption struct {
|
||||
FullName string `json:"full_name"`
|
||||
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
|
||||
Location string `json:"location" binding:"MaxSize(50)"`
|
||||
|
||||
@@ -171,13 +171,6 @@ func NewFuncMap() []template.FuncMap {
|
||||
}
|
||||
return false
|
||||
},
|
||||
"Iterate": func(arg interface{}) (items []int64) {
|
||||
count, _ := util.ToInt64(arg)
|
||||
for i := int64(0); i < count; i++ {
|
||||
items = append(items, i)
|
||||
}
|
||||
return items
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// setting
|
||||
|
||||
@@ -118,6 +118,12 @@ footer = Footer
|
||||
footer.software = About Software
|
||||
footer.links = Links
|
||||
|
||||
[heatmap]
|
||||
number_of_contributions_in_the_last_12_months = %s contributions in the last 12 months
|
||||
no_contributions = No contributions
|
||||
less = Less
|
||||
more = More
|
||||
|
||||
[editor]
|
||||
buttons.heading.tooltip = Add heading
|
||||
buttons.bold.tooltip = Add bold text
|
||||
@@ -246,7 +252,7 @@ install_btn_confirm = Install Gitea
|
||||
test_git_failed = Could not test 'git' command: %v
|
||||
sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version).
|
||||
invalid_db_setting = The database settings are invalid: %v
|
||||
invalid_db_table = The database table '%s' is invalid: %v
|
||||
invalid_db_table = The database table "%s" is invalid: %v
|
||||
invalid_repo_path = The repository root path is invalid: %v
|
||||
invalid_app_data_path = The app data path is invalid: %v
|
||||
run_user_not_match = The 'run as' username is not the current username: %s -> %s
|
||||
@@ -312,7 +318,7 @@ repo_no_results = No matching repositories found.
|
||||
user_no_results = No matching users found.
|
||||
org_no_results = No matching organizations found.
|
||||
code_no_results = No source code matching your search term found.
|
||||
code_search_results = Search results for '%s'
|
||||
code_search_results = Search results for "%s"
|
||||
code_last_indexed_at = Last indexed %s
|
||||
relevant_repositories_tooltip = Repositories that are forks or that have no topic, no icon, and no description are hidden.
|
||||
relevant_repositories = Only relevant repositories are being shown, <a href="%s">show unfiltered results</a>.
|
||||
@@ -489,8 +495,8 @@ size_error = ` must be size %s.`
|
||||
min_size_error = ` must contain at least %s characters.`
|
||||
max_size_error = ` must contain at most %s characters.`
|
||||
email_error = ` is not a valid email address.`
|
||||
url_error = `'%s' is not a valid URL.`
|
||||
include_error = ` must contain substring '%s'.`
|
||||
url_error = `"%s" is not a valid URL.`
|
||||
include_error = ` must contain substring "%s".`
|
||||
glob_pattern_error = ` glob pattern is invalid: %s.`
|
||||
regex_pattern_error = ` regex pattern is invalid: %s.`
|
||||
username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
|
||||
@@ -515,7 +521,7 @@ team_name_been_taken = The team name is already taken.
|
||||
team_no_units_error = Allow access to at least one repository section.
|
||||
email_been_used = The email address is already used.
|
||||
email_invalid = The email address is invalid.
|
||||
openid_been_used = The OpenID address '%s' is already used.
|
||||
openid_been_used = The OpenID address "%s" is already used.
|
||||
username_password_incorrect = Username or password is incorrect.
|
||||
password_complexity = Password does not pass complexity requirements:
|
||||
password_lowercase_one = At least one lowercase character
|
||||
@@ -567,9 +573,9 @@ disabled_public_activity = This user has disabled the public visibility of the a
|
||||
email_visibility.limited = Your email address is visible to all authenticated users
|
||||
email_visibility.private = Your email address is only visible to you and administrators
|
||||
|
||||
form.name_reserved = The username '%s' is reserved.
|
||||
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username.
|
||||
form.name_chars_not_allowed = User name '%s' contains invalid characters.
|
||||
form.name_reserved = The username "%s" is reserved.
|
||||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username.
|
||||
form.name_chars_not_allowed = User name "%s" contains invalid characters.
|
||||
|
||||
[settings]
|
||||
profile = Profile
|
||||
@@ -600,7 +606,7 @@ location = Location
|
||||
update_theme = Update Theme
|
||||
update_profile = Update Profile
|
||||
update_language = Update Language
|
||||
update_language_not_found = Language '%s' is not available.
|
||||
update_language_not_found = Language "%s" is not available.
|
||||
update_language_success = Language has been updated.
|
||||
update_profile_success = Your profile has been updated.
|
||||
change_username = Your username has been changed.
|
||||
@@ -677,7 +683,7 @@ add_new_email = Add New Email Address
|
||||
add_new_openid = Add New OpenID URI
|
||||
add_email = Add Email Address
|
||||
add_openid = Add OpenID URI
|
||||
add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address.
|
||||
add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address.
|
||||
add_email_success = The new email address has been added.
|
||||
email_preference_set_success = Email preference has been set successfully.
|
||||
add_openid_success = The new OpenID address has been added.
|
||||
@@ -716,7 +722,7 @@ gpg_token_help = You can generate a signature using:
|
||||
gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig
|
||||
gpg_token_signature = Armored GPG signature
|
||||
key_signature_gpg_placeholder = Begins with '-----BEGIN PGP SIGNATURE-----'
|
||||
verify_gpg_key_success = GPG key '%s' has been verified.
|
||||
verify_gpg_key_success = GPG key "%s" has been verified.
|
||||
ssh_key_verified=Verified Key
|
||||
ssh_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user.
|
||||
ssh_key_verify=Verify
|
||||
@@ -726,15 +732,15 @@ ssh_token = Token
|
||||
ssh_token_help = You can generate a signature using:
|
||||
ssh_token_signature = Armored SSH signature
|
||||
key_signature_ssh_placeholder = Begins with '-----BEGIN SSH SIGNATURE-----'
|
||||
verify_ssh_key_success = SSH key '%s' has been verified.
|
||||
verify_ssh_key_success = SSH key "%s" has been verified.
|
||||
subkeys = Subkeys
|
||||
key_id = Key ID
|
||||
key_name = Key Name
|
||||
key_content = Content
|
||||
principal_content = Content
|
||||
add_key_success = The SSH key '%s' has been added.
|
||||
add_gpg_key_success = The GPG key '%s' has been added.
|
||||
add_principal_success = The SSH certificate principal '%s' has been added.
|
||||
add_key_success = The SSH key "%s" has been added.
|
||||
add_gpg_key_success = The GPG key "%s" has been added.
|
||||
add_principal_success = The SSH certificate principal "%s" has been added.
|
||||
delete_key = Remove
|
||||
ssh_key_deletion = Remove SSH Key
|
||||
gpg_key_deletion = Remove GPG Key
|
||||
@@ -990,8 +996,8 @@ archive.pull.nocomment = This repo is archived. You cannot comment on pull reque
|
||||
|
||||
form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository.
|
||||
form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
|
||||
form.name_reserved = The repository name '%s' is reserved.
|
||||
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
|
||||
form.name_reserved = The repository name "%s" is reserved.
|
||||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository name.
|
||||
form.repo_size_limit_negative = Repository size limitation cannot be negative.
|
||||
form.repo_size_limit_only_by_admins = Only administrators can change the repository size limitation.
|
||||
|
||||
@@ -1145,7 +1151,7 @@ editor.must_be_on_a_branch = You must be on a branch to make or propose changes
|
||||
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
|
||||
editor.delete_this_file = Delete File
|
||||
editor.must_have_write_access = You must have write access to make or propose changes to this file.
|
||||
editor.file_delete_success = File '%s' has been deleted.
|
||||
editor.file_delete_success = File "%s" has been deleted.
|
||||
editor.name_your_file = Name your file…
|
||||
editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field.
|
||||
editor.or = or
|
||||
@@ -1153,12 +1159,12 @@ editor.cancel_lower = Cancel
|
||||
editor.commit_signed_changes = Commit Signed Changes
|
||||
editor.commit_changes = Commit Changes
|
||||
editor.add_tmpl = Add '<filename>'
|
||||
editor.add = Add '%s'
|
||||
editor.update = Update '%s'
|
||||
editor.delete = Delete '%s'
|
||||
editor.add = Add %s
|
||||
editor.update = Update %s
|
||||
editor.delete = Delete %s
|
||||
editor.patch = Apply Patch
|
||||
editor.patching = Patching:
|
||||
editor.fail_to_apply_patch = Unable to apply patch '%s'
|
||||
editor.fail_to_apply_patch = Unable to apply patch "%s"
|
||||
editor.new_patch = New Patch
|
||||
editor.commit_message_desc = Add an optional extended description…
|
||||
editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
@@ -1170,29 +1176,29 @@ editor.new_branch_name = Name the new branch for this commit
|
||||
editor.new_branch_name_desc = New branch name…
|
||||
editor.cancel = Cancel
|
||||
editor.filename_cannot_be_empty = The filename cannot be empty.
|
||||
editor.filename_is_invalid = The filename is invalid: '%s'.
|
||||
editor.branch_does_not_exist = Branch '%s' does not exist in this repository.
|
||||
editor.branch_already_exists = Branch '%s' already exists in this repository.
|
||||
editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository.
|
||||
editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor
|
||||
editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository.
|
||||
editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository.
|
||||
editor.file_deleting_no_longer_exists = The file being deleted, '%s', no longer exists in this repository.
|
||||
editor.filename_is_invalid = The filename is invalid: "%s".
|
||||
editor.branch_does_not_exist = Branch "%s" does not exist in this repository.
|
||||
editor.branch_already_exists = Branch "%s" already exists in this repository.
|
||||
editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository.
|
||||
editor.file_is_a_symlink = "%s" is a symbolic link. Symbolic links cannot be edited in the web editor
|
||||
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
|
||||
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
|
||||
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
|
||||
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
|
||||
editor.file_already_exists = A file named '%s' already exists in this repository.
|
||||
editor.file_already_exists = A file named "%s" already exists in this repository.
|
||||
editor.commit_empty_file_header = Commit an empty file
|
||||
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
|
||||
editor.no_changes_to_show = There are no changes to show.
|
||||
editor.fail_to_update_file = Failed to update/create file '%s'.
|
||||
editor.fail_to_update_file = Failed to update/create file "%s".
|
||||
editor.fail_to_update_file_summary = Error Message:
|
||||
editor.push_rejected_no_message = The change was rejected by the server without a message. Please check Git Hooks.
|
||||
editor.push_rejected = The change was rejected by the server. Please check Git Hooks.
|
||||
editor.push_rejected_summary = Full Rejection Message:
|
||||
editor.add_subdir = Add a directory…
|
||||
editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v
|
||||
editor.upload_file_is_locked = File '%s' is locked by %s.
|
||||
editor.upload_files_to_dir = Upload files to '%s'
|
||||
editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'.
|
||||
editor.unable_to_upload_files = Failed to upload files to "%s" with error: %v
|
||||
editor.upload_file_is_locked = File "%s" is locked by %s.
|
||||
editor.upload_files_to_dir = Upload files to "%s"
|
||||
editor.cannot_commit_to_protected_branch = Cannot commit to protected branch "%s".
|
||||
editor.no_commit_to_branch = Unable to commit directly to branch because:
|
||||
editor.user_no_push_to_branch = User cannot push to branch
|
||||
editor.require_signed_commit = Branch requires a signed commit
|
||||
@@ -1201,7 +1207,7 @@ editor.revert = Revert %s onto:
|
||||
|
||||
commits.desc = Browse source code change history.
|
||||
commits.commits = Commits
|
||||
commits.no_commits = No commits in common. '%s' and '%s' have entirely different histories.
|
||||
commits.no_commits = No commits in common. "%s" and "%s" have entirely different histories.
|
||||
commits.nothing_to_compare = These branches are equal.
|
||||
commits.search = Search commits…
|
||||
commits.search.tooltip = You can prefix keywords with "author:", "committer:", "after:", or "before:", e.g. "revert author:Alice before:2019-01-13".
|
||||
@@ -1237,14 +1243,14 @@ projects.create = Create Project
|
||||
projects.title = Title
|
||||
projects.new = New Project
|
||||
projects.new_subheader = Coordinate, track, and update your work in one place, so projects stay transparent and on schedule.
|
||||
projects.create_success = The project '%s' has been created.
|
||||
projects.create_success = The project "%s" has been created.
|
||||
projects.deletion = Delete Project
|
||||
projects.deletion_desc = Deleting a project removes it from all related issues. Continue?
|
||||
projects.deletion_success = The project has been deleted.
|
||||
projects.edit = Edit Project
|
||||
projects.edit_subheader = Projects organize issues and track progress.
|
||||
projects.modify = Edit Project
|
||||
projects.edit_success = Project '%s' has been updated.
|
||||
projects.edit_success = Project "%s" has been updated.
|
||||
projects.type.none = "None"
|
||||
projects.type.basic_kanban = "Basic Kanban"
|
||||
projects.type.bug_triage = "Bug Triage"
|
||||
@@ -1316,7 +1322,7 @@ issues.label_templates.title = Load a predefined set of labels
|
||||
issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set:
|
||||
issues.label_templates.helper = Select a label set
|
||||
issues.label_templates.use = Use Label Set
|
||||
issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v
|
||||
issues.label_templates.fail_to_load_file = Failed to load label template file "%s": %v
|
||||
issues.add_label = added the %s label %s
|
||||
issues.add_labels = added the %s labels %s
|
||||
issues.remove_label = removed the %s label %s
|
||||
@@ -1560,7 +1566,7 @@ issues.review.add_review_request = "requested review from %s %s"
|
||||
issues.review.remove_review_request = "removed review request for %s %s"
|
||||
issues.review.remove_review_request_self = "refused to review %s"
|
||||
issues.review.pending = Pending
|
||||
issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select '%s' -> '%s/%s/%s' at the top of the page.
|
||||
issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page.
|
||||
issues.review.review = Review
|
||||
issues.review.reviewers = Reviewers
|
||||
issues.review.outdated = Outdated
|
||||
@@ -1728,12 +1734,12 @@ milestones.desc = Description
|
||||
milestones.due_date = Due Date (optional)
|
||||
milestones.clear = Clear
|
||||
milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'."
|
||||
milestones.create_success = The milestone '%s' has been created.
|
||||
milestones.create_success = The milestone "%s" has been created.
|
||||
milestones.edit = Edit Milestone
|
||||
milestones.edit_subheader = Milestones organize issues and track progress.
|
||||
milestones.cancel = Cancel
|
||||
milestones.modify = Update Milestone
|
||||
milestones.edit_success = Milestone '%s' has been updated.
|
||||
milestones.edit_success = Milestone "%s" has been updated.
|
||||
milestones.deletion = Delete Milestone
|
||||
milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue?
|
||||
milestones.deletion_success = The milestone has been deleted.
|
||||
@@ -1744,7 +1750,7 @@ milestones.filter_sort.most_complete = Most complete
|
||||
milestones.filter_sort.most_issues = Most issues
|
||||
milestones.filter_sort.least_issues = Least issues
|
||||
|
||||
signing.will_sign = This commit will be signed with key '%s'
|
||||
signing.will_sign = This commit will be signed with key "%s"
|
||||
signing.wont_sign.error = There was an error whilst checking if the commit could be signed
|
||||
signing.wont_sign.nokey = There is no key available to sign this commit
|
||||
signing.wont_sign.never = Commits are never signed
|
||||
@@ -1780,9 +1786,9 @@ wiki.file_revision = Page Revision
|
||||
wiki.wiki_page_revisions = Wiki Page Revisions
|
||||
wiki.back_to_wiki = Back to wiki page
|
||||
wiki.delete_page_button = Delete Page
|
||||
wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue?
|
||||
wiki.delete_page_notice_1 = Deleting the wiki page "%s" cannot be undone. Continue?
|
||||
wiki.page_already_exists = A wiki page with the same name already exists.
|
||||
wiki.reserved_page = The wiki page name '%s' is reserved.
|
||||
wiki.reserved_page = The wiki page name "%s" is reserved.
|
||||
wiki.pages = Pages
|
||||
wiki.last_updated = Last updated %s
|
||||
wiki.page_name_desc = Enter a name for this Wiki page. Some special names are: 'Home', '_Sidebar' and '_Footer'.
|
||||
@@ -2129,7 +2135,7 @@ settings.title = Title
|
||||
settings.deploy_key_content = Content
|
||||
settings.key_been_used = A deploy key with identical content is already in use.
|
||||
settings.key_name_used = A deploy key with the same name already exists.
|
||||
settings.add_key_success = The deploy key '%s' has been added.
|
||||
settings.add_key_success = The deploy key "%s" has been added.
|
||||
settings.deploy_key_deletion = Remove Deploy Key
|
||||
settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue?
|
||||
settings.deploy_key_deletion_success = The deploy key has been removed.
|
||||
@@ -2178,9 +2184,9 @@ settings.protect_unprotected_file_patterns = "Unprotected file patterns (separat
|
||||
settings.protect_unprotected_file_patterns_desc = "Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon (';'). See <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>."
|
||||
settings.add_protected_branch = Enable protection
|
||||
settings.delete_protected_branch = Disable protection
|
||||
settings.update_protect_branch_success = Branch protection for rule '%s' has been updated.
|
||||
settings.remove_protected_branch_success = Branch protection for rule '%s' has been removed.
|
||||
settings.remove_protected_branch_failed = Removing branch protection rule '%s' failed.
|
||||
settings.update_protect_branch_success = Branch protection for rule "%s" has been updated.
|
||||
settings.remove_protected_branch_success = Branch protection for rule "%s" has been removed.
|
||||
settings.remove_protected_branch_failed = Removing branch protection rule "%s" failed.
|
||||
settings.protected_branch_deletion = Delete Branch Protection
|
||||
settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue?
|
||||
settings.block_rejected_reviews = Block merge on rejected reviews
|
||||
@@ -2365,42 +2371,42 @@ release.tags_for = Tags for %s
|
||||
|
||||
branch.name = Branch Name
|
||||
branch.search = Search branches
|
||||
branch.already_exists = A branch named '%s' already exists.
|
||||
branch.already_exists = A branch named "%s" already exists.
|
||||
branch.delete_head = Delete
|
||||
branch.delete = Delete Branch '%s'
|
||||
branch.delete = Delete Branch "%s"
|
||||
branch.delete_html = Delete Branch
|
||||
branch.delete_desc = Deleting a branch is permanent. It <strong>CANNOT</strong> be undone. Continue?
|
||||
branch.deletion_success = Branch '%s' has been deleted.
|
||||
branch.deletion_failed = Failed to delete branch '%s'.
|
||||
branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging.
|
||||
branch.deletion_success = Branch "%s" has been deleted.
|
||||
branch.deletion_failed = Failed to delete branch "%s".
|
||||
branch.delete_branch_has_new_commits = Branch "%s" cannot be deleted because new commits have been added after merging.
|
||||
branch.create_branch = Create branch <strong>%s</strong>
|
||||
branch.create_from = from '%s'
|
||||
branch.create_success = Branch '%s' has been created.
|
||||
branch.branch_already_exists = Branch '%s' already exists in this repository.
|
||||
branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'.
|
||||
branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository.
|
||||
branch.create_from = from "%s"
|
||||
branch.create_success = Branch "%s" has been created.
|
||||
branch.branch_already_exists = Branch "%s" already exists in this repository.
|
||||
branch.branch_name_conflict = Branch name "%s" conflicts with the already existing branch "%s".
|
||||
branch.tag_collision = Branch "%s" cannot be created as a tag with same name already exists in the repository.
|
||||
branch.deleted_by = Deleted by %s
|
||||
branch.restore_success = Branch '%s' has been restored.
|
||||
branch.restore_failed = Failed to restore branch '%s'.
|
||||
branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted.
|
||||
branch.default_deletion_failed = Branch '%s' is the default branch. It cannot be deleted.
|
||||
branch.restore = Restore Branch '%s'
|
||||
branch.download = Download Branch '%s'
|
||||
branch.restore_success = Branch "%s" has been restored.
|
||||
branch.restore_failed = Failed to restore branch "%s".
|
||||
branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted.
|
||||
branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted.
|
||||
branch.restore = Restore Branch "%s"
|
||||
branch.download = Download Branch "%s"
|
||||
branch.included_desc = This branch is part of the default branch
|
||||
branch.included = Included
|
||||
branch.create_new_branch = Create branch from branch:
|
||||
branch.confirm_create_branch = Create branch
|
||||
branch.create_branch_operation = Create branch
|
||||
branch.new_branch = Create new branch
|
||||
branch.new_branch_from = Create new branch from '%s'
|
||||
branch.new_branch_from = Create new branch from "%s"
|
||||
branch.renamed = Branch %s was renamed to %s.
|
||||
|
||||
tag.create_tag = Create tag <strong>%s</strong>
|
||||
tag.create_tag_operation = Create tag
|
||||
tag.confirm_create_tag = Create tag
|
||||
tag.create_tag_from = Create new tag from '%s'
|
||||
tag.create_tag_from = Create new tag from "%s"
|
||||
|
||||
tag.create_success = Tag '%s' has been created.
|
||||
tag.create_success = Tag "%s" has been created.
|
||||
|
||||
topic.manage_topics = Manage Topics
|
||||
topic.done = Done
|
||||
@@ -2437,8 +2443,8 @@ team_permission_desc = Permission
|
||||
team_unit_desc = Allow Access to Repository Sections
|
||||
team_unit_disabled = (Disabled)
|
||||
|
||||
form.name_reserved = The organization name '%s' is reserved.
|
||||
form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name.
|
||||
form.name_reserved = The organization name "%s" is reserved.
|
||||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
|
||||
form.create_org_not_allowed = You are not allowed to create an organization.
|
||||
|
||||
settings = Settings
|
||||
@@ -2647,7 +2653,7 @@ users.created = Created
|
||||
users.last_login = Last Sign-In
|
||||
users.never_login = Never Signed-In
|
||||
users.send_register_notify = Send User Registration Notification
|
||||
users.new_success = The user account '%s' has been created.
|
||||
users.new_success = The user account "%s" has been created.
|
||||
users.edit = Edit
|
||||
users.auth_source = Authentication Source
|
||||
users.local = Local
|
||||
@@ -2847,7 +2853,7 @@ auths.tip.yandex = Create a new application at https://oauth.yandex.com/client/n
|
||||
auths.tip.mastodon = Input a custom instance URL for the mastodon instance you want to authenticate with (or use the default one)
|
||||
auths.edit = Edit Authentication Source
|
||||
auths.activated = This Authentication Source is Activated
|
||||
auths.new_success = The authentication '%s' has been added.
|
||||
auths.new_success = The authentication "%s" has been added.
|
||||
auths.update_success = The authentication source has been updated.
|
||||
auths.update = Update Authentication Source
|
||||
auths.delete = Delete Authentication Source
|
||||
@@ -2855,7 +2861,7 @@ auths.delete_auth_title = Delete Authentication Source
|
||||
auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue?
|
||||
auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first.
|
||||
auths.deletion_success = The authentication source has been deleted.
|
||||
auths.login_source_exist = The authentication source '%s' already exists.
|
||||
auths.login_source_exist = The authentication source "%s" already exists.
|
||||
auths.login_source_of_type_exist = An authentication source of this type already exists.
|
||||
auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s
|
||||
auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)
|
||||
@@ -2948,8 +2954,8 @@ config.mailer_sendmail_timeout = Sendmail Timeout
|
||||
config.mailer_use_dummy = Dummy
|
||||
config.test_email_placeholder = Email (e.g. test@example.com)
|
||||
config.send_test_mail = Send Testing Email
|
||||
config.test_mail_failed = Failed to send a testing email to '%s': %v
|
||||
config.test_mail_sent = A testing email has been sent to '%s'.
|
||||
config.test_mail_failed = Failed to send a testing email to "%s": %v
|
||||
config.test_mail_sent = A testing email has been sent to "%s".
|
||||
|
||||
config.oauth_config = OAuth Configuration
|
||||
config.oauth_enabled = Enabled
|
||||
@@ -3340,7 +3346,7 @@ name = Name
|
||||
creation = Add Secret
|
||||
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
|
||||
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
|
||||
creation.success = The secret '%s' has been added.
|
||||
creation.success = The secret "%s" has been added.
|
||||
creation.failed = Failed to add secret.
|
||||
deletion = Remove secret
|
||||
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
|
||||
|
||||
@@ -119,6 +119,20 @@ footer.software=О программе
|
||||
footer.links=Ссылки
|
||||
|
||||
[editor]
|
||||
buttons.heading.tooltip=Добавить заголовок
|
||||
buttons.bold.tooltip=Добавить жирный текст
|
||||
buttons.italic.tooltip=Добавить курсив
|
||||
buttons.quote.tooltip=Цитировать текст
|
||||
buttons.code.tooltip=Добавить код
|
||||
buttons.link.tooltip=Добавить ссылку
|
||||
buttons.list.unordered.tooltip=Добавить маркированный список
|
||||
buttons.list.ordered.tooltip=Добавить нумерованный список
|
||||
buttons.list.task.tooltip=Добавить список задач
|
||||
buttons.mention.tooltip=Упомянуть пользователя или команду
|
||||
buttons.ref.tooltip=Сослаться на задачу или запрос на слияние
|
||||
buttons.switch_to_legacy.tooltip=Использовать старый редактор
|
||||
buttons.enable_monospace_font=Включить моноширинный шрифт
|
||||
buttons.disable_monospace_font=Выключить моноширинный шрифт
|
||||
|
||||
[filter]
|
||||
string.asc=A - Я
|
||||
@@ -1267,9 +1281,9 @@ issues.remove_labels=удалил(а) метки %s %s
|
||||
issues.add_remove_labels=добавил(а) метки %s и удалил(а) %s %s
|
||||
issues.add_milestone_at=`добавил(а) к этапу <b>%s</b> %s`
|
||||
issues.add_project_at=`добавил(а) в <b>%s</b> проект %s`
|
||||
issues.change_milestone_at=`поменял(а) целевой этап с <b>%s</b> на <b>%s</b> %s`
|
||||
issues.change_milestone_at=`изменил(а) целевой этап с <b>%s</b> на <b>%s</b> %s`
|
||||
issues.change_project_at=`изменил(а) проект с <b>%s</b> на <b>%s</b> %s`
|
||||
issues.remove_milestone_at=`удалил(а) из этапа <b>%s</b> %s`
|
||||
issues.remove_milestone_at=`удалил(а) это из этапа <b>%s</b> %s`
|
||||
issues.remove_project_at=`удалил(а) это из проекта <b>%s</b> %s`
|
||||
issues.deleted_milestone=`(удалено)`
|
||||
issues.deleted_project=`(удалено)`
|
||||
@@ -1423,9 +1437,9 @@ issues.start_tracking_history=`начал(а) работать %s`
|
||||
issues.tracker_auto_close=Таймер будет остановлен автоматически, когда эта проблема будет закрыта
|
||||
issues.tracking_already_started=`Вы уже начали отслеживать время для <a href="%s">другой задачи</a>!`
|
||||
issues.stop_tracking=Остановить таймер
|
||||
issues.stop_tracking_history=`перестал работать %s`
|
||||
issues.stop_tracking_history=`перестал(а) работать %s`
|
||||
issues.cancel_tracking=Отмена
|
||||
issues.cancel_tracking_history=`отменил отслеживание %s`
|
||||
issues.cancel_tracking_history=`отменил(а) отслеживание %s`
|
||||
issues.add_time=Вручную добавить время
|
||||
issues.del_time=Удалить этот журнал времени
|
||||
issues.add_time_short=Добавить время
|
||||
@@ -1463,7 +1477,7 @@ issues.dependency.cancel=Отменить
|
||||
issues.dependency.remove=Удалить
|
||||
issues.dependency.remove_info=Удалить эту зависимость
|
||||
issues.dependency.added_dependency=`добавил(а) новую зависимость %s`
|
||||
issues.dependency.removed_dependency=`убрал зависимость %s`
|
||||
issues.dependency.removed_dependency=`убрал(а) зависимость %s`
|
||||
issues.dependency.pr_closing_blockedby=Закрытие этого запроса на слияние блокируется следующими задачами
|
||||
issues.dependency.issue_closing_blockedby=Закрытие этой задачи блокируется следующими задачами
|
||||
issues.dependency.issue_close_blocks=Эта задача блокирует закрытие следующих задач
|
||||
@@ -1543,7 +1557,7 @@ pulls.has_pull_request=`Запрос на слияние этих веток у
|
||||
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.change_target_branch_at=`изменил целевую ветку с <b>%s</b> на <b>%s</b> %s`
|
||||
pulls.change_target_branch_at=`изменил(а) целевую ветку с <b>%s</b> на <b>%s</b> %s`
|
||||
pulls.tab_conversation=Обсуждение
|
||||
pulls.tab_commits=Коммиты
|
||||
pulls.tab_files=Изменённые файлы
|
||||
@@ -2606,6 +2620,7 @@ repos.issues=Задачи
|
||||
repos.size=Размер
|
||||
|
||||
packages.package_manage_panel=Управление пакетами
|
||||
packages.total_size=Общий размер: %s
|
||||
packages.owner=Владелец
|
||||
packages.creator=Автор
|
||||
packages.name=Наименование
|
||||
@@ -2975,6 +2990,7 @@ reopen_pull_request=`переоткрыл(а) запрос на слияние <
|
||||
comment_issue=`прокомментировал(а) задачу <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
comment_pull=`прокомментировал(а) запрос на слияние <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
merge_pull_request=`принял(а) запрос на слияние <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
auto_merge_pull_request=`автоматически принял(а) запрос на слияние <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
transfer_repo=передал(а) репозиторий <code>%s</code> <a href="%s">%s</a>
|
||||
push_tag=создал(а) тег <a href="%[2]s">%[3]s</a> в <a href="%[1]s">%[4]s</a>
|
||||
delete_tag=удалил(а) тэг %[2]s из <a href="%[1]s">%[3]s</a>
|
||||
@@ -3101,6 +3117,7 @@ container.multi_arch=ОС / архитектура
|
||||
container.labels=Метки
|
||||
container.labels.key=Ключ
|
||||
container.labels.value=Значение
|
||||
generic.download=Скачать пакет из командной строки:
|
||||
generic.documentation=Для получения дополнительной информации об общем реестре смотрите <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">документацию</a>.
|
||||
helm.registry=Настроить реестр из командной строки:
|
||||
helm.install=Чтобы установить пакет, выполните следующую команду:
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package private
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
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/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// GenerateActionsRunnerToken generates a new runner token for a given scope
|
||||
func GenerateActionsRunnerToken(ctx *context.PrivateContext) {
|
||||
var genRequest private.GenerateTokenRequest
|
||||
rd := ctx.Req.Body
|
||||
defer rd.Close()
|
||||
|
||||
if err := json.NewDecoder(rd).Decode(&genRequest); err != nil {
|
||||
log.Error("%v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
owner, repo, err := parseScope(ctx, genRequest.Scope)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
token, err := actions_model.GetUnactivatedRunnerToken(ctx, owner, repo)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
token, err = actions_model.NewRunnerToken(ctx, owner, repo)
|
||||
if err != nil {
|
||||
err := fmt.Sprintf("error while creating runner token: %v", err)
|
||||
log.Error("%v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
err := fmt.Sprintf("could not get unactivated runner token: %v", err)
|
||||
log.Error("%v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.PlainText(http.StatusOK, token.Token)
|
||||
}
|
||||
|
||||
func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) {
|
||||
ownerID = 0
|
||||
repoID = 0
|
||||
if scope == "" {
|
||||
return ownerID, repoID, nil
|
||||
}
|
||||
|
||||
ownerName, repoName, found := strings.Cut(scope, "/")
|
||||
|
||||
u, err := user_model.GetUserByName(ctx, ownerName)
|
||||
if err != nil {
|
||||
return ownerID, repoID, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return u.ID, repoID, nil
|
||||
}
|
||||
|
||||
r, err := repo_model.GetRepositoryByName(u.ID, repoName)
|
||||
if err != nil {
|
||||
return ownerID, repoID, err
|
||||
}
|
||||
repoID = r.ID
|
||||
return ownerID, repoID, nil
|
||||
}
|
||||
@@ -77,6 +77,7 @@ func Routes() *web.Route {
|
||||
r.Get("/manager/processes", Processes)
|
||||
r.Post("/mail/send", SendEmail)
|
||||
r.Post("/restore_repo", RestoreRepo)
|
||||
r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
+11
-13
@@ -143,31 +143,29 @@ func Recovery(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
"locale": lc,
|
||||
}
|
||||
|
||||
user := context.GetContextUser(req)
|
||||
// TODO: this recovery handler is usually called without Gitea's web context, so we shouldn't touch that context too much
|
||||
// Otherwise, the 500 page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic
|
||||
user := context.GetContextUser(req) // almost always nil
|
||||
if user == nil {
|
||||
// Get user from session if logged in - do not attempt to sign-in
|
||||
user = auth.SessionUser(sessionStore)
|
||||
}
|
||||
if user != nil {
|
||||
store["IsSigned"] = true
|
||||
store["SignedUser"] = user
|
||||
store["SignedUserID"] = user.ID
|
||||
store["SignedUserName"] = user.Name
|
||||
store["IsAdmin"] = user.IsAdmin
|
||||
} else {
|
||||
store["SignedUserID"] = int64(0)
|
||||
store["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
|
||||
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
if !setting.IsProd {
|
||||
if !setting.IsProd || (user != nil && user.IsAdmin) {
|
||||
store["ErrorMsg"] = combinedErr
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("HTML render in Recovery handler panics again: %v", err)
|
||||
}
|
||||
}()
|
||||
err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store))
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
log.Error("HTML render in Recovery handler fails again: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1014,7 +1014,7 @@ func Stars(ctx *context.Context) {
|
||||
|
||||
// Forks render repository's forked users
|
||||
func Forks(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repos.forks")
|
||||
ctx.Data["Title"] = ctx.Tr("repo.forks")
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
|
||||
@@ -298,7 +298,15 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
ctx.Data["footerPresent"] = false
|
||||
}
|
||||
|
||||
ctx.Data["toc"] = rctx.TableOfContents
|
||||
if rctx.SidebarTocNode != nil {
|
||||
sb := &strings.Builder{}
|
||||
err = markdown.SpecializedMarkdown().Renderer().Render(sb, nil, rctx.SidebarTocNode)
|
||||
if err != nil {
|
||||
log.Error("Failed to render wiki sidebar TOC: %v", err)
|
||||
} else {
|
||||
ctx.Data["sidebarTocContent"] = sb.String()
|
||||
}
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
|
||||
|
||||
@@ -107,6 +107,7 @@ func Dashboard(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["HeatmapData"] = data
|
||||
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
||||
}
|
||||
|
||||
feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
|
||||
|
||||
@@ -74,6 +74,7 @@ func Profile(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["HeatmapData"] = data
|
||||
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
||||
}
|
||||
|
||||
if len(ctx.ContextUser.Description) != 0 {
|
||||
|
||||
@@ -526,3 +526,38 @@ func (n *actionsNotifier) NotifyPullRequestChangeTargetBranch(ctx context.Contex
|
||||
WithPullRequest(pr).
|
||||
Notify(ctx)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) NotifyNewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
|
||||
ctx = withMethod(ctx, "NotifyNewWikiPage")
|
||||
|
||||
newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{
|
||||
Action: api.HookWikiCreated,
|
||||
Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Page: page,
|
||||
Comment: comment,
|
||||
}).Notify(ctx)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) NotifyEditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
|
||||
ctx = withMethod(ctx, "NotifyEditWikiPage")
|
||||
|
||||
newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{
|
||||
Action: api.HookWikiEdited,
|
||||
Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Page: page,
|
||||
Comment: comment,
|
||||
}).Notify(ctx)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) NotifyDeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) {
|
||||
ctx = withMethod(ctx, "NotifyDeleteWikiPage")
|
||||
|
||||
newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{
|
||||
Action: api.HookWikiDeleted,
|
||||
Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Page: page,
|
||||
}).Notify(ctx)
|
||||
}
|
||||
|
||||
@@ -40,5 +40,13 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us
|
||||
}
|
||||
}
|
||||
|
||||
// attempting to login as a non-user account
|
||||
if user.Type != user_model.UserTypeIndividual {
|
||||
return nil, user_model.ErrUserProhibitLogin{
|
||||
UID: user.ID,
|
||||
Name: user.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{template "base/alert" .}}
|
||||
<div class="inline required field {{if .Err_OrgName}}error{{end}}">
|
||||
<label for="org_name">{{.locale.Tr "org.org_name_holder"}}</label>
|
||||
<input id="org_name" name="org_name" value="{{.org_name}}" autofocus required>
|
||||
<input id="org_name" name="org_name" value="{{.org_name}}" autofocus required maxlength="40">
|
||||
<span class="help">{{.locale.Tr "org.org_name_helper"}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,26 +14,27 @@
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_Name}}error{{end}}">
|
||||
<label for="org_name">{{.locale.Tr "org.org_name_holder"}}
|
||||
<span class="text red gt-hidden" id="org-name-change-prompt"> {{.locale.Tr "org.settings.change_orgname_prompt"}}</span>
|
||||
<span class="text red gt-hidden" id="org-name-change-redirect-prompt"> {{.locale.Tr "org.settings.change_orgname_redirect_prompt"}}</span>
|
||||
<span class="text red gt-hidden" id="org-name-change-prompt">
|
||||
<br>{{.locale.Tr "org.settings.change_orgname_prompt"}}<br>{{.locale.Tr "org.settings.change_orgname_redirect_prompt"}}
|
||||
</span>
|
||||
</label>
|
||||
<input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required>
|
||||
<input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required maxlength="40">
|
||||
</div>
|
||||
<div class="field {{if .Err_FullName}}error{{end}}">
|
||||
<label for="full_name">{{.locale.Tr "org.org_full_name_holder"}}</label>
|
||||
<input id="full_name" name="full_name" value="{{.Org.FullName}}">
|
||||
<input id="full_name" name="full_name" value="{{.Org.FullName}}" maxlength="100">
|
||||
</div>
|
||||
<div class="field {{if .Err_Description}}error{{end}}">
|
||||
<label for="description">{{$.locale.Tr "org.org_desc"}}</label>
|
||||
<textarea id="description" name="description" rows="2">{{.Org.Description}}</textarea>
|
||||
<textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
|
||||
</div>
|
||||
<div class="field {{if .Err_Website}}error{{end}}">
|
||||
<label for="website">{{.locale.Tr "org.settings.website"}}</label>
|
||||
<input id="website" name="website" type="url" value="{{.Org.Website}}">
|
||||
<input id="website" name="website" type="url" value="{{.Org.Website}}" maxlength="255">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="location">{{.locale.Tr "org.settings.location"}}</label>
|
||||
<input id="location" name="location" value="{{.Org.Location}}">
|
||||
<input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@@ -65,28 +65,16 @@
|
||||
<p>{{.FormatWarning}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="ui gt-mt-0 {{if or .sidebarPresent .toc}}grid equal width{{end}}">
|
||||
<div class="ui {{if or .sidebarPresent .toc}}eleven wide column{{else}}gt-ml-0{{end}} segment markup wiki-content-main">
|
||||
<div class="ui gt-mt-0 {{if or .sidebarPresent .sidebarTocContent}}grid equal width{{end}}">
|
||||
<div class="ui {{if or .sidebarPresent .sidebarTocContent}}eleven wide column{{else}}gt-ml-0{{end}} segment markup wiki-content-main">
|
||||
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
|
||||
{{.content | Safe}}
|
||||
</div>
|
||||
{{if or .sidebarPresent .toc}}
|
||||
<div class="column" style="padding-top: 0;">
|
||||
{{if .toc}}
|
||||
{{if or .sidebarPresent .sidebarTocContent}}
|
||||
<div class="column gt-pt-0">
|
||||
{{if .sidebarTocContent}}
|
||||
<div class="ui segment wiki-content-toc">
|
||||
<details open>
|
||||
<summary>
|
||||
<div class="ui header">{{.locale.Tr "toc"}}</div>
|
||||
</summary>
|
||||
{{$level := 0}}
|
||||
{{range .toc}}
|
||||
{{if lt $level .Level}}{{range Iterate (Eval .Level "-" $level)}}<ul>{{end}}{{end}}
|
||||
{{if gt $level .Level}}{{range Iterate (Eval $level "-" .Level)}}</ul>{{end}}{{end}}
|
||||
{{$level = .Level}}
|
||||
<li><a href="#{{.ID}}">{{.Text}}</a></li>
|
||||
{{end}}
|
||||
{{range Iterate $level}}</ul>{{end}}
|
||||
</details>
|
||||
{{.sidebarTocContent | Safe}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .sidebarPresent}}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
{{if .HeatmapData}}
|
||||
<div id="user-heatmap" data-heatmap-data="{{Json .HeatmapData}}">
|
||||
<div id="user-heatmap"
|
||||
data-heatmap-data="{{Json .HeatmapData}}"
|
||||
data-locale-total-contributions="{{$.locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" ($.locale.PrettyNumber .HeatmapTotalContributions)}}"
|
||||
data-locale-no-contributions="{{.locale.Tr "heatmap.no_contributions"}}"
|
||||
data-locale-more="{{.locale.Tr "heatmap.more"}}"
|
||||
data-locale-less="{{.locale.Tr "heatmap.less"}}"
|
||||
>
|
||||
<div slot="loading">
|
||||
<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.locale.Tr "user.heatmap.loading"}}</div>
|
||||
</div>
|
||||
|
||||
@@ -214,7 +214,7 @@ func TestAPICreateFile(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, &fileResponse)
|
||||
expectedMessage := "Add '" + treePath + "'\n"
|
||||
expectedMessage := "Add " + treePath + "\n"
|
||||
assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
|
||||
|
||||
// Test trying to create a file that already exists, should fail
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestAPIDeleteFile(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &fileResponse)
|
||||
expectedMessage := "Delete '" + treePath + "'\n"
|
||||
expectedMessage := "Delete " + treePath + "\n"
|
||||
assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
|
||||
|
||||
// Test deleting a file with the wrong SHA
|
||||
|
||||
@@ -199,7 +199,7 @@ func TestAPIUpdateFile(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &fileResponse)
|
||||
expectedMessage := "Update '" + treePath + "'\n"
|
||||
expectedMessage := "Update " + treePath + "\n"
|
||||
assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
|
||||
|
||||
// Test updating a file with the wrong SHA
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
|
||||
// Check if master branch has been locked successfully
|
||||
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
|
||||
assert.NotNil(t, flashCookie)
|
||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||
|
||||
// Request editor page
|
||||
req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
|
||||
@@ -76,7 +76,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
|
||||
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
// Check body for error message
|
||||
assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch 'master'.")
|
||||
assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch "master".")
|
||||
|
||||
// remove the protected branch
|
||||
csrf = GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||
@@ -95,7 +95,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
|
||||
// Check if master branch has been locked successfully
|
||||
flashCookie = session.GetCookie(gitea_context.CookieNameFlash)
|
||||
assert.NotNil(t, flashCookie)
|
||||
assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25271%2527%2Bfailed.", flashCookie.Value)
|
||||
assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFil
|
||||
// Check if master branch has been locked successfully
|
||||
flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash)
|
||||
assert.NotNil(t, flashCookie)
|
||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestPullCreate(t *testing.T) {
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
|
||||
assert.Regexp(t, "diff", resp.Body)
|
||||
assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body)
|
||||
assert.Regexp(t, `Subject: \[PATCH\] Update README.md`, resp.Body)
|
||||
assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one
|
||||
})
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
resultMsg := htmlDoc.doc.Find(".ui.message>p").Text()
|
||||
|
||||
assert.EqualValues(t, "Branch 'user1/repo1:feature/test' has been deleted.", resultMsg)
|
||||
assert.EqualValues(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/* non-color variables */
|
||||
--border-radius: 0.28571429rem;
|
||||
--opacity-disabled: 0.55;
|
||||
--height-loading: 12rem;
|
||||
--height-loading: 16rem;
|
||||
/* base colors */
|
||||
--color-primary: #4183c4;
|
||||
--color-primary-contrast: #ffffff;
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
.gt-overflow-x-scroll { overflow-x: scroll !important; }
|
||||
.gt-cursor-default { cursor: default !important; }
|
||||
.gt-items-start { align-items: flex-start !important; }
|
||||
.gt-whitespace-pre { white-space: pre !important }
|
||||
.gt-whitespace-pre { white-space: pre !important; }
|
||||
.gt-invisible { visibility: hidden !important; }
|
||||
|
||||
.gt-mono {
|
||||
font-family: var(--fonts-monospace) !important;
|
||||
|
||||
@@ -542,7 +542,7 @@
|
||||
|
||||
.markup-block-error {
|
||||
display: block !important; /* override fomantic .ui.form .error.message {display: none} */
|
||||
border: 1px solid var(--color-error-border) !important;
|
||||
border: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
|
||||
@@ -3261,14 +3261,15 @@ td.blob-excerpt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wiki-content-toc > ul > li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.wiki-content-toc ul {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding-left: 1em;
|
||||
padding: 5px 0 5px 1em;
|
||||
}
|
||||
|
||||
.wiki-content-toc ul ul {
|
||||
border-left: 1px var(--color-secondary);
|
||||
border-left-style: dashed;
|
||||
}
|
||||
|
||||
/* fomantic's last-child selector does not work with hidden last child */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="user-heatmap">
|
||||
<div class="total-contributions">
|
||||
{{ sum }} contributions in the last 12 months
|
||||
{{ locale.contributions_in_the_last_12_months }}
|
||||
</div>
|
||||
<calendar-heatmap
|
||||
:locale="locale"
|
||||
@@ -41,15 +41,6 @@ export default {
|
||||
],
|
||||
endDate: new Date(),
|
||||
}),
|
||||
computed: {
|
||||
sum() {
|
||||
let s = 0;
|
||||
for (let i = 0; i < this.values.length; i++) {
|
||||
s += this.values[i].count;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// work around issue with first legend color being rendered twice and legend cut off
|
||||
const legend = document.querySelector('.vch__external-legend-wrapper');
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import $ from 'jquery';
|
||||
import {initCompLabelEdit} from './comp/LabelEdit.js';
|
||||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
import {toggleElem} from '../utils/dom.js';
|
||||
|
||||
export function initCommonOrganization() {
|
||||
if ($('.organization').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('.organization.settings.options').length > 0) {
|
||||
$('#org_name').on('keyup', function () {
|
||||
const $prompt = $('#org-name-change-prompt');
|
||||
const $prompt_redirect = $('#org-name-change-redirect-prompt');
|
||||
if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) {
|
||||
showElem($prompt);
|
||||
showElem($prompt_redirect);
|
||||
} else {
|
||||
hideElem($prompt);
|
||||
hideElem($prompt_redirect);
|
||||
}
|
||||
});
|
||||
}
|
||||
$('.organization.settings.options #org_name').on('input', function () {
|
||||
const nameChanged = $(this).val().toLowerCase() !== $(this).attr('data-org-name').toLowerCase();
|
||||
toggleElem('#org-name-change-prompt', nameChanged);
|
||||
});
|
||||
|
||||
// Labels
|
||||
initCompLabelEdit('.organization.settings.labels');
|
||||
|
||||
@@ -18,11 +18,15 @@ export function initHeatmap() {
|
||||
return {date: new Date(v), count: heatmap[v]};
|
||||
});
|
||||
|
||||
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
|
||||
const locale = {
|
||||
months: new Array(12).fill().map((_, idx) => translateMonth(idx)),
|
||||
days: new Array(7).fill().map((_, idx) => translateDay(idx)),
|
||||
contributions: 'contributions',
|
||||
no_contributions: 'No contributions',
|
||||
contributions_in_the_last_12_months: el.getAttribute('data-locale-total-contributions'),
|
||||
no_contributions: el.getAttribute('data-locale-no-contributions'),
|
||||
more: el.getAttribute('data-locale-more'),
|
||||
less: el.getAttribute('data-locale-less'),
|
||||
};
|
||||
|
||||
const View = createApp(ActivityHeatmap, {values, locale});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export async function renderAsciinemaPlayer() {
|
||||
export async function renderAsciicast() {
|
||||
const els = document.querySelectorAll('.asciinema-player-container');
|
||||
if (!els.length) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function displayError(el, err) {
|
||||
el.classList.remove('is-loading');
|
||||
const errorNode = document.createElement('pre');
|
||||
errorNode.setAttribute('class', 'ui message error markup-block-error');
|
||||
errorNode.textContent = err.str || err.message || String(err);
|
||||
el.before(errorNode);
|
||||
el.setAttribute('data-render-done', 'true');
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import {renderMermaid} from './mermaid.js';
|
||||
import {renderMath} from './math.js';
|
||||
import {renderCodeCopy} from './codecopy.js';
|
||||
import {renderAsciinemaPlayer} from './asciicast.js';
|
||||
import {renderAsciicast} from './asciicast.js';
|
||||
import {initMarkupTasklist} from './tasklist.js';
|
||||
|
||||
// code that runs for all markup content
|
||||
@@ -9,7 +9,7 @@ export function initMarkupContent() {
|
||||
renderMermaid();
|
||||
renderMath();
|
||||
renderCodeCopy();
|
||||
renderAsciinemaPlayer();
|
||||
renderAsciicast();
|
||||
}
|
||||
|
||||
// code that only runs for comments
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
function displayError(el, err) {
|
||||
const target = targetElement(el);
|
||||
target.classList.remove('is-loading');
|
||||
const errorNode = document.createElement('div');
|
||||
errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono');
|
||||
errorNode.textContent = err.str || err.message || String(err);
|
||||
target.before(errorNode);
|
||||
}
|
||||
import {displayError} from './common.js';
|
||||
|
||||
function targetElement(el) {
|
||||
// The target element is either the current element if it has the `is-loading` class or the pre that contains it
|
||||
// The target element is either the current element if it has the
|
||||
// `is-loading` class or the pre that contains it
|
||||
return el.classList.contains('is-loading') ? el : el.closest('pre');
|
||||
}
|
||||
|
||||
@@ -22,6 +16,8 @@ export async function renderMath() {
|
||||
]);
|
||||
|
||||
for (const el of els) {
|
||||
const target = targetElement(el);
|
||||
if (target.hasAttribute('data-render-done')) continue;
|
||||
const source = el.textContent;
|
||||
const displayMode = el.classList.contains('display');
|
||||
const nodeName = displayMode ? 'p' : 'span';
|
||||
@@ -33,9 +29,9 @@ export async function renderMath() {
|
||||
maxExpand: 50,
|
||||
displayMode,
|
||||
});
|
||||
targetElement(el).replaceWith(tempEl);
|
||||
target.replaceWith(tempEl);
|
||||
} catch (error) {
|
||||
displayError(el, error);
|
||||
displayError(target, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import {isDarkTheme} from '../utils.js';
|
||||
import {makeCodeCopyButton} from './codecopy.js';
|
||||
import {displayError} from './common.js';
|
||||
|
||||
const {mermaidMaxSourceCharacters} = window.config;
|
||||
|
||||
const iframeCss = `
|
||||
:root {color-scheme: normal}
|
||||
body {margin: 0; padding: 0; overflow: hidden}
|
||||
#mermaid {display: block; margin: 0 auto}
|
||||
`;
|
||||
|
||||
function displayError(el, err) {
|
||||
el.closest('pre').classList.remove('is-loading');
|
||||
const errorNode = document.createElement('div');
|
||||
errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono');
|
||||
errorNode.textContent = err.str || err.message || String(err);
|
||||
el.closest('pre').before(errorNode);
|
||||
}
|
||||
const iframeCss = `:root {color-scheme: normal}
|
||||
body {margin: 0; padding: 0; overflow: hidden}
|
||||
#mermaid {display: block; margin: 0 auto}`;
|
||||
|
||||
export async function renderMermaid() {
|
||||
const els = document.querySelectorAll('.markup code.language-mermaid');
|
||||
@@ -30,18 +21,19 @@ export async function renderMermaid() {
|
||||
});
|
||||
|
||||
for (const el of els) {
|
||||
const source = el.textContent;
|
||||
const pre = el.closest('pre');
|
||||
if (pre.hasAttribute('data-render-done')) continue;
|
||||
|
||||
const source = el.textContent;
|
||||
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
|
||||
displayError(el, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
|
||||
displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await mermaid.parse(source);
|
||||
} catch (err) {
|
||||
displayError(el, err);
|
||||
el.closest('pre').classList.remove('is-loading');
|
||||
displayError(pre, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -49,26 +41,32 @@ export async function renderMermaid() {
|
||||
// can't use bindFunctions here because we can't cross the iframe boundary. This
|
||||
// means js-based interactions won't work but they aren't intended to work either
|
||||
const {svg} = await mermaid.render('mermaid', source);
|
||||
const heightStr = (svg.match(/viewBox="(.+?)"/) || ['', ''])[1].split(/\s+/)[3];
|
||||
if (!heightStr) return displayError(el, new Error('Could not determine chart height'));
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.classList.add('markup-render');
|
||||
iframe.sandbox = 'allow-scripts';
|
||||
iframe.style.height = `${Math.ceil(parseFloat(heightStr))}px`;
|
||||
iframe.classList.add('markup-render', 'gt-invisible');
|
||||
iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
|
||||
|
||||
const mermaidBlock = document.createElement('div');
|
||||
mermaidBlock.classList.add('mermaid-block');
|
||||
mermaidBlock.classList.add('mermaid-block', 'is-loading', 'gt-hidden');
|
||||
mermaidBlock.append(iframe);
|
||||
|
||||
const btn = makeCodeCopyButton();
|
||||
btn.setAttribute('data-clipboard-text', source);
|
||||
|
||||
mermaidBlock.append(btn);
|
||||
el.closest('pre').replaceWith(mermaidBlock);
|
||||
|
||||
iframe.addEventListener('load', () => {
|
||||
pre.replaceWith(mermaidBlock);
|
||||
mermaidBlock.classList.remove('gt-hidden');
|
||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
||||
setTimeout(() => { // avoid flash of iframe background
|
||||
mermaidBlock.classList.remove('is-loading');
|
||||
iframe.classList.remove('gt-invisible');
|
||||
}, 0);
|
||||
});
|
||||
|
||||
document.body.append(mermaidBlock);
|
||||
} catch (err) {
|
||||
displayError(el, err);
|
||||
displayError(pre, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user