Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e32f9c1

Browse files
authoredApr 2, 2023
Merge branch 'main' into bugfix/issue-23824
2 parents 5133af4 + 0ed62db commit e32f9c1

38 files changed

+469
-161
lines changed
 

‎.eslintrc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ rules:
164164
jquery/no-parse-html: [2]
165165
jquery/no-prop: [0]
166166
jquery/no-proxy: [2]
167-
jquery/no-ready: [0]
167+
jquery/no-ready: [2]
168168
jquery/no-serialize: [2]
169169
jquery/no-show: [2]
170170
jquery/no-size: [2]

‎build/update-locales.sh

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,10 @@ fi
1717

1818
mv ./options/locale/locale_en-US.ini ./options/
1919

20-
# the "ini" library for locale has many quirks
21-
# * `a="xx"` gets `xx` (no quote)
22-
# * `a=x\"y` gets `x\"y` (no unescaping)
23-
# * `a="x\"y"` gets `"x\"y"` (no unescaping, the quotes are still there)
24-
# * `a='x\"y'` gets `x\"y` (no unescaping, no quote)
25-
# * `a="foo` gets `"foo` (although the quote is not closed)
26-
# * 'a=`foo`' works like single-quote
27-
# crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes
28-
# crowdin always outputs quoted strings if there are quotes in the strings.
29-
30-
# this script helps to unquote the crowdin outputs for the quirky ini library
20+
# the "ini" library for locale has many quirks, its behavior is different from Crowdin.
21+
# see i18n_test.go for more details
22+
23+
# this script helps to unquote the Crowdin outputs for the quirky ini library
3124
# * find all `key="...\"..."` lines
3225
# * remove the leading quote
3326
# * remove the trailing quote

‎models/auth/source.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,14 @@ func UpdateSource(source *Source) error {
317317
}
318318
}
319319

320-
_, err := db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(source)
320+
has, err := db.GetEngine(db.DefaultContext).Where("name=? AND id!=?", source.Name, source.ID).Exist(new(Source))
321+
if err != nil {
322+
return err
323+
} else if has {
324+
return ErrSourceAlreadyExist{source.Name}
325+
}
326+
327+
_, err = db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(source)
321328
if err != nil {
322329
return err
323330
}

‎models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ var migrations = []Migration{
477477
NewMigration("Add version column to action_runner table", v1_20.AddVersionToActionRunner),
478478
// v249 -> v250
479479
NewMigration("Improve Action table indices v3", v1_20.ImproveActionTableIndices),
480+
// v250 -> v251
481+
NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
480482
}
481483

482484
// GetCurrentDBVersion returns the current db version

‎models/migrations/v1_20/v250.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_20 //nolint
5+
6+
import (
7+
"strings"
8+
9+
"code.gitea.io/gitea/modules/json"
10+
11+
"xorm.io/xorm"
12+
)
13+
14+
func ChangeContainerMetadataMultiArch(x *xorm.Engine) error {
15+
sess := x.NewSession()
16+
defer sess.Close()
17+
18+
if err := sess.Begin(); err != nil {
19+
return err
20+
}
21+
22+
type PackageVersion struct {
23+
ID int64 `xorm:"pk"`
24+
MetadataJSON string `xorm:"metadata_json"`
25+
}
26+
27+
type PackageBlob struct{}
28+
29+
// Get all relevant packages (manifest list images have a container.manifest.reference property)
30+
31+
var pvs []*PackageVersion
32+
err := sess.
33+
Table("package_version").
34+
Select("id, metadata_json").
35+
Where("id IN (SELECT DISTINCT ref_id FROM package_property WHERE ref_type = 0 AND name = 'container.manifest.reference')").
36+
Find(&pvs)
37+
if err != nil {
38+
return err
39+
}
40+
41+
type MetadataOld struct {
42+
Type string `json:"type"`
43+
IsTagged bool `json:"is_tagged"`
44+
Platform string `json:"platform,omitempty"`
45+
Description string `json:"description,omitempty"`
46+
Authors []string `json:"authors,omitempty"`
47+
Licenses string `json:"license,omitempty"`
48+
ProjectURL string `json:"project_url,omitempty"`
49+
RepositoryURL string `json:"repository_url,omitempty"`
50+
DocumentationURL string `json:"documentation_url,omitempty"`
51+
Labels map[string]string `json:"labels,omitempty"`
52+
ImageLayers []string `json:"layer_creation,omitempty"`
53+
MultiArch map[string]string `json:"multiarch,omitempty"`
54+
}
55+
56+
type Manifest struct {
57+
Platform string `json:"platform"`
58+
Digest string `json:"digest"`
59+
Size int64 `json:"size"`
60+
}
61+
62+
type MetadataNew struct {
63+
Type string `json:"type"`
64+
IsTagged bool `json:"is_tagged"`
65+
Platform string `json:"platform,omitempty"`
66+
Description string `json:"description,omitempty"`
67+
Authors []string `json:"authors,omitempty"`
68+
Licenses string `json:"license,omitempty"`
69+
ProjectURL string `json:"project_url,omitempty"`
70+
RepositoryURL string `json:"repository_url,omitempty"`
71+
DocumentationURL string `json:"documentation_url,omitempty"`
72+
Labels map[string]string `json:"labels,omitempty"`
73+
ImageLayers []string `json:"layer_creation,omitempty"`
74+
Manifests []*Manifest `json:"manifests,omitempty"`
75+
}
76+
77+
for _, pv := range pvs {
78+
var old *MetadataOld
79+
if err := json.Unmarshal([]byte(pv.MetadataJSON), &old); err != nil {
80+
return err
81+
}
82+
83+
// Calculate the size of every contained manifest
84+
85+
manifests := make([]*Manifest, 0, len(old.MultiArch))
86+
for platform, digest := range old.MultiArch {
87+
size, err := sess.
88+
Table("package_blob").
89+
Join("INNER", "package_file", "package_blob.id = package_file.blob_id").
90+
Join("INNER", "package_version pv", "pv.id = package_file.version_id").
91+
Join("INNER", "package_version pv2", "pv2.package_id = pv.package_id").
92+
Where("pv.lower_version = ? AND pv2.id = ?", strings.ToLower(digest), pv.ID).
93+
SumInt(new(PackageBlob), "size")
94+
if err != nil {
95+
return err
96+
}
97+
98+
manifests = append(manifests, &Manifest{
99+
Platform: platform,
100+
Digest: digest,
101+
Size: size,
102+
})
103+
}
104+
105+
// Convert to new metadata format
106+
107+
new := &MetadataNew{
108+
Type: old.Type,
109+
IsTagged: old.IsTagged,
110+
Platform: old.Platform,
111+
Description: old.Description,
112+
Authors: old.Authors,
113+
Licenses: old.Licenses,
114+
ProjectURL: old.ProjectURL,
115+
RepositoryURL: old.RepositoryURL,
116+
DocumentationURL: old.DocumentationURL,
117+
Labels: old.Labels,
118+
ImageLayers: old.ImageLayers,
119+
Manifests: manifests,
120+
}
121+
122+
metadataJSON, err := json.Marshal(new)
123+
if err != nil {
124+
return err
125+
}
126+
127+
pv.MetadataJSON = string(metadataJSON)
128+
129+
if _, err := sess.ID(pv.ID).Update(pv); err != nil {
130+
return err
131+
}
132+
}
133+
134+
return sess.Commit()
135+
}

