Merge remote-tracking branch 'upstream/main' into limit-repo-size

This commit is contained in:
DmitryFrolovTri
2023-04-18 03:58:56 +00:00
58 changed files with 743 additions and 310 deletions
+2 -2
View File
@@ -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)
+56
View File
@@ -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
View File
@@ -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 才可以正常工作。
+1
View File
@@ -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
+9
View File
@@ -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
}
+27
View File
@@ -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
View File
@@ -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
+5
View File
@@ -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,
-2
View File
@@ -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())
+7
View File
@@ -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 {
+1 -1
View File
@@ -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)
}
+20 -14
View File
@@ -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>")
}
+14 -9
View File
@@ -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: "",
}
+39 -42
View File
@@ -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
+5 -5
View File
@@ -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)
}
})
}
+6 -2
View File
@@ -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)
+12 -1
View File
@@ -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
}
+27
View File
@@ -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
}
+2 -1
View File
@@ -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
+3 -3
View File
@@ -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)"`
-7
View File
@@ -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
+83 -77
View File
@@ -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?
+23 -6
View File
@@ -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=Чтобы установить пакет, выполните следующую команду:
+91
View File
@@ -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
}
+1
View File
@@ -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
View File
@@ -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)
}
}
}()
+1 -1
View File
@@ -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 {
+9 -1
View File
@@ -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)
+1
View File
@@ -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{
+1
View File
@@ -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 {
+35
View File
@@ -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)
}
+8
View File
@@ -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
}
+1 -1
View File
@@ -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>
+8 -7
View File
@@ -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>
+6 -18
View File
@@ -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}}
+7 -1
View File
@@ -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
+3 -3
View File
@@ -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 &#39;master&#39;.")
assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch &#34;master&#34;.")
// 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)
})
}
+1 -1
View File
@@ -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)
}
}
+1 -1
View File
@@ -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
})
}
+1 -1
View File
@@ -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)
})
}
+1 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+6 -5
View File
@@ -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 -10
View File
@@ -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');
+5 -14
View File
@@ -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');
+5 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
export async function renderAsciinemaPlayer() {
export async function renderAsciicast() {
const els = document.querySelectorAll('.asciinema-player-container');
if (!els.length) return;
+8
View File
@@ -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');
}
+2 -2
View File
@@ -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
+7 -11
View File
@@ -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);
}
}
}
+24 -26
View File
@@ -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);
}
}
}