‎modules/packages/container/metadata.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ type Metadata struct {
6262
DocumentationURL string `json:"documentation_url,omitempty"`
6363
Labels map[string]string `json:"labels,omitempty"`
6464
ImageLayers []string `json:"layer_creation,omitempty"`
65-
MultiArch map[string]string `json:"multiarch,omitempty"`
65+
Manifests []*Manifest `json:"manifests,omitempty"`
66+
}
67+
68+
type Manifest struct {
69+
Platform string `json:"platform"`
70+
Digest string `json:"digest"`
71+
Size int64 `json:"size"`
6672
}
6773

6874
// ParseImageConfig parses the metadata of an image config

‎modules/packages/container/metadata_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestParseImageConfig(t *testing.T) {
4646
},
4747
metadata.Labels,
4848
)
49-
assert.Empty(t, metadata.MultiArch)
49+
assert.Empty(t, metadata.Manifests)
5050

5151
configHelm := `{"description":"` + description + `", "home": "` + projectURL + `", "sources": ["` + repositoryURL + `"], "maintainers":[{"name":"` + author + `"}]}`
5252

‎modules/public/public.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
4545
return
4646
}
4747

48-
var corsSent bool
4948
if opts.CorsHandler != nil {
49+
var corsSent bool
5050
opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
5151
corsSent = true
5252
})).ServeHTTP(resp, req)
53-
}
54-
// If CORS is not sent, the response must have been written by other handlers
55-
if !corsSent {
56-
return
53+
// If CORS is not sent, the response must have been written by other handlers
54+
if !corsSent {
55+
return
56+
}
5757
}
5858

5959
file := req.URL.Path[len(opts.Prefix):]

‎modules/translation/i18n/i18n_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package i18n
55

66
import (
7+
"strings"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -75,3 +76,56 @@ c=22
7576
assert.Equal(t, "21", ls.Tr("lang1", "b"))
7677
assert.Equal(t, "22", ls.Tr("lang1", "c"))
7778
}
79+
80+
func TestLocaleStoreQuirks(t *testing.T) {
81+
const nl = "\n"
82+
q := func(q1, s string, q2 ...string) string {
83+
return q1 + s + strings.Join(q2, "")
84+
}
85+
testDataList := []struct {
86+
in string
87+
out string
88+
hint string
89+
}{
90+
{` xx`, `xx`, "simple, no quote"},
91+
{`" xx"`, ` xx`, "simple, double-quote"},
92+
{`' xx'`, ` xx`, "simple, single-quote"},
93+
{"` xx`", ` xx`, "simple, back-quote"},
94+
95+
{`x\"y`, `x\"y`, "no unescape, simple"},
96+
{q(`"`, `x\"y`, `"`), `"x\"y"`, "unescape, double-quote"},
97+
{q(`'`, `x\"y`, `'`), `x\"y`, "no unescape, single-quote"},
98+
{q("`", `x\"y`, "`"), `x\"y`, "no unescape, back-quote"},
99+
100+
{q(`"`, `x\"y`) + nl + "b=", `"x\"y`, "half open, double-quote"},
101+
{q(`'`, `x\"y`) + nl + "b=", `'x\"y`, "half open, single-quote"},
102+
{q("`", `x\"y`) + nl + "b=`", `x\"y` + nl + "b=", "half open, back-quote, multi-line"},
103+
104+
{`x ; y`, `x ; y`, "inline comment (;)"},
105+
{`x # y`, `x # y`, "inline comment (#)"},
106+
{`x \; y`, `x ; y`, `inline comment (\;)`},
107+
{`x \# y`, `x # y`, `inline comment (\#)`},
108+
}
109+
110+
for _, testData := range testDataList {
111+
ls := NewLocaleStore()
112+
err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
113+
assert.NoError(t, err, testData.hint)
114+
assert.Equal(t, testData.out, ls.Tr("lang1", "a"), testData.hint)
115+
assert.NoError(t, ls.Close())
116+
}
117+
118+
// TODO: Crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes
119+
// and Crowdin always outputs quoted strings if there are quotes in the strings.
120+
// So, Gitea's `key="quoted" unquoted` content shouldn't be used on Crowdin directly,
121+
// it should be converted to `key="\"quoted\" unquoted"` first.
122+
// TODO: We can not use UnescapeValueDoubleQuotes=true, because there are a lot of back-quotes in en-US.ini,
123+
// then Crowdin will output:
124+
// > key = "`x \" y`"
125+
// Then Gitea will read a string with back-quotes, which is incorrect.
126+
// TODO: Crowdin might generate multi-line strings, quoted by double-quote, it's not supported by LocaleStore
127+
// LocaleStore uses back-quote for multi-line strings, it's not supported by Crowdin.
128+
// TODO: Crowdin doesn't support back-quote as string quoter, it mainly uses double-quote
129+
// so, the following line will be parsed as: value="`first", comment="second`" on Crowdin
130+
// > a = `first; second`
131+
}

‎options/locale/locale_en-US.ini

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,10 +2140,10 @@ settings.dismiss_stale_approvals_desc = When new commits that change the content
21402140
settings.require_signed_commits = Require Signed Commits
21412141
settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable.
21422142
settings.protect_branch_name_pattern = Protected Branch Name Pattern
2143-
settings.protect_protected_file_patterns = `Protected file patterns (separated using semicolon ';'):`
2144-
settings.protect_protected_file_patterns_desc = `Protected files are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. 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>.`
2145-
settings.protect_unprotected_file_patterns = `Unprotected file patterns (separated using semicolon ';'):`
2146-
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>.`
2143+
settings.protect_protected_file_patterns = "Protected file patterns (separated using semicolon ';'):"
2144+
settings.protect_protected_file_patterns_desc = "Protected files are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. 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>."
2145+
settings.protect_unprotected_file_patterns = "Unprotected file patterns (separated using semicolon ';'):"
2146+
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>."
21472147
settings.add_protected_branch = Enable protection
21482148
settings.delete_protected_branch = Disable protection
21492149
settings.update_protect_branch_success = Branch protection for rule '%s' has been updated.

‎options/locale/locale_pt-PT.ini

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,10 +2140,10 @@ settings.dismiss_stale_approvals_desc=Quando novos cometimentos que mudam o cont
21402140
settings.require_signed_commits=Exigir cometimentos assinados
21412141
settings.require_signed_commits_desc=Rejeitar envios para este ramo que não estejam assinados ou que não sejam validáveis.
21422142
settings.protect_branch_name_pattern=Padrão do nome do ramo protegido
2143-
settings.protect_protected_file_patterns=`Padrões de ficheiros protegidos (separados com ponto e vírgula ' ;'):`
2144-
settings.protect_protected_file_patterns_desc=`Não é permitido alterar imediatamente ficheiros protegidos, mesmo que o utilizador tenha autorização para adicionar, editar ou eliminar ficheiros neste ramo. Podem ser usados múltiplos padrões separados por ponto e vírgula (' ;'). 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>.`
2145-
settings.protect_unprotected_file_patterns=`Padrões de ficheiros desprotegidos (separados com ponto e vírgula ' ;'):`
2146-
settings.protect_unprotected_file_patterns_desc=`Ficheiros desprotegidos que é permitido alterar imediatamente se o utilizador tiver permissão de escrita, passando ao lado da restrição no envio. Podem ser usados múltiplos padrões separados por ponto e vírgula (' ;'). 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>.`
2143+
settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'):
2144+
settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
2145+
settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ';'):
2146+
settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
21472147
settings.add_protected_branch=Habilitar salvaguarda
21482148
settings.delete_protected_branch=Desabilitar salvaguarda
21492149
settings.update_protect_branch_success=A salvaguarda do ramo '%s' foi modificada.
@@ -2280,6 +2280,8 @@ diff.image.side_by_side=Lado a Lado
22802280
diff.image.swipe=Deslizar
22812281
diff.image.overlay=Sobrepor
22822282
diff.has_escaped=Esta linha tem caracteres unicode escondidos
2283+
diff.show_file_tree=Mostrar árvore de ficheiros
2284+
diff.hide_file_tree=Esconder árvore de ficheiros
22832285

22842286
releases.desc=Acompanhe as versões e as descargas do repositório.
22852287
release.releases=Lançamentos
@@ -2293,6 +2295,7 @@ release.compare=Comparar
22932295
release.edit=editar
22942296
release.ahead.commits=<strong>%d</strong> cometimentos
22952297
release.ahead.target=para %s desde este lançamento
2298+
tag.ahead.target=para o ramo %s desde esta etiqueta
22962299
release.source_code=Código fonte
22972300
release.new_subheader=Lançamentos organizam as versões do trabalho.
22982301
release.edit_subheader=Lançamentos organizam as versões do trabalho.
@@ -3364,6 +3367,7 @@ runners.status.idle=Parada
33643367
runners.status.active=Em funcionamento
33653368
runners.status.offline=Desconectada
33663369
runners.version=Versão
3370+
runners.reset_registration_token_success=O código de incrição do executor foi reposto com sucesso
33673371

33683372
runs.all_workflows=Todas as sequências de trabalho
33693373
runs.open_tab=%d abertas

‎options/locale/locale_zh-CN.ini

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,7 @@ release=版本发布
10681068
releases=版本发布
10691069
tag=Git标签
10701070
released_this=发布
1071+
tagged_this=已标记
10711072
file.title=%s 位于 %s
10721073
file_raw=原始文件
10731074
file_history=文件历史
@@ -1276,6 +1277,7 @@ issues.choose.blank=默认模板
12761277
issues.choose.blank_about=从默认模板创建一个工单。
12771278
issues.choose.ignore_invalid_templates=已忽略无效模板
12781279
issues.choose.invalid_templates=发现了 %v 个无效模板
1280+
issues.choose.invalid_config=问题配置包含错误:
12791281
issues.no_ref=分支/标记未指定
12801282
issues.create=创建工单
12811283
issues.new_label=创建标签
@@ -1489,6 +1491,9 @@ issues.due_date_invalid=到期日期无效或超出范围。请使用 'yyyy-mm-d
14891491
issues.dependency.title=依赖工单
14901492
issues.dependency.issue_no_dependencies=没有设置依赖项。
14911493
issues.dependency.pr_no_dependencies=没有设置依赖项。
1494+
issues.dependency.no_permission_1=您没有读取 %d 依赖关系的权限
1495+
issues.dependency.no_permission_n=您没有读取 %d 依赖关系的权限
1496+
issues.dependency.no_permission.can_remove=您没有读取此依赖关系的权限,但可以删除此依赖关系
14921497
issues.dependency.add=添加依赖工单...
14931498
issues.dependency.cancel=取消
14941499
issues.dependency.remove=删除
@@ -2135,10 +2140,10 @@ settings.dismiss_stale_approvals_desc=当新的提交更改合并请求内容被
21352140
settings.require_signed_commits=需要签名提交
21362141
settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提交到分支
21372142
settings.protect_branch_name_pattern=受保护的分支名称模式
2138-
settings.protect_protected_file_patterns=`"受保护的文件模式 (使用分号 '\;' 分隔):" ;'):``
2139-
settings.protect_protected_file_patterns_desc=`即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号分隔多个模式(' ;'). 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>.`
2140-
settings.protect_unprotected_file_patterns=`不受保护的文件模式 (使用分号分隔 ' ;'):`
2141-
settings.protect_unprotected_file_patterns_desc=`如果用户有写入权限,则允许直接更改的不受保护的文件,以绕过推送限制。可以使用分号分隔多重模式 (' ;'). 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>.`
2143+
settings.protect_protected_file_patterns=受保护的文件模式(使用分号 ';' 分隔):
2144+
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号 (';') 分隔多个模式。 见<a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a>文档了解模式语法。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
2145+
settings.protect_unprotected_file_patterns=不受保护的文件模式(使用分号 ';' 分隔):
2146+
settings.protect_unprotected_file_patterns_desc=如果用户有写权限,则允许直接更改的不受保护的文件,以绕过推送限制。可以使用分号分隔多个模式 (';')。 见 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> 文档了解模式语法。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
21422147
settings.add_protected_branch=启用保护
21432148
settings.delete_protected_branch=禁用保护
21442149
settings.update_protect_branch_success=分支 "%s" 的分支保护已更新。
@@ -2275,6 +2280,8 @@ diff.image.side_by_side=双排
22752280
diff.image.swipe=滑动
22762281
diff.image.overlay=叠加
22772282
diff.has_escaped=这一行有隐藏的 Unicode 字符
2283+
diff.show_file_tree=显示文件树
2284+
diff.hide_file_tree=隐藏文件树
22782285

22792286
releases.desc=跟踪项目版本和下载。
22802287
release.releases=版本发布
@@ -2288,6 +2295,7 @@ release.compare=比较
22882295
release.edit=编辑
22892296
release.ahead.commits=<strong>%d</strong> 次提交
22902297
release.ahead.target=在此版本发布后被加入到 %s
2298+
tag.ahead.target=自此标签到 %s
22912299
release.source_code=源代码
22922300
release.new_subheader=版本发布组织项目的版本。
22932301
release.edit_subheader=版本发布组织项目的版本。
@@ -3359,6 +3367,7 @@ runners.status.idle=空闲
33593367
runners.status.active=激活
33603368
runners.status.offline=离线
33613369
runners.version=版本
3370+
runners.reset_registration_token_success=成功重置运行器注册令牌
33623371
33633372
runs.all_workflows=所有工作流
33643373
runs.open_tab=%d 开启中

‎options/locale/locale_zh-TW.ini

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,7 @@ issues.choose.blank=預設
12761276
issues.choose.blank_about=從預設範本建立問題。
12771277
issues.choose.ignore_invalid_templates=已忽略無效的範本
12781278
issues.choose.invalid_templates=找到了 %v 個無效的範本
1279+
issues.choose.invalid_config=問題設定包含錯誤:
12791280
issues.no_ref=未指定分支或標籤
12801281
issues.create=建立問題
12811282
issues.new_label=新增標籤
@@ -1935,6 +1936,7 @@ settings.trust_model.collaborator.long=協作者: 信任協作者的簽署
19351936
settings.trust_model.collaborator.desc=此儲存庫協作者的有效簽署將被標記為「受信任」(無論它們是否符合提交者),簽署只符合提交者時將標記為「不受信任」,都不符合時標記為「不符合」。
19361937
settings.trust_model.committer=提交者
19371938
settings.trust_model.committer.long=提交者: 信任與提交者相符的簽署 (此選項與 GitHub 相同,這會強制 Gitea 簽署提交並以 Gitea 作為提交者)
1939+
settings.trust_model.committer.desc=提交者的有效簽署將被標記為「受信任」,否則將被標記為「不符合」。這將會強制 Gitea 成為受簽署提交的提交者,實際的提交者將於提交訊息結尾被標記為「Co-authored-by:」和「Co-committed-by:」。預設的 Gitea 金鑰必須符合資料庫中的一位使用者。
19381940
settings.trust_model.collaboratorcommitter=協作者+提交者
19391941
settings.trust_model.collaboratorcommitter.long=協作者 + 提交者: 信任協作者同時是提交者的簽署
19401942
settings.trust_model.collaboratorcommitter.desc=此儲存庫協作者的有效簽署在他同時是提交者時將被標記為「受信任」,簽署只符合提交者時將標記為「不受信任」,都不符合時標記為「不符合」。這會強制 Gitea 成為受簽署提交的提交者,實際的提交者將於提交訊息結尾被標記為「Co-Authored-By:」和「Co-Committed-By:」。預設的 Gitea 金鑰必須符合資料庫中的一位使用者。
@@ -2134,6 +2136,10 @@ settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的
21342136
settings.require_signed_commits=僅接受經簽署的提交
21352137
settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。
21362138
settings.protect_branch_name_pattern=受保護的分支名稱模式
2139+
settings.protect_protected_file_patterns=受保護的檔案模式 (以分號區隔「;」):
2140+
settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改、刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「;」分隔多個模式。請於 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> 文件查看模式格式。範例: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
2141+
settings.protect_unprotected_file_patterns=未受保護的檔案模式 (以分號區隔「;」):
2142+
settings.protect_unprotected_file_patterns_desc=當使用者有寫入權限時,可繞過推送限制,直接修改未受保護的檔案。可以用半形分號「;」分隔多個模式。請於 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> 文件查看模式格式。範例: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
21372143
settings.add_protected_branch=啟用保護
21382144
settings.delete_protected_branch=停用保護
21392145
settings.update_protect_branch_success=已更新「%s」的分支保護。
@@ -2270,6 +2276,8 @@ diff.image.side_by_side=並列
22702276
diff.image.swipe=滑動
22712277
diff.image.overlay=重疊
22722278
diff.has_escaped=這一行有隱藏的 Unicode 字元
2279+
diff.show_file_tree=顯示檔案樹狀圖
2280+
diff.hide_file_tree=隱藏檔案樹狀圖
22732281

22742282
releases.desc=追蹤專案版本和檔案下載。
22752283
release.releases=版本發布
@@ -2283,6 +2291,7 @@ release.compare=比較
22832291
release.edit=編輯
22842292
release.ahead.commits=<strong>%d</strong> 次提交
22852293
release.ahead.target=在此版本發布後被加入到 %s
2294+
tag.ahead.target=在此標籤後被加入到 %s
22862295
release.source_code=程式碼
22872296
release.new_subheader=發布、整理專案的版本。
22882297
release.edit_subheader=發布、整理專案的版本。
@@ -3354,6 +3363,7 @@ runners.status.idle=閒置
33543363
runners.status.active=啟用
33553364
runners.status.offline=離線
33563365
runners.version=版本
3366+
runners.reset_registration_token_success=成功重設了 Runner 註冊 Token
33573367

33583368
runs.all_workflows=所有工作流程
33593369
runs.open_tab=%d 開放中

‎package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"add-asset-webpack-plugin": "2.0.1",
2020
"ansi-to-html": "0.7.2",
2121
"asciinema-player": "3.2.0",
22+
"clippie": "3.1.4",
2223
"css-loader": "6.7.3",
2324
"dropzone": "6.0.0-beta.2",
2425
"easymde": "2.18.0",
@@ -55,6 +56,7 @@
5556
"@playwright/test": "1.31.2",
5657
"@rollup/pluginutils": "5.0.2",
5758
"@stoplight/spectral-cli": "6.6.0",
59+
"@vitejs/plugin-vue": "4.1.0",
5860
"eslint": "8.36.0",
5961
"eslint-plugin-import": "2.27.5",
6062
"eslint-plugin-jquery": "1.5.1",

‎routers/api/packages/container/manifest.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
217217

218218
metadata := &container_module.Metadata{
219219
Type: container_module.TypeOCI,
220-
MultiArch: make(map[string]string),
220+
Manifests: make([]*container_module.Manifest, 0, len(index.Manifests)),
221221
}
222222

223223
for _, manifest := range index.Manifests {
@@ -233,7 +233,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
233233
}
234234
}
235235

236-
_, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
236+
pfd, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
237237
OwnerID: mci.Owner.ID,
238238
Image: mci.Image,
239239
Digest: string(manifest.Digest),
@@ -246,7 +246,18 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
246246
return err
247247
}
248248

249-
metadata.MultiArch[platform] = string(manifest.Digest)
249+
size, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{
250+
VersionID: pfd.File.VersionID,
251+
})
252+
if err != nil {
253+
return err
254+
}
255+
256+
metadata.Manifests = append(metadata.Manifests, &container_module.Manifest{
257+
Platform: platform,
258+
Digest: string(manifest.Digest),
259+
Size: size,
260+
})
250261
}
251262

252263
pv, err := createPackageAndVersion(ctx, mci, metadata)
@@ -369,8 +380,8 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
369380
return nil, err
370381
}
371382
}
372-
for _, digest := range metadata.MultiArch {
373-
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, digest); err != nil {
383+
for _, manifest := range metadata.Manifests {
384+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
374385
log.Error("Error setting package version property: %v", err)
375386
return nil, err
376387
}

‎routers/web/admin/auths.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,11 @@ func EditAuthSourcePost(ctx *context.Context) {
426426
source.IsActive = form.IsActive
427427
source.IsSyncEnabled = form.IsSyncEnabled
428428
source.Cfg = config
429-
// FIXME: if the name conflicts, it will result in 500: Error 1062: Duplicate entry 'aa' for key 'login_source.UQE_login_source_name'
430429
if err := auth.UpdateSource(source); err != nil {
431-
if oauth2.IsErrOpenIDConnectInitialize(err) {
430+
if auth.IsErrSourceAlreadyExist(err) {
431+
ctx.Data["Err_Name"] = true
432+
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthEdit, form)
433+
} else if oauth2.IsErrOpenIDConnectInitialize(err) {
432434
ctx.Flash.Error(err.Error(), true)
433435
ctx.Data["Err_DiscoveryURL"] = true
434436
ctx.HTML(http.StatusOK, tplAuthEdit)

‎templates/admin/auth/source/oauth.tmpl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@
7474

7575
<div class="field">
7676
<label for="oauth2_scopes">{{.locale.Tr "admin.auths.oauth2_scopes"}}</label>
77-
<input id="oauth2_scopes" name="oauth2_scopes" values="{{.oauth2_scopes}}">
77+
<input id="oauth2_scopes" name="oauth2_scopes" value="{{.oauth2_scopes}}">
7878
</div>
7979
<div class="field">
8080
<label for="oauth2_required_claim_name">{{.locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
81-
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{.oauth2_required_claim_name}}">
81+
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{.oauth2_required_claim_name}}">
8282
<p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
8383
</div>
8484
<div class="field">
8585
<label for="oauth2_required_claim_value">{{.locale.Tr "admin.auths.oauth2_required_claim_value"}}</label>
86-
<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{.oauth2_required_claim_value}}">
86+
<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" value="{{.oauth2_required_claim_value}}">
8787
<p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
8888
</div>
8989
<div class="field">
@@ -92,18 +92,18 @@
9292
</div>
9393
<div class="field">
9494
<label for="oauth2_admin_group">{{.locale.Tr "admin.auths.oauth2_admin_group"}}</label>
95-
<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{.oauth2_group_claim_name}}">
95+
<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{.oauth2_admin_group}}">
9696
</div>
9797
<div class="field">
9898
<label for="oauth2_restricted_group">{{.locale.Tr "admin.auths.oauth2_restricted_group"}}</label>
99-
<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{.oauth2_group_claim_name}}">
99+
<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{.oauth2_restricted_group}}">
100100
</div>
101101
<div class="field">
102102
<label>{{.locale.Tr "admin.auths.oauth2_map_group_to_team"}}</label>
103-
<input name="oauth2_group_team_map" value="{{.group_team_map}}" placeholder='e.g. {"Developer": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'>
103+
<input name="oauth2_group_team_map" value="{{.oauth2_group_team_map}}" placeholder='e.g. {"Developer": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'>
104104
</div>
105105
<div class="ui checkbox">
106106
<label>{{.locale.Tr "admin.auths.oauth2_map_group_to_team_removal"}}</label>
107-
<input name="oauth2_group_team_map_removal" type="checkbox" {{if .group_team_map_removal}}checked{{end}}>
107+
<input name="oauth2_group_team_map_removal" type="checkbox" {{if .oauth2_group_team_map_removal}}checked{{end}}>
108108
</div>
109109
</div>

‎templates/home.tmpl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
{{template "base/head" .}}
22
<div role="main" aria-label="{{if .IsSigned}}{{.locale.Tr "dashboard"}}{{else}}{{.locale.Tr "home"}}{{end}}" class="page-content home">
3-
<div class="ui stackable middle very relaxed page grid">
4-
<div class="sixteen wide center aligned centered column">
5-
<div>
6-
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{.locale.Tr "logo"}}">
7-
</div>
3+
<div class="gt-mb-5 gt-px-5">
4+
<div class="center">
5+
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{.locale.Tr "logo"}}">
86
<div class="hero">
97
<h1 class="ui icon header title">
108
{{AppName}}

‎templates/package/content/container.tmpl

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,27 @@
2323
</div>
2424
</div>
2525
</div>
26-
{{if .PackageDescriptor.Metadata.MultiArch}}
26+
{{if .PackageDescriptor.Metadata.Manifests}}
2727
<h4 class="ui top attached header">{{.locale.Tr "packages.container.multi_arch"}}</h4>
2828
<div class="ui attached segment">
29-
<div class="ui form">
30-
{{range $arch, $digest := .PackageDescriptor.Metadata.MultiArch}}
31-
<div class="field">
32-
<label>{{svg "octicon-terminal"}} {{$arch}}</label>
33-
{{if eq $.PackageDescriptor.Metadata.Type "oci"}}
34-
<div class="markup"><pre class="code-block"><code>docker pull {{$.RegistryHost}}/{{$.PackageDescriptor.Owner.LowerName}}/{{$.PackageDescriptor.Package.LowerName}}@{{$digest}}</code></pre></div>
29+
<table class="ui very basic compact table">
30+
<thead>
31+
<tr>
32+
<th>{{.locale.Tr "packages.container.digest"}}</th>
33+
<th>{{.locale.Tr "packages.container.multi_arch"}}</th>
34+
<th>{{.locale.Tr "admin.packages.size"}}</th>
35+
</tr>
36+
</thead>
37+
<tbody>
38+
{{range .PackageDescriptor.Metadata.Manifests}}
39+
<tr>
40+
<td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td>
41+
<td>{{.Platform}}</td>
42+
<td>{{FileSize .Size}}</td>
43+
</tr>
3544
{{end}}
36-
</div>
37-
{{end}}
38-
</div>
45+
</tbody>
46+
</table>
3947
</div>
4048
{{end}}
4149
{{if .PackageDescriptor.Metadata.Description}}

‎templates/package/view.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@
6262
{{template "package/metadata/rubygems" .}}
6363
{{template "package/metadata/swift" .}}
6464
{{template "package/metadata/vagrant" .}}
65+
{{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}}
6566
<div class="item">{{svg "octicon-database" 16 "gt-mr-3"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
67+
{{end}}
6668
</div>
6769
{{if not (eq .PackageDescriptor.Package.Type "container")}}
6870
<div class="ui divider"></div>

‎templates/repo/commit_status.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{if eq .State "pending"}}
2-
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
2+
{{svg "octicon-dot-fill" 18 "commit-status icon text grey"}}
33
{{end}}
44
{{if eq .State "running"}}
55
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}

‎templates/repo/diff/comment_form.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
{{if $.reply}}
2828
<button class="ui submit green tiny button btn-reply" type="submit">{{$.root.locale.Tr "repo.diff.comment.reply"}}</button>
2929
<input type="hidden" name="reply" value="{{$.reply}}">
30+
<input type="hidden" name="single_review" value="true">
3031
{{else}}
3132
{{if $.root.CurrentReview}}
3233
<button name="pending_review" type="submit" class="ui submit green tiny button btn-add-comment">{{$.root.locale.Tr "repo.diff.comment.add_review_comment"}}</button>

‎templates/repo/issue/list.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
{{.locale.Tr "repo.issues.filter_sort"}}
183183
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
184184
</span>
185-
<div class="left menu">
185+
<div class="menu">
186186
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
187187
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
188188
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>

‎tests/integration/api_packages_container_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func TestPackageContainer(t *testing.T) {
321321
metadata := pd.Metadata.(*container_module.Metadata)
322322
assert.Equal(t, container_module.TypeOCI, metadata.Type)
323323
assert.Len(t, metadata.ImageLayers, 2)
324-
assert.Empty(t, metadata.MultiArch)
324+
assert.Empty(t, metadata.Manifests)
325325

326326
assert.Len(t, pd.Files, 3)
327327
for _, pfd := range pd.Files {
@@ -462,10 +462,22 @@ func TestPackageContainer(t *testing.T) {
462462
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
463463
metadata := pd.Metadata.(*container_module.Metadata)
464464
assert.Equal(t, container_module.TypeOCI, metadata.Type)
465-
assert.Contains(t, metadata.MultiArch, "linux/arm/v7")
466-
assert.Equal(t, manifestDigest, metadata.MultiArch["linux/arm/v7"])
467-
assert.Contains(t, metadata.MultiArch, "linux/arm64/v8")
468-
assert.Equal(t, untaggedManifestDigest, metadata.MultiArch["linux/arm64/v8"])
465+
assert.Len(t, metadata.Manifests, 2)
466+
assert.Condition(t, func() bool {
467+
for _, m := range metadata.Manifests {
468+
switch m.Platform {
469+
case "linux/arm/v7":
470+
assert.Equal(t, manifestDigest, m.Digest)
471+
assert.EqualValues(t, 1524, m.Size)
472+
case "linux/arm64/v8":
473+
assert.Equal(t, untaggedManifestDigest, m.Digest)
474+
assert.EqualValues(t, 1514, m.Size)
475+
default:
476+
return false
477+
}
478+
}
479+
return true
480+
})
469481

470482
assert.Len(t, pd.Files, 1)
471483
assert.True(t, pd.Files[0].File.IsLead)

‎tests/integration/repo_commits_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec
110110
}
111111

112112
func TestRepoCommitsWithStatusPending(t *testing.T) {
113-
doTestRepoCommitWithStatus(t, "pending", "octicon-dot-fill", "yellow")
113+
doTestRepoCommitWithStatus(t, "pending", "octicon-dot-fill", "grey")
114114
}
115115

116116
func TestRepoCommitsWithStatusSuccess(t *testing.T) {
@@ -129,6 +129,10 @@ func TestRepoCommitsWithStatusWarning(t *testing.T) {
129129
doTestRepoCommitWithStatus(t, "warning", "gitea-exclamation", "yellow")
130130
}
131131

132+
func TestRepoCommitsWithStatusRunning(t *testing.T) {
133+
doTestRepoCommitWithStatus(t, "running", "octicon-dot-fill", "yellow")
134+
}
135+
132136
func TestRepoCommitsStatusParallel(t *testing.T) {
133137
defer tests.PrepareTestEnv(t)()
134138

‎vitest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {defineConfig} from 'vitest/dist/config.js';
22
import {readFile} from 'node:fs/promises';
33
import {dataToEsm} from '@rollup/pluginutils';
44
import {extname} from 'node:path';
5+
import vue from '@vitejs/plugin-vue';
56

67
function stringPlugin() {
78
return {
@@ -28,5 +29,6 @@ export default defineConfig({
2829
},
2930
plugins: [
3031
stringPlugin(),
32+
vue(),
3133
],
3234
});

‎web_src/css/base.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
--color-purple: #a333c8;
7979
--color-pink: #e03997;
8080
--color-brown: #a5673f;
81-
--color-grey: #888888;
8281
--color-black: #1b1c1d;
8382
/* light variants - produced via Sass scale-color(color, $lightness: +25%) */
8483
--color-red-light: #e45e5e;
@@ -92,9 +91,10 @@
9291
--color-purple-light: #bb64d8;
9392
--color-pink-light: #e86bb1;
9493
--color-brown-light: #c58b66;
95-
--color-grey-light: #a6a6a6;
9694
--color-black-light: #525558;
9795
/* other colors */
96+
--color-grey: #707070;
97+
--color-grey-light: #838383;
9898
--color-gold: #a1882b;
9999
--color-white: #ffffff;
100100
--color-diff-removed-word-bg: #fdb8c0;
@@ -2657,6 +2657,10 @@ table th[data-sortt-desc] .svg {
26572657
border-radius: 0 0 var(--border-radius) var(--border-radius);
26582658
}
26592659

2660+
.ui.multiple.dropdown > .label {
2661+
box-shadow: 0 0 0 1px var(--color-secondary) inset;
2662+
}
2663+
26602664
.text-label {
26612665
display: inline-flex !important;
26622666
align-items: center !important;

‎web_src/css/form.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ textarea:focus,
9494
color: var(--color-text);
9595
}
9696

97+
.ui.form .required.fields:not(.grouped) > .field > label::after,
98+
.ui.form .required.fields.grouped > label::after,
99+
.ui.form .required.field > label::after,
100+
.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
101+
.ui.form .required.field > .checkbox::after,
102+
.ui.form label.required::after {
103+
color: var(--color-red);
104+
}
105+
97106
.ui.input,
98107
.ui.checkbox input:focus ~ label::after,
99108
.ui.checkbox input:checked ~ label::after,

‎web_src/css/themes/theme-arc-green.css

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@
6868
--color-purple: #b259d0;
6969
--color-pink: #d22e8b;
7070
--color-brown: #a47252;
71-
--color-grey: #9ea2aa;
72-
--color-black: #1e222e;
71+
--color-black: #2e323e;
7372
/* light variants - produced via Sass scale-color(color, $lightness: -10%) */
7473
--color-red-light: #c23636;
7574
--color-orange-light: #b84f0b;
@@ -82,9 +81,10 @@
8281
--color-purple-light: #a742c9;
8382
--color-pink-light: #be297d;
8483
--color-brown-light: #94674a;
85-
--color-grey-light: #8d919b;
86-
--color-black-light: #1b1f29;
84+
--color-black-light: #292d38;
8785
/* other colors */
86+
--color-grey: #505665;
87+
--color-grey-light: #a1a6b7;
8888
--color-gold: #b1983b;
8989
--color-white: #ffffff;
9090
--color-diff-removed-word-bg: #6f3333;
@@ -124,19 +124,19 @@
124124
--color-orange-badge-hover-bg: #f2711c4d;
125125
--color-git: #f05133;
126126
/* target-based colors */
127-
--color-body: #383c4a;
127+
--color-body: #373b47;
128128
--color-box-header: #404652;
129129
--color-box-body: #2a2e3a;
130130
--color-box-body-highlight: #353945;
131131
--color-text-dark: #dbe0ea;
132-
--color-text: #bbc0ca;
133-
--color-text-light: #a6aab5;
134-
--color-text-light-1: #979ba6;
135-
--color-text-light-2: #8a8e99;
136-
--color-text-light-3: #707687;
132+
--color-text: #cbd0da;
133+
--color-text-light: #bbbfca;
134+
--color-text-light-1: #aaafb9;
135+
--color-text-light-2: #9a9ea9;
136+
--color-text-light-3: #8a8e99;
137137
--color-footer: #2e323e;
138138
--color-timeline: #4c525e;
139-
--color-input-text: #d5dbe6;
139+
--color-input-text: #dfe3ec;
140140
--color-input-background: #232933;
141141
--color-input-toggle-background: #454a57;
142142
--color-input-border: #454a57;
@@ -159,7 +159,7 @@
159159
--color-secondary-bg: #2a2e3a;
160160
--color-text-focus: #fff;
161161
--color-expand-button: #3c404d;
162-
--color-placeholder-text: #6a737d;
162+
--color-placeholder-text: #8a8e99;
163163
--color-editor-line-highlight: var(--color-primary-light-5);
164164
--color-project-board-bg: var(--color-secondary-light-2);
165165
--color-caret: var(--color-text); /* should ideally be --color-text-dark, see #15651 */
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {expect, test} from 'vitest';
2+
3+
import {ansiLogToHTML} from './RepoActionView.vue';
4+
import AnsiToHTML from 'ansi-to-html';
5+
6+
test('processConsoleLine', () => {
7+
expect(ansiLogToHTML('abc')).toEqual('abc');
8+
expect(ansiLogToHTML('abc\n')).toEqual('abc');
9+
expect(ansiLogToHTML('abc\r\n')).toEqual('abc');
10+
expect(ansiLogToHTML('\r')).toEqual('');
11+
expect(ansiLogToHTML('\rx\rabc')).toEqual('x\nabc');
12+
expect(ansiLogToHTML('\rabc\rx\r')).toEqual('abc\nx');
13+
14+
expect(ansiLogToHTML('\x1b[30mblack\x1b[37mwhite')).toEqual('<span style="color:#000">black<span style="color:#AAA">white</span></span>');
15+
expect(ansiLogToHTML('<script>')).toEqual('&lt;script&gt;');
16+
17+
18+
// upstream AnsiToHTML has bugs when processing "\033[1A" and "\033[1B", we fixed these control sequences in our code
19+
// if upstream could fix these bugs, we can remove these tests and remove our patch code
20+
const ath = new AnsiToHTML({escapeXML: true});
21+
expect(ath.toHtml('\x1b[1A\x1b[2Ktest\x1b[1B\x1b[1A\x1b[2K')).toEqual('AtestBA'); // AnsiToHTML bug
22+
expect(ath.toHtml('\x1b[1A\x1b[2K\rtest\r\x1b[1B\x1b[1A\x1b[2K')).toEqual('A\rtest\rBA'); // AnsiToHTML bug
23+
24+
// test our patched behavior
25+
expect(ansiLogToHTML('\x1b[1A\x1b[2Ktest\x1b[1B\x1b[1A\x1b[2K')).toEqual('test');
26+
expect(ansiLogToHTML('\x1b[1A\x1b[2K\rtest\r\x1b[1B\x1b[1A\x1b[2K')).toEqual('test');
27+
28+
// treat "\033[0K" and "\033[0J" (Erase display/line) as "\r", then it will be covered to "\n" finally.
29+
expect(ansiLogToHTML('a\x1b[Kb\x1b[2Jc')).toEqual('a\nb\nc');
30+
});

‎web_src/js/components/RepoActionView.vue

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ import AnsiToHTML from 'ansi-to-html';
7777
7878
const {csrfToken} = window.config;
7979
80+
const ansiLogRender = new AnsiToHTML({escapeXML: true});
81+
8082
const sfc = {
8183
name: 'RepoActionView',
8284
components: {
@@ -91,8 +93,6 @@ const sfc = {
9193
9294
data() {
9395
return {
94-
ansiToHTML: new AnsiToHTML({escapeXML: true}),
95-
9696
// internal state
9797
loading: false,
9898
intervalID: null,
@@ -214,7 +214,7 @@ const sfc = {
214214
215215
const logMessage = document.createElement('div');
216216
logMessage.className = 'log-msg';
217-
logMessage.innerHTML = this.ansiToHTML.toHtml(line.message);
217+
logMessage.innerHTML = ansiLogToHTML(line.message);
218218
div.appendChild(logMessage);
219219
220220
return div;
@@ -307,6 +307,48 @@ export function initRepositoryActionView() {
307307
view.mount(el);
308308
}
309309
310+
// some unhandled control sequences by AnsiToHTML
311+
// https://man7.org/linux/man-pages/man4/console_codes.4.html
312+
const ansiRegexpRemove = /\x1b\[\d+[A-H]/g; // Move cursor, treat them as no-op.
313+
const ansiRegexpNewLine = /\x1b\[\d?[JK]/g; // Erase display/line, treat them as a Carriage Return
314+
315+
function ansiCleanControlSequences(line) {
316+
if (line.includes('\x1b')) {
317+
line = line.replace(ansiRegexpRemove, '');
318+
line = line.replace(ansiRegexpNewLine, '\r');
319+
}
320+
return line;
321+
}
322+
323+
export function ansiLogToHTML(line) {
324+
if (line.endsWith('\r\n')) {
325+
line = line.substring(0, line.length - 2);
326+
} else if (line.endsWith('\n')) {
327+
line = line.substring(0, line.length - 1);
328+
}
329+
330+
// usually we do not need to process control chars like "\033[", let AnsiToHTML do it
331+
// but AnsiToHTML has bugs, so we need to clean some control sequences first
332+
line = ansiCleanControlSequences(line);
333+
334+
if (!line.includes('\r')) {
335+
return ansiLogRender.toHtml(line);
336+
}
337+
338+
// handle "\rReading...1%\rReading...5%\rReading...100%",
339+
// convert it into a multiple-line string: "Reading...1%\nReading...5%\nReading...100%"
340+
const lines = [];
341+
for (const part of line.split('\r')) {
342+
if (part === '') continue;
343+
const partHtml = ansiLogRender.toHtml(part);
344+
if (partHtml !== '') {
345+
lines.push(partHtml);
346+
}
347+
}
348+
// the log message element is with "white-space: break-spaces;", so use "\n" to break lines
349+
return lines.join('\n');
350+
}
351+
310352
</script>
311353
312354
<style scoped>

‎web_src/js/features/clipboard.js

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,9 @@
11
import {showTemporaryTooltip} from '../modules/tippy.js';
22
import {toAbsoluteUrl} from '../utils.js';
3+
import {clippie} from 'clippie';
34

45
const {copy_success, copy_error} = window.config.i18n;
56

6-
export async function copyToClipboard(content) {
7-
if (content instanceof Blob) {
8-
const item = new ClipboardItem({[content.type]: content});
9-
await navigator.clipboard.write([item]);
10-
} else { // text
11-
try {
12-
await navigator.clipboard.writeText(content);
13-
} catch {
14-
return fallbackCopyToClipboard(content);
15-
}
16-
}
17-
return true;
18-
}
19-
20-
// Fallback to use if navigator.clipboard doesn't exist. Achieved via creating
21-
// a temporary textarea element, selecting the text, and using document.execCommand
22-
function fallbackCopyToClipboard(text) {
23-
if (!document.execCommand) return false;
24-
25-
const tempTextArea = document.createElement('textarea');
26-
tempTextArea.value = text;
27-
28-
// avoid scrolling
29-
tempTextArea.style.top = 0;
30-
tempTextArea.style.left = 0;
31-
tempTextArea.style.position = 'fixed';
32-
33-
document.body.appendChild(tempTextArea);
34-
35-
tempTextArea.select();
36-
37-
// if unsecure (not https), there is no navigator.clipboard, but we can still
38-
// use document.execCommand to copy to clipboard
39-
const success = document.execCommand('copy');
40-
41-
document.body.removeChild(tempTextArea);
42-
43-
return success;
44-
}
45-
467
// For all DOM elements with [data-clipboard-target] or [data-clipboard-text],
478
// this copy-to-clipboard will work for them
489
export function initGlobalCopyToClipboardListener() {
@@ -61,7 +22,7 @@ export function initGlobalCopyToClipboardListener() {
6122
e.preventDefault();
6223

6324
(async() => {
64-
const success = await copyToClipboard(text);
25+
const success = await clippie(text);
6526
showTemporaryTooltip(target, success ? copy_success : copy_error);
6627
})();
6728

‎web_src/js/features/copycontent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import {copyToClipboard} from './clipboard.js';
1+
import {clippie} from 'clippie';
22
import {showTemporaryTooltip} from '../modules/tippy.js';
33
import {convertImage} from '../utils.js';
44

55
const {i18n} = window.config;
66

77
async function doCopy(content, btn) {
8-
const success = await copyToClipboard(content);
8+
const success = await clippie(content);
99
showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
1010
}
1111

‎web_src/js/features/repo-code.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import $ from 'jquery';
22
import {svg} from '../svg.js';
33
import {invertFileFolding} from './file-fold.js';
44
import {createTippy} from '../modules/tippy.js';
5-
import {copyToClipboard} from './clipboard.js';
5+
import {clippie} from 'clippie';
66
import {toAbsoluteUrl} from '../utils.js';
77

88
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
@@ -190,7 +190,7 @@ export function initRepoCodeView() {
190190
currentTarget.closest('tr').outerHTML = blob;
191191
});
192192
$(document).on('click', '.copy-line-permalink', async (e) => {
193-
const success = await copyToClipboard(toAbsoluteUrl(e.currentTarget.getAttribute('data-url')));
193+
const success = await clippie(toAbsoluteUrl(e.currentTarget.getAttribute('data-url')));
194194
if (!success) return;
195195
document.querySelector('.code-line-button')?._tippy?.hide();
196196
});

‎web_src/js/features/repo-issue.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,8 +645,6 @@ export function initRepoIssueTitleEdit() {
645645
$.post(update_url, {
646646
_csrf: csrfToken,
647647
target_branch: targetBranch
648-
}).done((data) => {
649-
$branchTarget.text(data.base_branch);
650648
}).always(() => {
651649
window.location.reload();
652650
});

‎web_src/js/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
22
import './bootstrap.js';
33

4-
import $ from 'jquery';
54
import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue';
65
import {initDashboardRepoList} from './components/DashboardRepoList.vue';
76

@@ -90,6 +89,7 @@ import {initCaptcha} from './features/captcha.js';
9089
import {initRepositoryActionView} from './components/RepoActionView.vue';
9190
import {initGlobalTooltips} from './modules/tippy.js';
9291
import {initGiteaFomantic} from './modules/fomantic.js';
92+
import {onDomReady} from './utils/dom.js';
9393

9494
// Run time-critical code as soon as possible. This is safe to do because this
9595
// script appears at the end of <body> and rendered HTML is accessible at that point.
@@ -98,7 +98,7 @@ initFormattingReplacements();
9898
// Init Gitea's Fomantic settings
9999
initGiteaFomantic();
100100

101-
$(document).ready(() => {
101+
onDomReady(() => {
102102
initGlobalCommon();
103103

104104
initGlobalTooltips();

‎web_src/js/utils/dom.js

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,3 @@
1-
function getComputedStyleProperty(el, prop) {
2-
const cs = el ? window.getComputedStyle(el) : null;
3-
return cs ? cs[prop] : null;
4-
}
5-
6-
function isShown(el) {
7-
return getComputedStyleProperty(el, 'display') !== 'none';
8-
}
9-
10-
function assertShown(el, expectShown) {
11-
if (window.config.runModeIsProd) return;
12-
13-
// to help developers to catch display bugs, this assertion can be removed after next release cycle or if it has been proved that there is no bug.
14-
if (expectShown && !isShown(el)) {
15-
throw new Error('element is hidden but should be shown');
16-
} else if (!expectShown && isShown(el)) {
17-
throw new Error('element is shown but should be hidden');
18-
}
19-
}
20-
211
function elementsCall(el, func, ...args) {
222
if (typeof el === 'string' || el instanceof String) {
233
el = document.querySelectorAll(el);
@@ -41,16 +21,10 @@ function elementsCall(el, func, ...args) {
4121
function toggleShown(el, force) {
4222
if (force === true) {
4323
el.classList.remove('gt-hidden');
44-
assertShown(el, true);
4524
} else if (force === false) {
4625
el.classList.add('gt-hidden');
47-
assertShown(el, false);
4826
} else if (force === undefined) {
49-
const wasShown = window.config.runModeIsProd ? undefined : isShown(el);
5027
el.classList.toggle('gt-hidden');
51-
if (wasShown !== undefined) {
52-
assertShown(el, !wasShown);
53-
}
5428
} else {
5529
throw new Error('invalid force argument');
5630
}
@@ -67,3 +41,11 @@ export function hideElem(el) {
6741
export function toggleElem(el, force) {
6842
elementsCall(el, toggleShown, force);
6943
}
44+
45+
export function onDomReady(cb) {
46+
if (document.readyState === 'loading') {
47+
document.addEventListener('DOMContentLoaded', cb);
48+
} else {
49+
cb();
50+
}
51+
}

0 commit comments

Comments
 (0)
Please sign in to comment.