Date: Sat, 16 Mar 2024 08:09:49 +0100
Subject: [PATCH 007/426] Split filePreviewPatternProcessor into a new type
FilePreview and some functions to make code more maintainable
---
modules/markup/file_preview.go | 269 +++++++++++++++++++++++++++++++++
modules/markup/html.go | 245 +-----------------------------
2 files changed, 276 insertions(+), 238 deletions(-)
create mode 100644 modules/markup/file_preview.go
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
new file mode 100644
index 0000000000..646bf83630
--- /dev/null
+++ b/modules/markup/file_preview.go
@@ -0,0 +1,269 @@
+package markup
+
+import (
+ "bytes"
+ "html/template"
+ "regexp"
+ "slices"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/translation"
+ "golang.org/x/net/html"
+ "golang.org/x/net/html/atom"
+)
+
+var (
+ // filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
+ filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
+)
+
+type FilePreview struct {
+ fileContent []template.HTML
+ subTitle template.HTML
+ lineOffset int
+ urlFull string
+ filePath string
+ start int
+ end int
+}
+
+func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
+ preview := &FilePreview{}
+
+ m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
+ if m == nil {
+ return nil
+ }
+
+ // Ensure that every group has a match
+ if slices.Contains(m, -1) {
+ return nil
+ }
+
+ preview.urlFull = node.Data[m[0]:m[1]]
+
+ // Ensure that we only use links to local repositories
+ if !strings.HasPrefix(preview.urlFull, setting.AppURL+setting.AppSubURL) {
+ return nil
+ }
+
+ projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/")
+
+ commitSha := node.Data[m[4]:m[5]]
+ preview.filePath = node.Data[m[6]:m[7]]
+ hash := node.Data[m[8]:m[9]]
+
+ preview.start = m[0]
+ preview.end = m[1]
+
+ // If url ends in '.', it's very likely that it is not part of the
+ // actual url but used to finish a sentence.
+ if strings.HasSuffix(preview.urlFull, ".") {
+ preview.end--
+ preview.urlFull = preview.urlFull[:len(preview.urlFull)-1]
+ hash = hash[:len(hash)-1]
+ }
+
+ projPathSegments := strings.Split(projPath, "/")
+ fileContent, err := DefaultProcessorHelper.GetRepoFileContent(
+ ctx.Ctx,
+ projPathSegments[len(projPathSegments)-2],
+ projPathSegments[len(projPathSegments)-1],
+ commitSha, preview.filePath,
+ )
+ if err != nil {
+ return nil
+ }
+
+ lineSpecs := strings.Split(hash, "-")
+ lineCount := len(fileContent)
+
+ commitLinkBuffer := new(bytes.Buffer)
+ html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
+
+ if len(lineSpecs) == 1 {
+ line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ if line < 1 || line > lineCount {
+ return nil
+ }
+
+ preview.fileContent = fileContent[line-1 : line]
+ preview.subTitle = locale.Tr(
+ "markup.filepreview.line", line,
+ template.HTML(commitLinkBuffer.String()),
+ )
+
+ preview.lineOffset = line - 1
+ } else {
+ startLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ endLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
+
+ if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
+ return nil
+ }
+
+ preview.fileContent = fileContent[startLine-1 : endLine]
+ preview.subTitle = locale.Tr(
+ "markup.filepreview.lines", startLine, endLine,
+ template.HTML(commitLinkBuffer.String()),
+ )
+
+ preview.lineOffset = startLine - 1
+ }
+
+ return preview
+}
+
+func (p *FilePreview) CreateHtml(locale translation.Locale) *html.Node {
+ table := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Table.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "file-preview"}},
+ }
+ tbody := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Tbody.String(),
+ }
+
+ status := &charset.EscapeStatus{}
+ statuses := make([]*charset.EscapeStatus, len(p.fileContent))
+ for i, line := range p.fileContent {
+ statuses[i], p.fileContent[i] = charset.EscapeControlHTML(line, locale, charset.FileviewContext)
+ status = status.Or(statuses[i])
+ }
+
+ for idx, code := range p.fileContent {
+ tr := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Tr.String(),
+ }
+
+ lineNum := strconv.Itoa(p.lineOffset + idx + 1)
+
+ tdLinesnum := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "id", Val: "L" + lineNum},
+ {Key: "class", Val: "lines-num"},
+ },
+ }
+ spanLinesNum := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Span.String(),
+ Attr: []html.Attribute{
+ {Key: "id", Val: "L" + lineNum},
+ {Key: "data-line-number", Val: lineNum},
+ },
+ }
+ tdLinesnum.AppendChild(spanLinesNum)
+ tr.AppendChild(tdLinesnum)
+
+ if status.Escaped {
+ tdLinesEscape := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "lines-escape"},
+ },
+ }
+
+ if statuses[idx].Escaped {
+ btnTitle := ""
+ if statuses[idx].HasInvisible {
+ btnTitle += locale.TrString("repo.invisible_runes_line") + " "
+ }
+ if statuses[idx].HasAmbiguous {
+ btnTitle += locale.TrString("repo.ambiguous_runes_line")
+ }
+
+ escapeBtn := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Button.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "toggle-escape-button btn interact-bg"},
+ {Key: "title", Val: btnTitle},
+ },
+ }
+ tdLinesEscape.AppendChild(escapeBtn)
+ }
+
+ tr.AppendChild(tdLinesEscape)
+ }
+
+ tdCode := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "rel", Val: "L" + lineNum},
+ {Key: "class", Val: "lines-code chroma"},
+ },
+ }
+ codeInner := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Code.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "code-inner"}},
+ }
+ codeText := &html.Node{
+ Type: html.RawNode,
+ Data: string(code),
+ }
+ codeInner.AppendChild(codeText)
+ tdCode.AppendChild(codeInner)
+ tr.AppendChild(tdCode)
+
+ tbody.AppendChild(tr)
+ }
+
+ table.AppendChild(tbody)
+
+ twrapper := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "ui table"}},
+ }
+ twrapper.AppendChild(table)
+
+ header := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "header"}},
+ }
+ afilepath := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.A.String(),
+ Attr: []html.Attribute{
+ {Key: "href", Val: p.urlFull},
+ {Key: "class", Val: "muted"},
+ },
+ }
+ afilepath.AppendChild(&html.Node{
+ Type: html.TextNode,
+ Data: p.filePath,
+ })
+ header.AppendChild(afilepath)
+
+ psubtitle := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Span.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "text small grey"}},
+ }
+ psubtitle.AppendChild(&html.Node{
+ Type: html.RawNode,
+ Data: string(p.subTitle),
+ })
+ header.AppendChild(psubtitle)
+
+ preview_node := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
+ }
+ preview_node.AppendChild(header)
+ preview_node.AppendChild(twrapper)
+
+ return preview_node
+}
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 1e83dad701..2e38c05f58 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -5,19 +5,15 @@ package markup
import (
"bytes"
- "html/template"
"io"
"net/url"
"path"
"path/filepath"
"regexp"
- "slices"
- "strconv"
"strings"
"sync"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -65,9 +61,6 @@ var (
validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`)
- // filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
- filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
-
// While this email regex is definitely not perfect and I'm sure you can come up
// with edge cases, it is still accepted by the CommonMark specification, as
// well as the HTML5 spec:
@@ -1072,252 +1065,28 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
next := node.NextSibling
for node != nil && node != next {
- m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
- if m == nil {
- return
- }
-
- // Ensure that every group has a match
- if slices.Contains(m, -1) {
- return
- }
-
- urlFull := node.Data[m[0]:m[1]]
-
- // Ensure that we only use links to local repositories
- if !strings.HasPrefix(urlFull, setting.AppURL+setting.AppSubURL) {
- return
- }
-
- projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/")
-
- commitSha := node.Data[m[4]:m[5]]
- filePath := node.Data[m[6]:m[7]]
- hash := node.Data[m[8]:m[9]]
-
- start := m[0]
- end := m[1]
-
- // If url ends in '.', it's very likely that it is not part of the
- // actual url but used to finish a sentence.
- if strings.HasSuffix(urlFull, ".") {
- end--
- urlFull = urlFull[:len(urlFull)-1]
- hash = hash[:len(hash)-1]
- }
-
- projPathSegments := strings.Split(projPath, "/")
- fileContent, err := DefaultProcessorHelper.GetRepoFileContent(
- ctx.Ctx,
- projPathSegments[len(projPathSegments)-2],
- projPathSegments[len(projPathSegments)-1],
- commitSha, filePath,
- )
- if err != nil {
- return
- }
-
- lineSpecs := strings.Split(hash, "-")
- lineCount := len(fileContent)
-
- commitLinkBuffer := new(bytes.Buffer)
- html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
-
- var subTitle template.HTML
- var lineOffset int
-
locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale)
if !ok {
locale = translation.NewLocale("en-US")
}
- if len(lineSpecs) == 1 {
- line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
- if line < 1 || line > lineCount {
- return
- }
-
- fileContent = fileContent[line-1 : line]
- subTitle = locale.Tr(
- "markup.filepreview.line", line,
- template.HTML(commitLinkBuffer.String()),
- )
-
- lineOffset = line - 1
- } else {
- startLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
- endLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
-
- if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
- return
- }
-
- fileContent = fileContent[startLine-1 : endLine]
- subTitle = locale.Tr(
- "markup.filepreview.lines", startLine, endLine,
- template.HTML(commitLinkBuffer.String()),
- )
-
- lineOffset = startLine - 1
+ preview := NewFilePreview(ctx, node, locale)
+ if preview == nil {
+ return
}
- table := &html.Node{
- Type: html.ElementNode,
- Data: atom.Table.String(),
- Attr: []html.Attribute{{Key: "class", Val: "file-preview"}},
- }
- tbody := &html.Node{
- Type: html.ElementNode,
- Data: atom.Tbody.String(),
- }
-
- status := &charset.EscapeStatus{}
- statuses := make([]*charset.EscapeStatus, len(fileContent))
- for i, line := range fileContent {
- statuses[i], fileContent[i] = charset.EscapeControlHTML(line, locale, charset.FileviewContext)
- status = status.Or(statuses[i])
- }
-
- for idx, code := range fileContent {
- tr := &html.Node{
- Type: html.ElementNode,
- Data: atom.Tr.String(),
- }
-
- lineNum := strconv.Itoa(lineOffset + idx + 1)
-
- tdLinesnum := &html.Node{
- Type: html.ElementNode,
- Data: atom.Td.String(),
- Attr: []html.Attribute{
- {Key: "id", Val: "L" + lineNum},
- {Key: "class", Val: "lines-num"},
- },
- }
- spanLinesNum := &html.Node{
- Type: html.ElementNode,
- Data: atom.Span.String(),
- Attr: []html.Attribute{
- {Key: "id", Val: "L" + lineNum},
- {Key: "data-line-number", Val: lineNum},
- },
- }
- tdLinesnum.AppendChild(spanLinesNum)
- tr.AppendChild(tdLinesnum)
-
- if status.Escaped {
- tdLinesEscape := &html.Node{
- Type: html.ElementNode,
- Data: atom.Td.String(),
- Attr: []html.Attribute{
- {Key: "class", Val: "lines-escape"},
- },
- }
-
- if statuses[idx].Escaped {
- btnTitle := ""
- if statuses[idx].HasInvisible {
- btnTitle += locale.TrString("repo.invisible_runes_line") + " "
- }
- if statuses[idx].HasAmbiguous {
- btnTitle += locale.TrString("repo.ambiguous_runes_line")
- }
-
- escapeBtn := &html.Node{
- Type: html.ElementNode,
- Data: atom.Button.String(),
- Attr: []html.Attribute{
- {Key: "class", Val: "toggle-escape-button btn interact-bg"},
- {Key: "title", Val: btnTitle},
- },
- }
- tdLinesEscape.AppendChild(escapeBtn)
- }
-
- tr.AppendChild(tdLinesEscape)
- }
-
- tdCode := &html.Node{
- Type: html.ElementNode,
- Data: atom.Td.String(),
- Attr: []html.Attribute{
- {Key: "rel", Val: "L" + lineNum},
- {Key: "class", Val: "lines-code chroma"},
- },
- }
- codeInner := &html.Node{
- Type: html.ElementNode,
- Data: atom.Code.String(),
- Attr: []html.Attribute{{Key: "class", Val: "code-inner"}},
- }
- codeText := &html.Node{
- Type: html.RawNode,
- Data: string(code),
- }
- codeInner.AppendChild(codeText)
- tdCode.AppendChild(codeInner)
- tr.AppendChild(tdCode)
-
- tbody.AppendChild(tr)
- }
-
- table.AppendChild(tbody)
-
- twrapper := &html.Node{
- Type: html.ElementNode,
- Data: atom.Div.String(),
- Attr: []html.Attribute{{Key: "class", Val: "ui table"}},
- }
- twrapper.AppendChild(table)
-
- header := &html.Node{
- Type: html.ElementNode,
- Data: atom.Div.String(),
- Attr: []html.Attribute{{Key: "class", Val: "header"}},
- }
- afilepath := &html.Node{
- Type: html.ElementNode,
- Data: atom.A.String(),
- Attr: []html.Attribute{
- {Key: "href", Val: urlFull},
- {Key: "class", Val: "muted"},
- },
- }
- afilepath.AppendChild(&html.Node{
- Type: html.TextNode,
- Data: filePath,
- })
- header.AppendChild(afilepath)
-
- psubtitle := &html.Node{
- Type: html.ElementNode,
- Data: atom.Span.String(),
- Attr: []html.Attribute{{Key: "class", Val: "text small grey"}},
- }
- psubtitle.AppendChild(&html.Node{
- Type: html.RawNode,
- Data: string(subTitle),
- })
- header.AppendChild(psubtitle)
-
- preview := &html.Node{
- Type: html.ElementNode,
- Data: atom.Div.String(),
- Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
- }
- preview.AppendChild(header)
- preview.AppendChild(twrapper)
+ preview_node := preview.CreateHtml(locale)
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
- before := node.Data[:start]
- after := node.Data[end:]
+ before := node.Data[:preview.start]
+ after := node.Data[preview.end:]
node.Data = before
nextSibling := node.NextSibling
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "",
}, nextSibling)
- node.Parent.InsertBefore(preview, nextSibling)
+ node.Parent.InsertBefore(preview_node, nextSibling)
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "" + after,
From 8218e80bfc3a1f9ba02ce60f1acafdc0e57c5ae0 Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Sat, 16 Mar 2024 08:18:47 +0100
Subject: [PATCH 008/426] Fix linting issues
---
modules/markup/file_preview.go | 22 ++++++++++++----------
modules/markup/html.go | 4 ++--
2 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 646bf83630..be788aae4b 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -9,16 +9,15 @@ import (
"strings"
"code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
-var (
- // filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
- filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
-)
+// filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
+var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
type FilePreview struct {
fileContent []template.HTML
@@ -82,7 +81,10 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
lineCount := len(fileContent)
commitLinkBuffer := new(bytes.Buffer)
- html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
+ err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
+ if err != nil {
+ log.Error("failed to render commitLink: %v", err)
+ }
if len(lineSpecs) == 1 {
line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
@@ -117,7 +119,7 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
return preview
}
-func (p *FilePreview) CreateHtml(locale translation.Locale) *html.Node {
+func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
table := &html.Node{
Type: html.ElementNode,
Data: atom.Table.String(),
@@ -257,13 +259,13 @@ func (p *FilePreview) CreateHtml(locale translation.Locale) *html.Node {
})
header.AppendChild(psubtitle)
- preview_node := &html.Node{
+ node := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
}
- preview_node.AppendChild(header)
- preview_node.AppendChild(twrapper)
+ node.AppendChild(header)
+ node.AppendChild(twrapper)
- return preview_node
+ return node
}
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 2e38c05f58..9a04e02fb8 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -1075,7 +1075,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
return
}
- preview_node := preview.CreateHtml(locale)
+ previewNode := preview.CreateHTML(locale)
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
before := node.Data[:preview.start]
@@ -1086,7 +1086,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
Type: html.RawNode,
Data: "
",
}, nextSibling)
- node.Parent.InsertBefore(preview_node, nextSibling)
+ node.Parent.InsertBefore(previewNode, nextSibling)
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "" + after,
From 10bca456a9140519e95559aa7bac2221e1156c5b Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 18 Mar 2024 06:19:27 +0100
Subject: [PATCH 009/426] Remove `rel` and `id` attributes that only add the
linenumber to elements
---
modules/markup/file_preview.go | 3 ---
modules/markup/sanitizer.go | 1 -
2 files changed, 4 deletions(-)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index be788aae4b..167bbd1997 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -149,7 +149,6 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
Type: html.ElementNode,
Data: atom.Td.String(),
Attr: []html.Attribute{
- {Key: "id", Val: "L" + lineNum},
{Key: "class", Val: "lines-num"},
},
}
@@ -157,7 +156,6 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
Type: html.ElementNode,
Data: atom.Span.String(),
Attr: []html.Attribute{
- {Key: "id", Val: "L" + lineNum},
{Key: "data-line-number", Val: lineNum},
},
}
@@ -200,7 +198,6 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
Type: html.ElementNode,
Data: atom.Td.String(),
Attr: []html.Attribute{
- {Key: "rel", Val: "L" + lineNum},
{Key: "class", Val: "lines-code chroma"},
},
}
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index 73e17060a7..c37027b843 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -128,7 +128,6 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
- policy.AllowAttrs("rel").Matching(regexp.MustCompile("^L[0-9]+$")).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
From db6f6281fcf568ae8e35330a4a93c9be1cb46efd Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 18 Mar 2024 06:21:35 +0100
Subject: [PATCH 010/426] Add copyright & license header to file_preview.go
---
modules/markup/file_preview.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 167bbd1997..377809529d 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -1,3 +1,6 @@
+// Copyright The Forgejo Authors.
+// SPDX-License-Identifier: MIT
+
package markup
import (
From ed8e8a792e75b930074cd3cf1bab580a09ff8485 Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 18 Mar 2024 06:23:12 +0100
Subject: [PATCH 011/426] Run make fmt
---
modules/markup/file_preview.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 377809529d..2702cb7ce3 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
+
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
From d6428f92ce7ce67d127cbd5bb4977aa92abf071c Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 25 Mar 2024 14:33:30 +0100
Subject: [PATCH 012/426] Fix typo in language files
---
options/locale/locale_en-US.ini | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index ebc8db24ca..efaf8b72c9 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3709,5 +3709,5 @@ symbolic_link = Symbolic link
submodule = Submodule
[markup]
-filepreview.line = Line %[1]d in %[3]s
+filepreview.line = Line %[1]d in %[2]s
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
From 069d87b80f909e91626249afbb240a1df339a8fd Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 25 Mar 2024 14:33:54 +0100
Subject: [PATCH 013/426] Remove unneeded case for a trailing dot
---
modules/markup/file_preview.go | 8 --------
1 file changed, 8 deletions(-)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 2702cb7ce3..3e76dcb8a4 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -62,14 +62,6 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.start = m[0]
preview.end = m[1]
- // If url ends in '.', it's very likely that it is not part of the
- // actual url but used to finish a sentence.
- if strings.HasSuffix(preview.urlFull, ".") {
- preview.end--
- preview.urlFull = preview.urlFull[:len(preview.urlFull)-1]
- hash = hash[:len(hash)-1]
- }
-
projPathSegments := strings.Split(projPath, "/")
fileContent, err := DefaultProcessorHelper.GetRepoFileContent(
ctx.Ctx,
From 2b6546adc954d450a9c6befccd407ce2ca1636a0 Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Mon, 25 Mar 2024 16:05:01 +0100
Subject: [PATCH 014/426] Add setting to restrict count of lines being
displayed & only highlight those lines
---
custom/conf/app.example.ini | 2 +
modules/markup/file_preview.go | 103 ++++++++++++++++++++++++-----
modules/markup/html.go | 2 +-
modules/markup/renderer.go | 4 +-
modules/markup/sanitizer.go | 1 +
modules/setting/markup.go | 2 +
options/locale/locale_en-US.ini | 1 +
services/markup/processorhelper.go | 24 ++++---
web_src/css/markup/filepreview.css | 6 ++
9 files changed, 117 insertions(+), 28 deletions(-)
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index b3896bc31c..91f86da5f8 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -2338,6 +2338,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
+;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
+;FILEPREVIEW_MAX_LINES = 50
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 3e76dcb8a4..32683c317c 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -4,6 +4,7 @@
package markup
import (
+ "bufio"
"bytes"
"html/template"
"regexp"
@@ -12,6 +13,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
@@ -31,9 +33,15 @@ type FilePreview struct {
filePath string
start int
end int
+ isTruncated bool
}
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
+ if (setting.FilePreviewMaxLines == 0) {
+ // Feature is disabled
+ return nil
+ }
+
preview := &FilePreview{}
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
@@ -63,18 +71,20 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.end = m[1]
projPathSegments := strings.Split(projPath, "/")
- fileContent, err := DefaultProcessorHelper.GetRepoFileContent(
+ var language string
+ fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
ctx.Ctx,
projPathSegments[len(projPathSegments)-2],
projPathSegments[len(projPathSegments)-1],
commitSha, preview.filePath,
+ &language,
)
if err != nil {
return nil
}
lineSpecs := strings.Split(hash, "-")
- lineCount := len(fileContent)
+ // lineCount := len(fileContent)
commitLinkBuffer := new(bytes.Buffer)
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
@@ -82,28 +92,31 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
log.Error("failed to render commitLink: %v", err)
}
- if len(lineSpecs) == 1 {
- line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
- if line < 1 || line > lineCount {
- return nil
- }
+ var startLine, endLine int
- preview.fileContent = fileContent[line-1 : line]
+ if len(lineSpecs) == 1 {
+ startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ endLine = startLine
+ // if line < 1 || line > lineCount {
+ // return nil
+ // }
+
+ // preview.fileContent = fileContent[line-1 : line]
preview.subTitle = locale.Tr(
- "markup.filepreview.line", line,
+ "markup.filepreview.line", startLine,
template.HTML(commitLinkBuffer.String()),
)
- preview.lineOffset = line - 1
+ preview.lineOffset = startLine - 1
} else {
- startLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
- endLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
+ startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
- if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
- return nil
- }
+ // if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
+ // return nil
+ // }
- preview.fileContent = fileContent[startLine-1 : endLine]
+ // preview.fileContent = fileContent[startLine-1 : endLine]
preview.subTitle = locale.Tr(
"markup.filepreview.lines", startLine, endLine,
template.HTML(commitLinkBuffer.String()),
@@ -112,6 +125,50 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.lineOffset = startLine - 1
}
+ lineCount := endLine - (startLine-1)
+ if startLine < 1 || endLine < 1 || lineCount < 1 {
+ return nil
+ }
+
+ if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
+ preview.isTruncated = true
+ lineCount = setting.FilePreviewMaxLines
+ }
+
+ dataRc, err := fileBlob.DataAsync()
+ if err != nil {
+ return nil
+ }
+ defer dataRc.Close()
+
+ reader := bufio.NewReader(dataRc)
+
+ // skip all lines until we find our startLine
+ for i := 1; i < startLine; i++ {
+ _, err := reader.ReadBytes('\n')
+ if err != nil {
+ return nil
+ }
+ }
+
+ // capture the lines we're interested in
+ lineBuffer := new(bytes.Buffer)
+ for i := 0; i < lineCount; i++ {
+ buf, err := reader.ReadBytes('\n')
+ if err != nil {
+ break;
+ }
+ lineBuffer.Write(buf)
+ }
+
+ // highlight the file...
+ fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
+ if err != nil {
+ log.Error("highlight.File failed, fallback to plain text: %v", err)
+ fileContent = highlight.PlainText(lineBuffer.Bytes())
+ }
+ preview.fileContent = fileContent
+
return preview
}
@@ -258,6 +315,20 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
}
node.AppendChild(header)
+
+ if (p.isTruncated) {
+ warning := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
+ }
+ warning.AppendChild(&html.Node{
+ Type: html.TextNode,
+ Data: locale.TrString("markup.filepreview.truncated"),
+ })
+ node.AppendChild(warning)
+ }
+
node.AppendChild(twrapper)
return node
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 9a04e02fb8..4c74a81ba7 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -1059,7 +1059,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil {
return
}
- if DefaultProcessorHelper.GetRepoFileContent == nil {
+ if DefaultProcessorHelper.GetRepoFileBlob == nil {
return
}
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index b6d742e5ce..b08c9eb230 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -8,7 +8,7 @@ import (
"context"
"errors"
"fmt"
- "html/template"
+ // "html/template"
"io"
"net/url"
"path/filepath"
@@ -32,7 +32,7 @@ const (
type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
- GetRepoFileContent func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error)
+ GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
}
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index c37027b843..1048f0e374 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -135,6 +135,7 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
policy.AllowAttrs("data-tooltip-content").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
// Allow generally safe attributes
generalSafeAttrs := []string{
diff --git a/modules/setting/markup.go b/modules/setting/markup.go
index 6c2246342b..e893c1c2f1 100644
--- a/modules/setting/markup.go
+++ b/modules/setting/markup.go
@@ -15,6 +15,7 @@ var (
ExternalMarkupRenderers []*MarkupRenderer
ExternalSanitizerRules []MarkupSanitizerRule
MermaidMaxSourceCharacters int
+ FilePreviewMaxLines int
)
const (
@@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
+ FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index efaf8b72c9..8533cf0650 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3711,3 +3711,4 @@ submodule = Submodule
[markup]
filepreview.line = Line %[1]d in %[2]s
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
+filepreview.truncated = Preview has been truncated
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index df96f25ce9..98a7824a6e 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -6,15 +6,17 @@ package markup
import (
"context"
"fmt"
- "html/template"
- "io"
+
+ // "html/template"
+ // "io"
"code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/modules/highlight"
+ // "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context"
@@ -39,7 +41,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
// when using gitea context (web context), use user's visibility and user's permission to check
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
},
- GetRepoFileContent: func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error) {
+ GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
if err != nil {
return nil, err
@@ -70,9 +72,11 @@ func ProcessorHelper() *markup.ProcessorHelper {
return nil, err
}
- language, err := file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
- if err != nil {
- log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
+ if language != nil {
+ *language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
+ if err != nil {
+ log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
+ }
}
blob, err := commit.GetBlobByPath(filePath)
@@ -80,7 +84,9 @@ func ProcessorHelper() *markup.ProcessorHelper {
return nil, err
}
- dataRc, err := blob.DataAsync()
+ return blob, nil
+
+ /*dataRc, err := blob.DataAsync()
if err != nil {
return nil, err
}
@@ -97,7 +103,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
fileContent = highlight.PlainText(buf)
}
- return fileContent, nil
+ return fileContent, nil*/
},
}
}
diff --git a/web_src/css/markup/filepreview.css b/web_src/css/markup/filepreview.css
index 69360e2a70..d2ec16ea8b 100644
--- a/web_src/css/markup/filepreview.css
+++ b/web_src/css/markup/filepreview.css
@@ -25,6 +25,12 @@
background: var(--color-box-header);
}
+.markup .file-preview-box .warning {
+ border-radius: 0;
+ margin: 0;
+ padding: .5rem .5rem .5rem 1rem;
+}
+
.markup .file-preview-box .header > a {
display: block;
}
From 4f43b7338b956e7850927ba452492fe1b4f33238 Mon Sep 17 00:00:00 2001
From: Leo Heitmann Ruiz
Date: Tue, 26 Mar 2024 22:01:53 +0100
Subject: [PATCH 015/426] "Plaintext" => "Text"
---
modules/highlight/highlight.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go
index d7ab3f7afd..8fa2de6daf 100644
--- a/modules/highlight/highlight.go
+++ b/modules/highlight/highlight.go
@@ -217,7 +217,7 @@ func PlainText(code []byte) []template.HTML {
func formatLexerName(name string) string {
if name == "fallback" {
- return "Plaintext"
+ return "Text"
}
return util.ToTitleCaseNoLower(name)
From c340e020786b1451c1693853fc3c42102729a10a Mon Sep 17 00:00:00 2001
From: Leo Heitmann Ruiz
Date: Tue, 26 Mar 2024 22:25:34 +0100
Subject: [PATCH 016/426] "Plaintext" => "Text"
---
modules/highlight/highlight_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go
index 659688bd0f..dd15b97847 100644
--- a/modules/highlight/highlight_test.go
+++ b/modules/highlight/highlight_test.go
@@ -58,7 +58,7 @@ func TestFile(t *testing.T) {
name: "tags.txt",
code: "<>",
want: lines("<>"),
- lexerName: "Plaintext",
+ lexerName: "Text",
},
{
name: "tags.py",
From 4c7cb0a5d20e8973b03e35d91119cf917eed125e Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Wed, 27 Mar 2024 18:25:37 +0100
Subject: [PATCH 017/426] Close git.Repository when GetRepoFileBlob returns
---
services/markup/processorhelper.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index 98a7824a6e..7466e962d2 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -66,6 +66,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
if err != nil {
return nil, err
}
+ defer gitRepo.Close()
commit, err := gitRepo.GetCommit(commitSha)
if err != nil {
From 7e0014dd1391e123d95f2537c3b2165fef7122ef Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Wed, 27 Mar 2024 18:36:12 +0100
Subject: [PATCH 018/426] Fix formating & remove commented out code
---
modules/markup/file_preview.go | 20 ++++----------------
modules/markup/renderer.go | 2 +-
services/markup/processorhelper.go | 19 -------------------
3 files changed, 5 insertions(+), 36 deletions(-)
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
index 32683c317c..95c94e0c14 100644
--- a/modules/markup/file_preview.go
+++ b/modules/markup/file_preview.go
@@ -37,7 +37,7 @@ type FilePreview struct {
}
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
- if (setting.FilePreviewMaxLines == 0) {
+ if setting.FilePreviewMaxLines == 0 {
// Feature is disabled
return nil
}
@@ -84,7 +84,6 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
}
lineSpecs := strings.Split(hash, "-")
- // lineCount := len(fileContent)
commitLinkBuffer := new(bytes.Buffer)
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
@@ -97,11 +96,6 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
if len(lineSpecs) == 1 {
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine = startLine
- // if line < 1 || line > lineCount {
- // return nil
- // }
-
- // preview.fileContent = fileContent[line-1 : line]
preview.subTitle = locale.Tr(
"markup.filepreview.line", startLine,
template.HTML(commitLinkBuffer.String()),
@@ -111,12 +105,6 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
} else {
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
-
- // if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
- // return nil
- // }
-
- // preview.fileContent = fileContent[startLine-1 : endLine]
preview.subTitle = locale.Tr(
"markup.filepreview.lines", startLine, endLine,
template.HTML(commitLinkBuffer.String()),
@@ -125,7 +113,7 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.lineOffset = startLine - 1
}
- lineCount := endLine - (startLine-1)
+ lineCount := endLine - (startLine - 1)
if startLine < 1 || endLine < 1 || lineCount < 1 {
return nil
}
@@ -156,7 +144,7 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
for i := 0; i < lineCount; i++ {
buf, err := reader.ReadBytes('\n')
if err != nil {
- break;
+ break
}
lineBuffer.Write(buf)
}
@@ -316,7 +304,7 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
}
node.AppendChild(header)
- if (p.isTruncated) {
+ if p.isTruncated {
warning := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index b08c9eb230..163cd5d688 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -32,7 +32,7 @@ const (
type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
- GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
+ GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
}
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index 7466e962d2..ac751d0e62 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -86,25 +86,6 @@ func ProcessorHelper() *markup.ProcessorHelper {
}
return blob, nil
-
- /*dataRc, err := blob.DataAsync()
- if err != nil {
- return nil, err
- }
- defer dataRc.Close()
-
- buf, err := io.ReadAll(dataRc)
- if err != nil {
- log.Error("failed to completly read blob for %-v:%s. Error: %v", repo, filePath, err)
- }
-
- fileContent, _, err := highlight.File(blob.Name(), language, buf)
- if err != nil {
- log.Error("highlight.File failed, fallback to plain text: %v", err)
- fileContent = highlight.PlainText(buf)
- }
-
- return fileContent, nil*/
},
}
}
From 5785ae72c75ea66cdadfc260d59084e0bb0bf0bb Mon Sep 17 00:00:00 2001
From: oliverpool
Date: Wed, 27 Mar 2024 22:02:51 +0100
Subject: [PATCH 019/426] [TESTS] prevent overriding testlogger when calling
mainApp
---
modules/testlogger/testlogger.go | 5 +++
tests/integration/cmd_forgejo_actions_test.go | 32 +++++++++-------
tests/integration/cmd_forgejo_test.go | 36 ------------------
tests/integration/cmd_keys_test.go | 27 +++++++------
tests/integration/integration_test.go | 38 +++++++++++++++++++
5 files changed, 75 insertions(+), 63 deletions(-)
delete mode 100644 tests/integration/cmd_forgejo_test.go
diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go
index c5ca906220..43fbaf0afd 100644
--- a/modules/testlogger/testlogger.go
+++ b/modules/testlogger/testlogger.go
@@ -128,6 +128,11 @@ func (w *testLoggerWriterCloser) recordError(msg string) {
err = w.errs[len(w.errs)-1]
}
+ if len(w.t) > 0 {
+ // format error message to easily add it to the ignore list
+ msg = fmt.Sprintf("// %s\n\t`%s`,", w.t[len(w.t)-1].Name(), msg)
+ }
+
err = errors.Join(err, errors.New(msg))
if len(w.errs) > 0 {
diff --git a/tests/integration/cmd_forgejo_actions_test.go b/tests/integration/cmd_forgejo_actions_test.go
index 44211007f5..e45526ac7a 100644
--- a/tests/integration/cmd_forgejo_actions_test.go
+++ b/tests/integration/cmd_forgejo_actions_test.go
@@ -4,8 +4,11 @@ package integration
import (
gocontext "context"
+ "errors"
+ "io"
"net/url"
"os"
+ "os/exec"
"strings"
"testing"
@@ -19,16 +22,18 @@ import (
func Test_CmdForgejo_Actions(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
- token, err := cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "generate-runner-token"})
+ token, err := runMainApp("forgejo-cli", "actions", "generate-runner-token")
assert.NoError(t, err)
assert.EqualValues(t, 40, len(token))
- secret, err := cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "generate-secret"})
+ secret, err := runMainApp("forgejo-cli", "actions", "generate-secret")
assert.NoError(t, err)
assert.EqualValues(t, 40, len(secret))
- _, err = cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "register"})
- assert.ErrorContains(t, err, "at least one of the --secret")
+ _, err = runMainApp("forgejo-cli", "actions", "register")
+ var exitErr *exec.ExitError
+ assert.True(t, errors.As(err, &exitErr))
+ assert.Contains(t, string(exitErr.Stderr), "at least one of the --secret")
for _, testCase := range []struct {
testName string
@@ -62,10 +67,12 @@ func Test_CmdForgejo_Actions(t *testing.T) {
},
} {
t.Run(testCase.testName, func(t *testing.T) {
- cmd := []string{"forgejo", "forgejo-cli", "actions", "register", "--secret", testCase.secret, "--scope", testCase.scope}
- output, err := cmdForgejoCaptureOutput(t, cmd)
- assert.ErrorContains(t, err, testCase.errorMessage)
+ output, err := runMainApp("forgejo-cli", "actions", "register", "--secret", testCase.secret, "--scope", testCase.scope)
assert.EqualValues(t, "", output)
+
+ var exitErr *exec.ExitError
+ assert.True(t, errors.As(err, &exitErr))
+ assert.Contains(t, string(exitErr.Stderr), testCase.errorMessage)
})
}
@@ -75,7 +82,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
for _, testCase := range []struct {
testName string
secretOption func() string
- stdin []string
+ stdin io.Reader
}{
{
testName: "secret from argument",
@@ -88,7 +95,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
secretOption: func() string {
return "--secret-stdin"
},
- stdin: []string{secret},
+ stdin: strings.NewReader(secret),
},
{
testName: "secret from file",
@@ -100,8 +107,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
},
} {
t.Run(testCase.testName, func(t *testing.T) {
- cmd := []string{"forgejo", "forgejo-cli", "actions", "register", testCase.secretOption(), "--scope=org26"}
- uuid, err := cmdForgejoCaptureOutput(t, cmd, testCase.stdin...)
+ uuid, err := runMainAppWithStdin(testCase.stdin, "forgejo-cli", "actions", "register", testCase.secretOption(), "--scope=org26")
assert.NoError(t, err)
assert.EqualValues(t, expecteduuid, uuid)
})
@@ -161,7 +167,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
} {
t.Run(testCase.testName, func(t *testing.T) {
cmd := []string{
- "forgejo", "forgejo-cli", "actions", "register",
+ "actions", "register",
"--secret", testCase.secret, "--scope", testCase.scope,
}
if testCase.name != "" {
@@ -177,7 +183,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
// Run twice to verify it is idempotent
//
for i := 0; i < 2; i++ {
- uuid, err := cmdForgejoCaptureOutput(t, cmd)
+ uuid, err := runMainApp("forgejo-cli", cmd...)
assert.NoError(t, err)
if assert.EqualValues(t, testCase.uuid, uuid) {
ownerName, repoName, found := strings.Cut(testCase.scope, "/")
diff --git a/tests/integration/cmd_forgejo_test.go b/tests/integration/cmd_forgejo_test.go
deleted file mode 100644
index 76f5a6fc08..0000000000
--- a/tests/integration/cmd_forgejo_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-package integration
-
-import (
- "bytes"
- "context"
- "strings"
- "testing"
-
- "code.gitea.io/gitea/cmd/forgejo"
-
- "github.com/urfave/cli/v2"
-)
-
-func cmdForgejoCaptureOutput(t *testing.T, args []string, stdin ...string) (string, error) {
- buf := new(bytes.Buffer)
-
- app := cli.NewApp()
- app.Writer = buf
- app.ErrWriter = buf
- ctx := context.Background()
- ctx = forgejo.ContextSetNoInit(ctx, true)
- ctx = forgejo.ContextSetNoExit(ctx, true)
- ctx = forgejo.ContextSetStdout(ctx, buf)
- ctx = forgejo.ContextSetStderr(ctx, buf)
- if len(stdin) > 0 {
- ctx = forgejo.ContextSetStdin(ctx, strings.NewReader(strings.Join(stdin, "")))
- }
- app.Commands = []*cli.Command{
- forgejo.CmdForgejo(ctx),
- }
- err := app.Run(args)
-
- return buf.String(), err
-}
diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go
index 61f11c58b0..a3220c13ce 100644
--- a/tests/integration/cmd_keys_test.go
+++ b/tests/integration/cmd_keys_test.go
@@ -4,16 +4,15 @@
package integration
import (
- "bytes"
+ "errors"
"net/url"
+ "os/exec"
"testing"
- "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
)
func Test_CmdKeys(t *testing.T) {
@@ -24,30 +23,30 @@ func Test_CmdKeys(t *testing.T) {
wantErr bool
expectedOutput string
}{
- {"test_empty_1", []string{"keys", "--username=git", "--type=test", "--content=test"}, true, ""},
- {"test_empty_2", []string{"keys", "-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""},
+ {"test_empty_1", []string{"--username=git", "--type=test", "--content=test"}, true, ""},
+ {"test_empty_2", []string{"-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""},
{
"with_key",
- []string{"keys", "-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
+ []string{"-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
false,
"# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n",
},
- {"invalid", []string{"keys", "--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"},
+ {"invalid", []string{"--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- out := new(bytes.Buffer)
- app := cli.NewApp()
- app.Writer = out
- app.Commands = []*cli.Command{cmd.CmdKeys}
- cmd.CmdKeys.HideHelp = true
- err := app.Run(append([]string{"prog"}, tt.args...))
+ out, err := runMainApp("keys", tt.args...)
+
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
+ t.Log(string(exitErr.Stderr))
+ }
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
- assert.Equal(t, tt.expectedOutput, out.String())
+ assert.Equal(t, tt.expectedOutput, out)
})
}
})
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index e8f28105c1..b087281ff4 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -17,6 +17,7 @@ import (
"net/http/httptest"
"net/url"
"os"
+ "os/exec"
"path/filepath"
"strconv"
"strings"
@@ -24,6 +25,7 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
@@ -93,7 +95,43 @@ func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
}
}
+// runMainApp runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainApp(subcommand string, args ...string) (string, error) {
+ return runMainAppWithStdin(nil, subcommand, args...)
+}
+
+// runMainAppWithStdin runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainAppWithStdin(stdin io.Reader, subcommand string, args ...string) (string, error) {
+ // running the main app directly will very likely mess with the testing setup (logger & co.)
+ // hence we run it as a subprocess and capture its output
+ args = append([]string{subcommand}, args...)
+ cmd := exec.Command(os.Args[0], args...)
+ cmd.Env = append(os.Environ(),
+ "GITEA_TEST_CLI=true",
+ "GITEA_CONF="+setting.CustomConf,
+ "GITEA_WORK_DIR="+setting.AppWorkPath)
+ cmd.Stdin = stdin
+ out, err := cmd.Output()
+ return string(out), err
+}
+
func TestMain(m *testing.M) {
+ // GITEA_TEST_CLI is set by runMainAppWithStdin
+ // inspired by https://abhinavg.net/2022/05/15/hijack-testmain/
+ if testCLI := os.Getenv("GITEA_TEST_CLI"); testCLI == "true" {
+ app := cmd.NewMainApp("test-version", "integration-test")
+ args := append([]string{
+ "executable-name", // unused, but expected at position 1
+ "--config", os.Getenv("GITEA_CONF"),
+ },
+ os.Args[1:]..., // skip the executable name
+ )
+ if err := cmd.RunMainApp(app, args...); err != nil {
+ panic(err) // should never happen since RunMainApp exits on error
+ }
+ return
+ }
+
defer log.GetManager().Close()
managerCtx, cancel := context.WithCancel(context.Background())
From 16a8658878a2656cb131453b728b65a89271f11f Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Thu, 28 Mar 2024 04:20:13 +0100
Subject: [PATCH 020/426] Update test
---
modules/markup/html_test.go | 40 ++++++++++++------
.../markup/tests/repo/repo1_filepreview/HEAD | 1 +
.../tests/repo/repo1_filepreview/config | 6 +++
.../tests/repo/repo1_filepreview/description | 1 +
.../tests/repo/repo1_filepreview/info/exclude | 6 +++
.../19/0d9492934af498c3f669d6a2431dc5459e5b20 | Bin 0 -> 120 bytes
.../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes
.../83/57a737d04385bb7f2ab59ff184be94756e7972 | Bin 0 -> 44 bytes
.../84/22d40f12717e1ebd5cef2449f6c09d1f775969 | Bin 0 -> 23 bytes
.../d4/490327def9658be036d6a52c4417d84e74dd4c | Bin 0 -> 46 bytes
.../ee/2b1253d9cf407796e2e724926cbe3a974b214d | 1 +
.../repo/repo1_filepreview/refs/heads/master | 1 +
12 files changed, 43 insertions(+), 13 deletions(-)
create mode 100644 modules/markup/tests/repo/repo1_filepreview/HEAD
create mode 100644 modules/markup/tests/repo/repo1_filepreview/config
create mode 100644 modules/markup/tests/repo/repo1_filepreview/description
create mode 100644 modules/markup/tests/repo/repo1_filepreview/info/exclude
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/83/57a737d04385bb7f2ab59ff184be94756e7972
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/84/22d40f12717e1ebd5cef2449f6c09d1f775969
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/d4/490327def9658be036d6a52c4417d84e74dd4c
create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d
create mode 100644 modules/markup/tests/repo/repo1_filepreview/refs/heads/master
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index c43f006266..3583894bae 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -5,7 +5,6 @@ package markup_test
import (
"context"
- "html/template"
"io"
"os"
"strings"
@@ -14,14 +13,15 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var localMetas = map[string]string{
@@ -677,16 +677,30 @@ func TestIssue18471(t *testing.T) {
}
func TestRender_FilePreview(t *testing.T) {
+ setting.StaticRootPath = "../../"
+ setting.Names = []string{"english"}
+ setting.Langs = []string{"en-US"}
+ translation.InitLocales(context.Background())
+
setting.AppURL = markup.TestAppURL
markup.Init(&markup.ProcessorHelper{
- GetRepoFileContent: func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error) {
- buf := []byte("A\nB\nC\nD\n")
- return highlight.PlainText(buf), nil
+ GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
+ gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
+ require.NoError(t, err)
+ defer gitRepo.Close()
+
+ commit, err := gitRepo.GetCommit("HEAD")
+ require.NoError(t, err)
+
+ blob, err := commit.GetBlobByPath("path/to/file.go")
+ require.NoError(t, err)
+
+ return blob, nil
},
})
- sha := "b6dd6210eaebc915fd5be5579c58cce4da2e2579"
- commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L1-L2"
+ sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
+ commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{
@@ -703,21 +717,21 @@ func TestRender_FilePreview(t *testing.T) {
`
`+
``+
``+
`
`+
`
`+
``+
``+
- ` `+
- `A`+"\n"+`
`+
+ ` `+
+ `B `+"\n"+`
`+
` `+
``+
- ` `+
- `B`+"\n"+`
`+
+ ` `+
+ `C `+"\n"+`
`+
` `+
` `+
`
`+
diff --git a/modules/markup/tests/repo/repo1_filepreview/HEAD b/modules/markup/tests/repo/repo1_filepreview/HEAD
new file mode 100644
index 0000000000..cb089cd89a
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/modules/markup/tests/repo/repo1_filepreview/config b/modules/markup/tests/repo/repo1_filepreview/config
new file mode 100644
index 0000000000..42cc799c8d
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+[remote "origin"]
+ url = /home/mai/projects/codeark/forgejo/forgejo/modules/markup/tests/repo/repo1_filepreview/../../__test_repo
diff --git a/modules/markup/tests/repo/repo1_filepreview/description b/modules/markup/tests/repo/repo1_filepreview/description
new file mode 100644
index 0000000000..498b267a8c
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/modules/markup/tests/repo/repo1_filepreview/info/exclude b/modules/markup/tests/repo/repo1_filepreview/info/exclude
new file mode 100644
index 0000000000..a5196d1be8
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20 b/modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20
new file mode 100644
index 0000000000000000000000000000000000000000..161d0bafc6731f5fe0b3b3c29ffe5463b056e840
GIT binary patch
literal 120
zcmV-;0Ehp00hNtO4#F@D06FIsz9S(!b&^)95RZTdgxH8kluC)q`&oX#X-+d!)@7*%
z6w=O`DhTt0gHNKjDTeW?I7Ep#_`*y{M%Kh4TwLDlzBagYZ3Of7#i>`*Lw&yTqskE|
a(I?AD9`;CxuKZr6|5@&=-P{|T!ZCX0g*&(a
literal 0
HcmV?d00001
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 0000000000000000000000000000000000000000..adf64119a33d7621aeeaa505d30adb58afaa5559
GIT binary patch
literal 15
Wcmb
)%hIiUR!8gx4luvu~TxC+uKC9{8ioO8p9WH2!R0)>Lak_?9C@a5(goLhI-Yi*tXv1Q+s(!9zd00ff{
EldH%Sg8%>k
literal 0
HcmV?d00001
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d b/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d
new file mode 100644
index 0000000000..e13ca647db
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d
@@ -0,0 +1 @@
+x+)JMU06e040031QHIKghQ/TX'7潊s#3
\ No newline at end of file
diff --git a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master
new file mode 100644
index 0000000000..49c348b41c
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master
@@ -0,0 +1 @@
+190d9492934af498c3f669d6a2431dc5459e5b20
From 6e98bacbbd3c089b2ccfa725c58184f4dfe5e7fe Mon Sep 17 00:00:00 2001
From: Mai-Lapyst
Date: Thu, 28 Mar 2024 05:42:25 +0100
Subject: [PATCH 021/426] Format code
---
modules/markup/html_test.go | 2 +-
modules/markup/renderer.go | 1 -
services/markup/processorhelper.go | 4 ----
3 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 3583894bae..1ecf519f46 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -688,7 +688,7 @@ func TestRender_FilePreview(t *testing.T) {
gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
require.NoError(t, err)
defer gitRepo.Close()
-
+
commit, err := gitRepo.GetCommit("HEAD")
require.NoError(t, err)
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 163cd5d688..6781d2e552 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -8,7 +8,6 @@ import (
"context"
"errors"
"fmt"
- // "html/template"
"io"
"net/url"
"path/filepath"
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index ac751d0e62..40bf1d65da 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -7,16 +7,12 @@ import (
"context"
"fmt"
- // "html/template"
- // "io"
-
"code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
- // "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context"
From cbd067e759b1de054da17c18f92fc934c10eacf8 Mon Sep 17 00:00:00 2001
From: 0ko <0ko@noreply.codeberg.org>
Date: Thu, 28 Mar 2024 19:48:56 +0500
Subject: [PATCH 022/426] Fix accessibility and translatability of repo explore
counters
Progression of: https://codeberg.org/forgejo/forgejo/commit/9e69ef9c51cded6321e4cca39d33a64e9801d910
Regression of: https://codeberg.org/forgejo/forgejo/commit/65e190ae8bd6c72d8701a58d67b256c87b92c189#diff-8d94e33cfe70fa6443d059b9c34e3f8064514816
---
templates/explore/repo_list.tmpl | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index 9e5042d650..99c9bc1736 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -38,15 +38,15 @@
{{end}}
{{if not $.DisableStars}}
-
- {{svg "octicon-star" 16}}
- {{CountFmt .NumStars}}
+
+ {{svg "octicon-star" 16}}
+ {{if ge .NumStars 1000}}data-tooltip-content="{{.NumStars}}"{{end}}{{CountFmt .NumStars}}
{{end}}
{{if not $.DisableForks}}
-
- {{svg "octicon-git-branch" 16}}
- {{CountFmt .NumForks}}
+
+ {{svg "octicon-git-branch" 16}}
+ {{if ge .NumForks 1000}}data-tooltip-content="{{.NumForks}}"{{end}}{{CountFmt .NumForks}}
{{end}}
From 869795a530f6a5a4e28687c003300d39fbaee6e2 Mon Sep 17 00:00:00 2001
From: Earl Warren
Date: Thu, 28 Mar 2024 15:45:51 +0100
Subject: [PATCH 023/426] [RELEASE] GITEA_VERSION is a fallback for
FORGEJO_VERSION
Existing Forgejo packages may rely on setting GITEA_VERSION to specify
the version to build if:
* they do not build from the git repository with the proper tag
* they build from a source tarbal that does not have a VERSION file
With 7.0 the logic of setting the version was modified in the
`[RELEASE] Gitea version is for interoperability only` commit and
ignores this variable which creates an unecessary breaking change.
If GITEA_VERSION is set, the versions will be set on 7.0 exactly as
they would have with version before and included 1.21.
* If GITEA_VERSION is not set, all versions are the same
* If GITEA_VERSION is set, there is a distinction between the version
set in the binary are returned by the Gitea API and the
version returned by the Forgejo API which includes metadata.
Before:
$ make GITEA_VERSION=7.0.0 show-version-full
7.0.0-dev-1809-cd6fa771ab+gitea-1.22.0
$ make GITEA_VERSION=7.0.0 show-version-api
7.0.0-dev-1809-cd6fa771ab+gitea-1.22.0
After:
$ make GITEA_VERSION=7.0.0 show-version-full
7.0.0
$ make GITEA_VERSION=7.0.0 show-version-api
7.0.0+gitea-1.22.0
---
Makefile | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/Makefile b/Makefile
index b1c4864c7a..16d7edb2dd 100644
--- a/Makefile
+++ b/Makefile
@@ -88,8 +88,13 @@ STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),)
FORGEJO_VERSION ?= $(STORED_VERSION)
else
- # drop the "g" prefix prepended by git describe to the commit hash
- FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
+ ifneq ($(GITEA_VERSION),)
+ FORGEJO_VERSION ?= $(GITEA_VERSION)
+ FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
+ else
+ # drop the "g" prefix prepended by git describe to the commit hash
+ FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
+ endif
endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
@@ -106,7 +111,12 @@ show-version-minor:
RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION}
-LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
+FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
+
+show-version-api:
+ @echo ${FORGEJO_VERSION_API}
+
+LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
From 6647e4d53f04f8d5585090c7f0cf266713542b4b Mon Sep 17 00:00:00 2001
From: Earl Warren
Date: Thu, 28 Mar 2024 17:29:32 +0100
Subject: [PATCH 024/426] [I18N] make merge-locales &
build/merge-forgejo-locales.go are noop
Instead of failing or do things that could be damaging, this script
prints a deprecation notice. It fixes the unnecessary breaking
change introduced in `[I18n] tooling and process`.
https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/forgejo/default.nix#L80
$ make merge-locales
NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY
$ go run build/merge-forgejo-locales.go
NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY
Also remove the build/crowdin-to-weblate.sh script that was never
needed.
---
Makefile | 4 ++
build/crowdin-to-weblate.sh | 27 ---------
build/merge-forgejo-locales.go | 100 +--------------------------------
3 files changed, 7 insertions(+), 124 deletions(-)
delete mode 100755 build/crowdin-to-weblate.sh
diff --git a/Makefile b/Makefile
index b1c4864c7a..70a7f906eb 100644
--- a/Makefile
+++ b/Makefile
@@ -814,6 +814,10 @@ generate-go: $(TAGS_PREREQ)
@echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
+.PHONY: merge-locales
+merge-locales:
+ @echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
+
.PHONY: security-check
security-check:
go run $(GOVULNCHECK_PACKAGE) ./...
diff --git a/build/crowdin-to-weblate.sh b/build/crowdin-to-weblate.sh
deleted file mode 100755
index 877b5d9e07..0000000000
--- a/build/crowdin-to-weblate.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-# Copyright 2024 The Forgejo Authors
-# SPDX-License-Identifier: MIT
-
-D=/tmp/crowdin-to-weblate
-mkdir -p $D
-
-function checkout() {
- if test -d $D/gitea ; then
- git -C $D/gitea reset --hard
- return
- fi
-
- git clone --depth 1 https://github.com/go-gitea/gitea $D/gitea
-}
-
-function replace() {
- go run build/merge-forgejo-locales.go $D/gitea/options/locale
- cp -a $D/gitea/options/locale/* options/locale
-}
-
-function run() {
- checkout
- replace
-}
-
-"$@"
diff --git a/build/merge-forgejo-locales.go b/build/merge-forgejo-locales.go
index eebe12578b..05c8d9b5e9 100644
--- a/build/merge-forgejo-locales.go
+++ b/build/merge-forgejo-locales.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@@ -7,103 +7,9 @@
package main
import (
- "bufio"
- "log"
- "os"
- "regexp"
- "strings"
+ "fmt"
)
-const (
- trimPrefix = "gitea_"
- sourceFolder = "options/locales/"
-)
-
-// returns list of locales, still containing the file extension!
-func generate_locale_list() []string {
- localeFiles, _ := os.ReadDir(sourceFolder)
- locales := []string{}
- for _, localeFile := range localeFiles {
- if !localeFile.IsDir() && strings.HasPrefix(localeFile.Name(), trimPrefix) {
- locales = append(locales, strings.TrimPrefix(localeFile.Name(), trimPrefix))
- }
- }
- return locales
-}
-
-// replace all occurrences of Gitea with Forgejo
-func renameGiteaForgejo(filename string) []byte {
- file, err := os.Open(filename)
- if err != nil {
- panic(err)
- }
-
- replacements := []string{
- "Gitea", "Forgejo",
- "https://docs.gitea.com/installation/install-from-binary", "https://forgejo.org/download/#installation-from-binary",
- "https://github.com/go-gitea/gitea/tree/master/docker", "https://forgejo.org/download/#container-image",
- "https://docs.gitea.com/installation/install-from-package", "https://forgejo.org/download",
- "https://code.gitea.io/gitea", "https://forgejo.org/download",
- "code.gitea.io/gitea", "Forgejo",
- `GitHub `, `Codeberg `,
- "https://github.com/go-gitea/gitea", "https://codeberg.org/forgejo/forgejo",
- "https://blog.gitea.io", "https://forgejo.org/news",
- "https://docs.gitea.com/usage/protected-tags", "https://forgejo.org/docs/latest/user/protection/#protected-tags",
- "https://docs.gitea.com/usage/webhooks", "https://forgejo.org/docs/latest/user/webhooks/",
- }
- replacer := strings.NewReplacer(replacements...)
- replaced := make(map[string]bool, len(replacements)/2)
- count_replaced := func(original string) {
- for i := 0; i < len(replacements); i += 2 {
- if strings.Contains(original, replacements[i]) {
- replaced[replacements[i]] = true
- }
- }
- }
-
- out := make([]byte, 0, 1024)
- scanner := bufio.NewScanner(file)
- scanner.Split(bufio.ScanLines)
- for scanner.Scan() {
- line := scanner.Text()
-
- if strings.HasPrefix(line, "license_desc=") {
- line = strings.Replace(line, "GitHub", "Forgejo", 1)
- }
-
- if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
- out = append(out, []byte(line+"\n")...)
- } else if strings.HasPrefix(line, "settings.web_hook_name_gitea") {
- out = append(out, []byte(line+"\n")...)
- out = append(out, []byte("settings.web_hook_name_forgejo = Forgejo\n")...)
- } else if strings.HasPrefix(line, "migrate.gitea.description") {
- re := regexp.MustCompile(`(.*Gitea)`)
- out = append(out, []byte(re.ReplaceAllString(line, "${1}/Forgejo")+"\n")...)
- } else {
- count_replaced(line)
- out = append(out, []byte(replacer.Replace(line)+"\n")...)
- }
- }
- file.Close()
- if strings.HasSuffix(filename, "gitea_en-US.ini") {
- for i := 0; i < len(replacements); i += 2 {
- if replaced[replacements[i]] == false {
- log.Fatalf("%s was never used to replace something in %s, it is obsolete and must be updated", replacements[i], filename)
- }
- }
- }
- return out
-}
-
func main() {
- d := os.Args[1]
- files, err := os.ReadDir(d)
- if err != nil {
- log.Fatal(err)
- }
-
- for _, f := range files {
- p := d + "/" + f.Name()
- os.WriteFile(p, renameGiteaForgejo(p), 0o644)
- }
+ fmt.Println("NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY")
}
From 1ebf4abddc05bb74d3187d01eda2384bba4e70f5 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Thu, 28 Mar 2024 22:27:53 +0100
Subject: [PATCH 025/426] [BUG] Use correct format
- `%w` is to wrap errors, but can only be used by `fmt.Errorf`. Instead
use `%v` to display the error.
- Regression of #2763
Before:
[E] failed to run attr-check. Error: %!w(*exec.ExitError=&{0xc006568e28 []})
Stderr: fatal: this operation must be run in a work tree
After:
[E] failed to run attr-check. Error: exit status 128
Stderr: fatal: this operation must be run in a work tree
---
modules/git/repo_attribute.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 7651a4434c..0008fcfe11 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -268,7 +268,7 @@ func (repo *Repository) GitAttributeChecker(treeish string, attributes ...string
if err != nil && // If there is an error we need to return but:
cmd.parentContext.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
- log.Error("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
+ log.Error("failed to run attr-check. Error: %v\nStderr: %s", err, stdErr.String())
}
}()
From 79b70893601c33a33d8d44eb0421797dfd846a47 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Thu, 28 Mar 2024 21:41:52 +0100
Subject: [PATCH 026/426] [FEAT] Configure if protected branch rule should
apply to admins
- Currently protected branch rules do not apply to admins, however in
some cases (like in the case of Forgejo project) you might also want to
apply these rules to admins to avoid accidental merges.
- Add new option to configure this on a per-rule basis.
- Adds integration tests.
- Resolves #65
---
models/forgejo_migrations/migrate.go | 2 +
models/forgejo_migrations/v1_22/v9.go | 15 ++++
models/git/protected_branch.go | 1 +
modules/structs/repo_branch.go | 3 +
options/locale/locale_en-US.ini | 3 +
routers/api/v1/repo/branch.go | 5 ++
routers/private/hook_pre_receive.go | 16 ++--
routers/web/repo/setting/protected_branch.go | 1 +
services/convert/convert.go | 1 +
services/forms/repo_form.go | 1 +
services/pull/check.go | 7 +-
services/pull/merge.go | 29 ++++---
templates/repo/issue/view_content/pull.tmpl | 2 +-
templates/repo/settings/protected_branch.tmpl | 8 ++
templates/swagger/v1_json.tmpl | 12 +++
tests/integration/proctected_branch_test.go | 87 +++++++++++++++++++
16 files changed, 167 insertions(+), 26 deletions(-)
create mode 100644 models/forgejo_migrations/v1_22/v9.go
create mode 100644 tests/integration/proctected_branch_test.go
diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go
index c2ffda5eb7..965b748ac9 100644
--- a/models/forgejo_migrations/migrate.go
+++ b/models/forgejo_migrations/migrate.go
@@ -54,6 +54,8 @@ var migrations = []*Migration{
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
// v7 -> v8
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
+ // v8 -> v9
+ NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
}
// GetCurrentDBVersion returns the current Forgejo database version.
diff --git a/models/forgejo_migrations/v1_22/v9.go b/models/forgejo_migrations/v1_22/v9.go
new file mode 100644
index 0000000000..34c2844c39
--- /dev/null
+++ b/models/forgejo_migrations/v1_22/v9.go
@@ -0,0 +1,15 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddApplyToAdminsSetting(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ ID int64 `xorm:"pk autoincr"`
+ ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(&ProtectedBranch{})
+}
diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go
index e0ff4d1542..a8b8c81bbe 100644
--- a/models/git/protected_branch.go
+++ b/models/git/protected_branch.go
@@ -58,6 +58,7 @@ type ProtectedBranch struct {
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"`
+ ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
diff --git a/modules/structs/repo_branch.go b/modules/structs/repo_branch.go
index e96d276b29..0b3b0bb030 100644
--- a/modules/structs/repo_branch.go
+++ b/modules/structs/repo_branch.go
@@ -47,6 +47,7 @@ type BranchProtection struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
+ ApplyToAdmins bool `json:"apply_to_admins"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@@ -80,6 +81,7 @@ type CreateBranchProtectionOption struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
+ ApplyToAdmins bool `json:"apply_to_admins"`
}
// EditBranchProtectionOption options for editing a branch protection
@@ -106,4 +108,5 @@ type EditBranchProtectionOption struct {
RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
+ ApplyToAdmins *bool `json:"apply_to_admins"`
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 17a8180ec9..5042e5467e 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2348,6 +2348,7 @@ settings.event_pull_request_review_request = Pull request review requested
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
settings.event_pull_request_approvals = Pull request approvals
settings.event_pull_request_merge = Pull request merge
+settings.event_pull_request_enforcement = Enforcement
settings.event_package = Package
settings.event_package_desc = Package created or deleted in a repository.
settings.branch_filter = Branch filter
@@ -2462,6 +2463,8 @@ settings.block_on_official_review_requests = Block merge on official review requ
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
settings.block_outdated_branch = Block merge if pull request is outdated
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
+settings.enforce_on_admins = Enforce this rule for repository admins
+settings.enforce_on_admins_desc = Repository admins cannot bypass this rule.
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
settings.merge_style_desc = Merge styles
settings.default_merge_style_desc = Default merge style
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 5e6b6a8658..c33beee0ae 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -621,6 +621,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
+ ApplyToAdmins: form.ApplyToAdmins,
}
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
@@ -808,6 +809,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
}
+ if form.ApplyToAdmins != nil {
+ protectBranch.ApplyToAdmins = *form.ApplyToAdmins
+ }
+
var whitelistUsers []int64
if form.PushWhitelistUsernames != nil {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index f45e57b9e3..0613492845 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -337,13 +337,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
return
}
- // If we're an admin for the repository we can ignore status checks, reviews and override protected files
- if ctx.userPerm.IsAdmin() {
- return
- }
-
- // Now if we're not an admin - we can't overwrite protected files so fail now
- if changedProtectedfiles {
+ // It's not allowed t overwrite protected files. Unless if the user is an
+ // admin and the protected branch rule doesn't apply to admins.
+ if changedProtectedfiles && (!ctx.user.IsAdmin || protectBranch.ApplyToAdmins) {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
@@ -352,8 +348,12 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
// Check all status checks and reviews are ok
- if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
+ if pb, err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
if models.IsErrDisallowedToMerge(err) {
+ // Allow this if the rule doesn't apply to admins and the user is an admin.
+ if ctx.user.IsAdmin && !pb.ApplyToAdmins {
+ return
+ }
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error())
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()),
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index 7ee67e5925..25146779de 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -237,6 +237,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
+ protectBranch.ApplyToAdmins = f.ApplyToAdmins
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
diff --git a/services/convert/convert.go b/services/convert/convert.go
index ca3ec32a40..dd2239458e 100644
--- a/services/convert/convert.go
+++ b/services/convert/convert.go
@@ -162,6 +162,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
+ ApplyToAdmins: bp.ApplyToAdmins,
Created: bp.CreatedUnix.AsTime(),
Updated: bp.UpdatedUnix.AsTime(),
}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 0f7665804d..b5ff031f4b 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -219,6 +219,7 @@ type ProtectBranchForm struct {
RequireSignedCommits bool
ProtectedFilePatterns string
UnprotectedFilePatterns string
+ ApplyToAdmins bool
}
// Validate validates the fields
diff --git a/services/pull/check.go b/services/pull/check.go
index f4dd332b14..9aab3c94f3 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -104,7 +104,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
return ErrIsChecking
}
- if err := CheckPullBranchProtections(ctx, pr, false); err != nil {
+ if pb, err := CheckPullBranchProtections(ctx, pr, false); err != nil {
if !models.IsErrDisallowedToMerge(err) {
log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err)
return err
@@ -117,8 +117,9 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
err = nil
}
- // * if the doer is admin, they could skip the branch protection check
- if adminSkipProtectionCheck {
+ // * if the doer is admin, they could skip the branch protection check,
+ // if that's allowed by the protected branch rule.
+ if adminSkipProtectionCheck && !pb.ApplyToAdmins {
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
return errCheckAdmin
diff --git a/services/pull/merge.go b/services/pull/merge.go
index df8d66e2d4..7f79eca2aa 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -424,63 +424,64 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a
return false, nil
}
-// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
-func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) {
+// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks).
+// Returns the protected branch rule when `ErrDisallowedToMerge` is returned as error.
+func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (protectedBranchRule *git_model.ProtectedBranch, err error) {
if err = pr.LoadBaseRepo(ctx); err != nil {
- return fmt.Errorf("LoadBaseRepo: %w", err)
+ return nil, fmt.Errorf("LoadBaseRepo: %w", err)
}
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if err != nil {
- return fmt.Errorf("LoadProtectedBranch: %v", err)
+ return nil, fmt.Errorf("LoadProtectedBranch: %v", err)
}
if pb == nil {
- return nil
+ return nil, nil
}
isPass, err := IsPullCommitStatusPass(ctx, pr)
if err != nil {
- return err
+ return nil, err
}
if !isPass {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "Not all required status checks successful",
}
}
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "Does not have enough approvals",
}
}
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "There are requested changes",
}
}
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "There are official review requests",
}
}
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "The head branch is behind the base branch",
}
}
if skipProtectedFilesCheck {
- return nil
+ return nil, nil
}
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
- return models.ErrDisallowedToMerge{
+ return pb, models.ErrDisallowedToMerge{
Reason: "Changed protected files",
}
}
- return nil
+ return nil, nil
}
// MergedManually mark pr as merged manually
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 19f1d3f91d..08f666d210 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -158,7 +158,7 @@
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
{{/* admin can merge without checks, writer can merge when checks succeed */}}
- {{$canMergeNow := and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
+ {{$canMergeNow := and (or (and $.IsRepoAdmin (not .ProtectedBranch.ApplyToAdmins)) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{/* admin and writer both can make an auto merge schedule */}}
{{if $canMergeNow}}
diff --git a/templates/repo/settings/protected_branch.tmpl b/templates/repo/settings/protected_branch.tmpl
index e95dd831c9..e1ee7b36f5 100644
--- a/templates/repo/settings/protected_branch.tmpl
+++ b/templates/repo/settings/protected_branch.tmpl
@@ -260,6 +260,14 @@
{{ctx.Locale.Tr "repo.settings.block_outdated_branch_desc"}}
+
+
+
+
+
{{ctx.Locale.Tr "repo.settings.enforce_on_admins"}}
+
{{ctx.Locale.Tr "repo.settings.enforce_on_admins_desc"}}
+
+
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 6034a8fbce..ee1ed64eaf 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -17756,6 +17756,10 @@
"description": "BranchProtection represents a branch protection for a repository",
"type": "object",
"properties": {
+ "apply_to_admins": {
+ "type": "boolean",
+ "x-go-name": "ApplyToAdmins"
+ },
"approvals_whitelist_teams": {
"type": "array",
"items": {
@@ -18406,6 +18410,10 @@
"description": "CreateBranchProtectionOption options for creating a branch protection",
"type": "object",
"properties": {
+ "apply_to_admins": {
+ "type": "boolean",
+ "x-go-name": "ApplyToAdmins"
+ },
"approvals_whitelist_teams": {
"type": "array",
"items": {
@@ -19577,6 +19585,10 @@
"description": "EditBranchProtectionOption options for editing a branch protection",
"type": "object",
"properties": {
+ "apply_to_admins": {
+ "type": "boolean",
+ "x-go-name": "ApplyToAdmins"
+ },
"approvals_whitelist_teams": {
"type": "array",
"items": {
diff --git a/tests/integration/proctected_branch_test.go b/tests/integration/proctected_branch_test.go
new file mode 100644
index 0000000000..9c6e5e3cae
--- /dev/null
+++ b/tests/integration/proctected_branch_test.go
@@ -0,0 +1,87 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "testing"
+
+ git_model "code.gitea.io/gitea/models/git"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestProtectedBranch_AdminEnforcement(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ session := loginUser(t, "user1")
+ testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
+ testEditFileToNewBranch(t, session, "user1", "repo1", "master", "add-readme", "README.md", "WIP")
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 1, Name: "repo1"})
+
+ req := NewRequestWithValues(t, "POST", "user1/repo1/compare/master...add-readme", map[string]string{
+ "_csrf": GetCSRF(t, session, "user1/repo1/compare/master...add-readme"),
+ "title": "pull request",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ t.Run("No protected branch", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
+ assert.Contains(t, text, "This pull request can be merged automatically.")
+ assert.Contains(t, text, "'canMergeNow': true")
+ })
+
+ t.Run("Without admin enforcement", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithValues(t, "POST", "/user1/repo1/settings/branches/edit", map[string]string{
+ "_csrf": GetCSRF(t, session, "/user1/repo1/settings/branches/edit"),
+ "rule_name": "master",
+ "required_approvals": "1",
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
+ assert.Contains(t, text, "This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.")
+ assert.Contains(t, text, "'canMergeNow': true")
+ })
+
+ t.Run("With admin enforcement", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ protectedBranch := unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{RuleName: "master", RepoID: repo.ID})
+ req := NewRequestWithValues(t, "POST", "/user1/repo1/settings/branches/edit", map[string]string{
+ "_csrf": GetCSRF(t, session, "/user1/repo1/settings/branches/edit"),
+ "rule_name": "master",
+ "rule_id": strconv.FormatInt(protectedBranch.ID, 10),
+ "required_approvals": "1",
+ "apply_to_admins": "true",
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
+ assert.Contains(t, text, "This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.")
+ assert.Contains(t, text, "'canMergeNow': false")
+ })
+ })
+}
From 2c8bcc163e1ed047af7c43edee9ca25f250df30e Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 00:20:21 +0100
Subject: [PATCH 027/426] [REFACTOR] PKT protocol
- Use `Fprintf` to convert to hex and do padding. Simplifies the code.
- Use `Read()` and `io.ReadFull` instead of `ReadByte()`. Should improve
performance and allows for cleaner code.
- s/pktLineTypeUnknow/pktLineTypeUnknown.
- Disallow empty Pkt line per the specification.
- Disallow too large Pkt line per the specification.
- Add unit tests.
---
cmd/hook.go | 54 ++++++++++++++----------------
cmd/hook_test.go | 85 ++++++++++++++++++++++++++++++++++++------------
cmd/serv.go | 6 +++-
3 files changed, 93 insertions(+), 52 deletions(-)
diff --git a/cmd/hook.go b/cmd/hook.go
index 966e4a57ca..3be480618f 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -583,7 +583,7 @@ Forgejo or set your environment appropriately.`, "")
for {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
- rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
+ rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -604,7 +604,7 @@ Forgejo or set your environment appropriately.`, "")
if hasPushOptions {
for {
- rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
+ rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -699,8 +699,8 @@ Forgejo or set your environment appropriately.`, "")
type pktLineType int64
const (
- // UnKnow type
- pktLineTypeUnknow pktLineType = 0
+ // Unknown type
+ pktLineTypeUnknown pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
@@ -714,22 +714,16 @@ type gitPktLine struct {
Data []byte
}
+// Reads an Pkt-Line from `in`. If requestType is not unknown, it will a
func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
- var (
- err error
- r *gitPktLine
- )
-
- // read prefix
+ // Read length prefix
lengthBytes := make([]byte, 4)
- for i := 0; i < 4; i++ {
- lengthBytes[i], err = in.ReadByte()
- if err != nil {
- return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
- }
+ if n, err := in.Read(lengthBytes); n != 4 || err != nil {
+ return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
}
- r = new(gitPktLine)
+ var err error
+ r := &gitPktLine{}
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
if err != nil {
return nil, fail(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err)
@@ -748,11 +742,8 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
}
r.Data = make([]byte, r.Length-4)
- for i := range r.Data {
- r.Data[i], err = in.ReadByte()
- if err != nil {
- return nil, fail(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err)
- }
+ if n, err := io.ReadFull(in, r.Data); uint64(n) != r.Length-4 || err != nil {
+ return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
}
r.Type = pktLineTypeData
@@ -768,20 +759,23 @@ func writeFlushPktLine(ctx context.Context, out io.Writer) error {
return nil
}
+// Write an Pkt-Line based on `data` to `out` according to the specifcation.
+// https://git-scm.com/docs/protocol-common
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
- hexchar := []byte("0123456789abcdef")
- hex := func(n uint64) byte {
- return hexchar[(n)&15]
+ // Implementations SHOULD NOT send an empty pkt-line ("0004").
+ if len(data) == 0 {
+ return fail(ctx, "Protocol: write error", "Not allowed to write empty Pkt-Line")
}
length := uint64(len(data) + 4)
- tmp := make([]byte, 4)
- tmp[0] = hex(length >> 12)
- tmp[1] = hex(length >> 8)
- tmp[2] = hex(length >> 4)
- tmp[3] = hex(length)
- lr, err := out.Write(tmp)
+ // The maximum length of a pkt-line’s data component is 65516 bytes.
+ // Implementations MUST NOT send pkt-line whose length exceeds 65520 (65516 bytes of payload + 4 bytes of length data).
+ if length > 65520 {
+ return fail(ctx, "Protocol: write error", "Pkt-Line exceeds maximum of 65520 bytes")
+ }
+
+ lr, err := fmt.Fprintf(out, "%04x", length)
if err != nil || lr != 4 {
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
}
diff --git a/cmd/hook_test.go b/cmd/hook_test.go
index 91f24ff2b4..d4e16dc411 100644
--- a/cmd/hook_test.go
+++ b/cmd/hook_test.go
@@ -14,29 +14,72 @@ import (
)
func TestPktLine(t *testing.T) {
- // test read
ctx := context.Background()
- s := strings.NewReader("0000")
- r := bufio.NewReader(s)
- result, err := readPktLine(ctx, r, pktLineTypeFlush)
- assert.NoError(t, err)
- assert.Equal(t, pktLineTypeFlush, result.Type)
- s = strings.NewReader("0006a\n")
- r = bufio.NewReader(s)
- result, err = readPktLine(ctx, r, pktLineTypeData)
- assert.NoError(t, err)
- assert.Equal(t, pktLineTypeData, result.Type)
- assert.Equal(t, []byte("a\n"), result.Data)
+ t.Run("Read", func(t *testing.T) {
+ s := strings.NewReader("0000")
+ r := bufio.NewReader(s)
+ result, err := readPktLine(ctx, r, pktLineTypeFlush)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeFlush, result.Type)
- // test write
- w := bytes.NewBuffer([]byte{})
- err = writeFlushPktLine(ctx, w)
- assert.NoError(t, err)
- assert.Equal(t, []byte("0000"), w.Bytes())
+ s = strings.NewReader("0006a\n")
+ r = bufio.NewReader(s)
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeData, result.Type)
+ assert.Equal(t, []byte("a\n"), result.Data)
- w.Reset()
- err = writeDataPktLine(ctx, w, []byte("a\nb"))
- assert.NoError(t, err)
- assert.Equal(t, []byte("0007a\nb"), w.Bytes())
+ s = strings.NewReader("0004")
+ r = bufio.NewReader(s)
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.Error(t, err)
+ assert.Nil(t, result)
+
+ data := strings.Repeat("x", 65516)
+ r = bufio.NewReader(strings.NewReader("fff0" + data))
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeData, result.Type)
+ assert.Equal(t, []byte(data), result.Data)
+
+ r = bufio.NewReader(strings.NewReader("fff1a"))
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.Error(t, err)
+ assert.Nil(t, result)
+ })
+
+ t.Run("Write", func(t *testing.T) {
+ w := bytes.NewBuffer([]byte{})
+ err := writeFlushPktLine(ctx, w)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("0000"), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, []byte("a\nb"))
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("0007a\nb"), w.Bytes())
+
+ w.Reset()
+ data := bytes.Repeat([]byte{0x05}, 288)
+ err = writeDataPktLine(ctx, w, data)
+ assert.NoError(t, err)
+ assert.Equal(t, append([]byte("0124"), data...), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, nil)
+ assert.Error(t, err)
+ assert.Empty(t, w.Bytes())
+
+ w.Reset()
+ data = bytes.Repeat([]byte{0x64}, 65516)
+ err = writeDataPktLine(ctx, w, data)
+ assert.NoError(t, err)
+ assert.Equal(t, append([]byte("fff0"), data...), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, bytes.Repeat([]byte{0x64}, 65516+1))
+ assert.Error(t, err)
+ assert.Empty(t, w.Bytes())
+ })
}
diff --git a/cmd/serv.go b/cmd/serv.go
index 9d26515254..d5c54f91b8 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -14,6 +14,7 @@ import (
"regexp"
"strconv"
"strings"
+ "testing"
"time"
"unicode"
@@ -106,7 +107,10 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
logMsg = userMessage + ". " + logMsg
}
}
- _ = private.SSHLog(ctx, true, logMsg)
+ // Don't send an log if this is done in a test and no InternalToken is set.
+ if !testing.Testing() || setting.InternalToken != "" {
+ _ = private.SSHLog(ctx, true, logMsg)
+ }
}
return cli.Exit("", 1)
}
From 3ba127c61849c2bec1efed0149c0d7ea1aaaf3c3 Mon Sep 17 00:00:00 2001
From: Lunny Xiao
Date: Mon, 25 Mar 2024 15:51:23 +0800
Subject: [PATCH 028/426] [Port] gitea#29999: Fix Add/Remove WIP on pull
request title failure
Fix #29997
---
Conflict resolution:
- Use Forgejo's user blocking feature (services/issue/issue.go)
- Trivial (tests/integration/pull_review_test.go)
Ref: https://codeberg.org/forgejo/forgejo/pulls/2872
(cherry picked from commit 475b6e839caa88994318f905f0965c3b418f876a)
---
services/issue/issue.go | 35 +++++++++++++++------------
services/issue/pull.go | 16 +++++++-----
tests/integration/pull_review_test.go | 16 +++++++++++-
3 files changed, 44 insertions(+), 23 deletions(-)
diff --git a/services/issue/issue.go b/services/issue/issue.go
index 995015da25..5e726176d0 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -17,6 +17,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
notify_service "code.gitea.io/gitea/services/notify"
@@ -64,25 +65,27 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
return nil
}
- var reviewNotifers []*ReviewRequestNotifier
-
- if err := db.WithTx(ctx, func(ctx context.Context) error {
- if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
- return err
- }
-
- if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
- var err error
- reviewNotifers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
- if err != nil {
- return err
- }
- }
- return nil
- }); err != nil {
+ if err := issue.LoadRepo(ctx); err != nil {
return err
}
+ if user_model.IsBlockedMultiple(ctx, []int64{issue.PosterID, issue.Repo.OwnerID}, doer.ID) {
+ return user_model.ErrBlockedByUser
+ }
+
+ if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
+ return err
+ }
+
+ var reviewNotifers []*ReviewRequestNotifier
+ if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
+ var err error
+ reviewNotifers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
+ if err != nil {
+ log.Error("PullRequestCodeOwnersReview: %v", err)
+ }
+ }
+
notify_service.IssueChangeTitle(ctx, doer, issue, oldTitle)
ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
diff --git a/services/issue/pull.go b/services/issue/pull.go
index 8e85c11e9b..b7b63a7024 100644
--- a/services/issue/pull.go
+++ b/services/issue/pull.go
@@ -40,7 +40,7 @@ type ReviewRequestNotifier struct {
ReviewTeam *org_model.Team
}
-func PullRequestCodeOwnersReview(ctx context.Context, pull *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
+func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
if pr.IsWorkInProgress(ctx) {
@@ -90,7 +90,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *issues_model.Issue,
// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
// between the merge base and the head commit but not the base branch and the head commit
- changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.HeadCommitID)
+ changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName())
if err != nil {
return nil, err
}
@@ -112,9 +112,13 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *issues_model.Issue,
notifiers := make([]*ReviewRequestNotifier, 0, len(uniqUsers)+len(uniqTeams))
+ if err := issue.LoadPoster(ctx); err != nil {
+ return nil, err
+ }
+
for _, u := range uniqUsers {
- if u.ID != pull.Poster.ID {
- comment, err := issues_model.AddReviewRequest(ctx, pull, u, pull.Poster)
+ if u.ID != issue.Poster.ID {
+ comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
if err != nil {
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
return nil, err
@@ -122,12 +126,12 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *issues_model.Issue,
notifiers = append(notifiers, &ReviewRequestNotifier{
Comment: comment,
IsAdd: true,
- Reviwer: pull.Poster,
+ Reviwer: u,
})
}
}
for _, t := range uniqTeams {
- comment, err := issues_model.AddTeamReviewRequest(ctx, pull, t, pull.Poster)
+ comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
if err != nil {
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
return nil, err
diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go
index 1400ad1d73..82e709382d 100644
--- a/tests/integration/pull_review_test.go
+++ b/tests/integration/pull_review_test.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ issue_service "code.gitea.io/gitea/services/issue"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests"
@@ -303,8 +304,21 @@ func TestPullView_CodeOwner(t *testing.T) {
session := loginUser(t, "user2")
testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch", "Test Pull Request")
- pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch"})
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: repo.ID, HeadBranch: "codeowner-basebranch"})
unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5})
+ assert.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ err := issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request")
+ assert.NoError(t, err)
+ prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ assert.NoError(t, prUpdated1.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title)
+
+ err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2")
+ assert.NoError(t, err)
+ prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title)
})
// change the default branch CODEOWNERS file to change README.md's codeowner
From 88800059397596e75786f128e6b72dd8a5ef281e Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 12:55:37 +0100
Subject: [PATCH 029/426] [THEME] Increase contrast of code block
- Make the background color of code blocks a bit darker, so they are
more distinctive when used in containers that use `--color-box-body` as
background color (for example, comments).
- Ref: https://codeberg.org/Codeberg/Community/issues/1523
---
web_src/css/themes/theme-forgejo-dark.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web_src/css/themes/theme-forgejo-dark.css b/web_src/css/themes/theme-forgejo-dark.css
index f27db7ac3b..819a366f27 100644
--- a/web_src/css/themes/theme-forgejo-dark.css
+++ b/web_src/css/themes/theme-forgejo-dark.css
@@ -205,7 +205,7 @@
--color-menu: var(--steel-700);
--color-card: var(--steel-700);
--color-markup-table-row: #ffffff06;
- --color-markup-code-block: var(--steel-800);
+ --color-markup-code-block: var(--steel-850);
--color-button: var(--steel-600);
--color-code-bg: var(--steel-750);
--color-code-sidebar-bg: var(--steel-600);
From 92aaa14117f5fa83838590f25e3bbe0299e02bfb Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 12:55:01 +0100
Subject: [PATCH 030/426] [BUG] Allow to exclude files in dump
- Move the skip path check outside the `file.IsDir()` condition, so it
can be used to skip files.
- Add unit tests.
- Resolves https://codeberg.org/forgejo/forgejo/issues/1397
- Ref: https://codeberg.org/forgejo/forgejo/pulls/1438
---
cmd/dump.go | 18 +++++---
cmd/dump_test.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 128 insertions(+), 7 deletions(-)
create mode 100644 cmd/dump_test.go
diff --git a/cmd/dump.go b/cmd/dump.go
index 6fbd049191..3ea92aa112 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -454,14 +454,18 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
+
+ if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
+ log.Debug("Skipping %q because matched an excluded path.", currentAbsPath)
+ continue
+ }
+
if file.IsDir() {
- if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
- if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
- return err
- }
- if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
- return err
- }
+ if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
+ return err
+ }
+ if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
+ return err
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
diff --git a/cmd/dump_test.go b/cmd/dump_test.go
new file mode 100644
index 0000000000..7b83c70f09
--- /dev/null
+++ b/cmd/dump_test.go
@@ -0,0 +1,117 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "io"
+ "os"
+ "testing"
+
+ "github.com/mholt/archiver/v3"
+ "github.com/stretchr/testify/assert"
+)
+
+type mockArchiver struct {
+ addedFiles []string
+}
+
+func (mockArchiver) Create(out io.Writer) error {
+ return nil
+}
+
+func (m *mockArchiver) Write(f archiver.File) error {
+ m.addedFiles = append(m.addedFiles, f.Name())
+ return nil
+}
+
+func (mockArchiver) Close() error {
+ return nil
+}
+
+func TestAddRecursiveExclude(t *testing.T) {
+ t.Run("Empty", func(t *testing.T) {
+ dir := t.TempDir()
+ archiver := &mockArchiver{}
+
+ err := addRecursiveExclude(archiver, "", dir, []string{}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+
+ t.Run("Single file", func(t *testing.T) {
+ dir := t.TempDir()
+ err := os.WriteFile(dir+"/example", nil, 0o666)
+ assert.NoError(t, err)
+
+ t.Run("No exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, nil, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 1)
+ assert.EqualValues(t, "example", archiver.addedFiles[0])
+ })
+
+ t.Run("With exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/example"}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+ })
+
+ t.Run("File inside directory", func(t *testing.T) {
+ dir := t.TempDir()
+ err := os.MkdirAll(dir+"/deep/nested/folder", 0o750)
+ assert.NoError(t, err)
+ err = os.WriteFile(dir+"/deep/nested/folder/example", nil, 0o666)
+ assert.NoError(t, err)
+ err = os.WriteFile(dir+"/deep/nested/folder/another-file", nil, 0o666)
+ assert.NoError(t, err)
+
+ t.Run("No exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, nil, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 5)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
+ assert.EqualValues(t, "deep/nested/folder/example", archiver.addedFiles[3])
+ assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[4])
+ })
+
+ t.Run("Exclude first directory", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep"}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+
+ t.Run("Exclude nested directory", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder"}, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 2)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ })
+
+ t.Run("Exclude file", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder/example"}, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 4)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
+ assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[3])
+ })
+ })
+}
From 39b53ef56f5d0d0cef6ff5a9a1204b04eb7f05e7 Mon Sep 17 00:00:00 2001
From: 0ko <0ko@noreply.codeberg.org>
Date: Tue, 19 Mar 2024 16:27:01 +0500
Subject: [PATCH 031/426] Provide plural support for issue participants
---
options/locale/locale_ar.ini | 2 +-
options/locale/locale_bg.ini | 2 +-
options/locale/locale_cs-CZ.ini | 2 +-
options/locale/locale_de-DE.ini | 2 +-
options/locale/locale_el-GR.ini | 2 +-
options/locale/locale_en-US.ini | 3 ++-
options/locale/locale_es-ES.ini | 2 +-
options/locale/locale_fa-IR.ini | 2 +-
options/locale/locale_fi-FI.ini | 2 +-
options/locale/locale_fr-FR.ini | 3 ++-
options/locale/locale_hu-HU.ini | 2 +-
options/locale/locale_id-ID.ini | 2 +-
options/locale/locale_it-IT.ini | 2 +-
options/locale/locale_ja-JP.ini | 2 +-
options/locale/locale_ko-KR.ini | 2 +-
options/locale/locale_lv-LV.ini | 2 +-
options/locale/locale_nl-NL.ini | 2 +-
options/locale/locale_pl-PL.ini | 2 +-
options/locale/locale_pt-BR.ini | 2 +-
options/locale/locale_pt-PT.ini | 2 +-
options/locale/locale_ru-RU.ini | 3 ++-
options/locale/locale_si-LK.ini | 2 +-
options/locale/locale_sv-SE.ini | 2 +-
options/locale/locale_tr-TR.ini | 2 +-
options/locale/locale_uk-UA.ini | 2 +-
options/locale/locale_zh-CN.ini | 2 +-
options/locale/locale_zh-HK.ini | 2 +-
options/locale/locale_zh-TW.ini | 2 +-
templates/repo/issue/view_content/sidebar.tmpl | 2 +-
29 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini
index 38e18bec5a..4e0c617907 100644
--- a/options/locale/locale_ar.ini
+++ b/options/locale/locale_ar.ini
@@ -680,7 +680,7 @@ issues.self_assign_at = `كلّف نفسه بها %s`
issues.label_deletion = احذف التصنيف
issues.filter_milestone_all = كل الأهداف
issues.unlock.notice_2 = - يمكنك دوما إقفال هذه المسألة من جديد في المستقبل.
-issues.num_participants = %d متحاور
+issues.num_participants_few = %d متحاور
release.title = عنوان الإصدار
issues.closed_at = `أغلق هذه المسألة %[2]s `
issues.lock.title = إقفال التحاور في هذه المسألة.
diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini
index 5b9bb0715e..cea90012b1 100644
--- a/options/locale/locale_bg.ini
+++ b/options/locale/locale_bg.ini
@@ -629,7 +629,7 @@ issues.filter_milestone_all = Всички етапи
issues.filter_milestone_open = Отворени етапи
issues.filter_milestone_none = Без етапи
issues.filter_project = Проект
-issues.num_participants = %d участващи
+issues.num_participants_few = %d участващи
issues.filter_assignee = Изпълнител
issues.filter_milestone_closed = Затворени етапи
issues.filter_assginee_no_select = Всички изпълнители
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 3747ae28c9..03ee22944f 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -1603,7 +1603,7 @@ issues.label.filter_sort.alphabetically=Od začátku abecedy
issues.label.filter_sort.reverse_alphabetically=Od konce abecedy
issues.label.filter_sort.by_size=Nejmenší velikost
issues.label.filter_sort.reverse_by_size=Největší velikost
-issues.num_participants=%d účastníků
+issues.num_participants_few=%d účastníků
issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
issues.attachment.download=`Klikněte pro stažení „%s“`
issues.subscribe=Odebírat
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index f5aafdf5c9..1bdf6c4d0b 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -1596,7 +1596,7 @@ issues.label.filter_sort.alphabetically=Alphabetisch
issues.label.filter_sort.reverse_alphabetically=Umgekehrt alphabetisch
issues.label.filter_sort.by_size=Kleinste Größe
issues.label.filter_sort.reverse_by_size=Größte Größe
-issues.num_participants=%d Beteiligte
+issues.num_participants_few=%d Beteiligte
issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
issues.subscribe=Abonnieren
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index 36b7518c62..17362a6036 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -1580,7 +1580,7 @@ issues.label.filter_sort.alphabetically=Αλφαβητικά
issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβητικά
issues.label.filter_sort.by_size=Μικρότερο μέγεθος
issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος
-issues.num_participants=%d Συμμετέχοντες
+issues.num_participants_few=%d Συμμετέχοντες
issues.attachment.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
issues.subscribe=Εγγραφή
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 17a8180ec9..bb7c716e49 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1636,7 +1636,8 @@ issues.label.filter_sort.alphabetically = Alphabetically
issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
issues.label.filter_sort.by_size = Smallest size
issues.label.filter_sort.reverse_by_size = Largest size
-issues.num_participants = %d participants
+issues.num_participants_one = %d participant
+issues.num_participants_few = %d participants
issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe
diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini
index 3dbca87381..78e06b8f09 100644
--- a/options/locale/locale_es-ES.ini
+++ b/options/locale/locale_es-ES.ini
@@ -1561,7 +1561,7 @@ issues.label.filter_sort.alphabetically=Alfabéticamente
issues.label.filter_sort.reverse_alphabetically=Invertir alfabéticamente
issues.label.filter_sort.by_size=Tamaño más pequeño
issues.label.filter_sort.reverse_by_size=Tamaño más grande
-issues.num_participants=%d participantes
+issues.num_participants_few=%d participantes
issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
issues.attachment.download=`Haga clic para descargar "%s"`
issues.subscribe=Suscribir
diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini
index 598c1636dc..7f394176d0 100644
--- a/options/locale/locale_fa-IR.ini
+++ b/options/locale/locale_fa-IR.ini
@@ -1189,7 +1189,7 @@ issues.label.filter_sort.alphabetically=الفبایی
issues.label.filter_sort.reverse_alphabetically=برعکس ترتیب الفبا
issues.label.filter_sort.by_size=کوچکترین اندازه
issues.label.filter_sort.reverse_by_size=بزرگترین اندازه
-issues.num_participants=%d مشارکت کننده
+issues.num_participants_few=%d مشارکت کننده
issues.attachment.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید
issues.attachment.download=`برای دریافت "%s" کلیک کنید`
issues.subscribe=مشترک شدن
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index c6c64ad6ce..60b6e93f00 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -920,7 +920,7 @@ issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
issues.label.filter_sort.by_size=Pienin koko
issues.label.filter_sort.reverse_by_size=Suurin koko
-issues.num_participants=%d osallistujaa
+issues.num_participants_few=%d osallistujaa
issues.subscribe=Tilaa
issues.unsubscribe=Lopeta tilaus
issues.lock=Lukitse keskustelu
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 659615e6ff..fe527fe3d3 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -1599,7 +1599,7 @@ issues.label.filter_sort.alphabetically=Par ordre alphabétique
issues.label.filter_sort.reverse_alphabetically=Par ordre alphabétique inversé
issues.label.filter_sort.by_size=Plus petite taille
issues.label.filter_sort.reverse_by_size=Plus grande taille
-issues.num_participants=%d participants
+issues.num_participants_few=%d participants
issues.attachment.open_tab=`Cliquez ici pour voir « %s » dans un nouvel onglet.`
issues.attachment.download=`Cliquez pour télécharger « %s ».`
issues.subscribe=S’abonner
@@ -3754,6 +3754,7 @@ component_failed_to_load = Une erreur inattendue s'est produite.
contributors.what = contributions
component_loading = Chargement %s...
component_loading_failed = Échec de chargement de %s
+
code_frequency.what = fŕequence de code
recent_commits.what = commits récents
diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini
index 3764f6fa61..5be3fa9c6f 100644
--- a/options/locale/locale_hu-HU.ini
+++ b/options/locale/locale_hu-HU.ini
@@ -857,7 +857,7 @@ issues.label.filter_sort.alphabetically=Betűrendben
issues.label.filter_sort.reverse_alphabetically=Fordított betűrendben
issues.label.filter_sort.by_size=Legkisebb méret
issues.label.filter_sort.reverse_by_size=Legnagyobb méret
-issues.num_participants=%d Résztvevő
+issues.num_participants_few=%d Résztvevő
issues.attachment.open_tab=`A(z) "%s" megnyitása új fülön`
issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez`
issues.subscribe=Feliratkozás
diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini
index 574063bcaa..3ab9991f4a 100644
--- a/options/locale/locale_id-ID.ini
+++ b/options/locale/locale_id-ID.ini
@@ -729,7 +729,7 @@ issues.label_edit=Sunting
issues.label_delete=Hapus
issues.label.filter_sort.alphabetically=Urutan abjad
issues.label.filter_sort.reverse_alphabetically=Membalikkan menurut abjad
-issues.num_participants=%d peserta
+issues.num_participants_few=%d peserta
issues.attachment.open_tab=`Klik untuk melihat "%s" di tab baru`
issues.attachment.download=`Klik untuk mengunduh "%s"`
issues.subscribe=Berlangganan
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index dc6f4c127e..cb30d51e2f 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -1478,7 +1478,7 @@ issues.label.filter_sort.alphabetically=In ordine alfabetico
issues.label.filter_sort.reverse_alphabetically=In ordine alfabetico inverso
issues.label.filter_sort.by_size=Dimensione più piccola
issues.label.filter_sort.reverse_by_size=Dimensione più grande
-issues.num_participants=%d partecipanti
+issues.num_participants_few=%d partecipanti
issues.attachment.open_tab=`Clicca per vedere "%s" in una nuova scheda`
issues.attachment.download=`Clicca qui per scaricare "%s"`
issues.subscribe=Iscriviti
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index 7f3e1ded36..4774ef78cb 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -1587,7 +1587,7 @@ issues.label.filter_sort.alphabetically=アルファベット順
issues.label.filter_sort.reverse_alphabetically=逆アルファベット順
issues.label.filter_sort.by_size=サイズの小さい順
issues.label.filter_sort.reverse_by_size=サイズの大きい順
-issues.num_participants=%d 人の参加者
+issues.num_participants_few=%d 人の参加者
issues.attachment.open_tab=`クリックして新しいタブで "%s" を見る`
issues.attachment.download=`クリックして "%s" をダウンロード`
issues.subscribe=購読する
diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini
index fa6df2d2a1..0903f5eb3e 100644
--- a/options/locale/locale_ko-KR.ini
+++ b/options/locale/locale_ko-KR.ini
@@ -782,7 +782,7 @@ issues.label_deletion_desc=라벨을 삭제하면 모든 이슈로부터도 삭
issues.label_deletion_success=라벨이 삭제되었습니다.
issues.label.filter_sort.alphabetically=알파벳순
issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬
-issues.num_participants=참여자 %d명
+issues.num_participants_few=참여자 %d명
issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기`
issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 '
issues.subscribe=구독하기
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index ce8f05e4b5..587143c9c3 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -1540,7 +1540,7 @@ issues.label.filter_sort.alphabetically=Alfabētiski
issues.label.filter_sort.reverse_alphabetically=Pretēji alfabētiski
issues.label.filter_sort.by_size=Mazākais izmērs
issues.label.filter_sort.reverse_by_size=Lielākais izmērs
-issues.num_participants=%d dalībnieki
+issues.num_participants_few=%d dalībnieki
issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
issues.subscribe=Abonēt
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index 4358cc1e81..d5071d98bf 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -1482,7 +1482,7 @@ issues.label.filter_sort.alphabetically=Alfabetisch
issues.label.filter_sort.reverse_alphabetically=Omgekeerd alfabetisch
issues.label.filter_sort.by_size=Kleinste grootte
issues.label.filter_sort.reverse_by_size=Grootste grootte
-issues.num_participants=%d deelnemers
+issues.num_participants_few=%d deelnemers
issues.attachment.open_tab=`Klik om "%s" in een nieuw tabblad te bekijken`
issues.attachment.download=`Klik om "%s" te downloaden`
issues.subscribe=Abonneren
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index a254a912bd..f23f68f7c8 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -1179,7 +1179,7 @@ issues.label.filter_sort.alphabetically=Alfabetycznie
issues.label.filter_sort.reverse_alphabetically=Alfabetycznie odwrotnie
issues.label.filter_sort.by_size=Najmniejszy rozmiar
issues.label.filter_sort.reverse_by_size=Największy rozmiar
-issues.num_participants=Uczestnicy %d
+issues.num_participants_few=Uczestnicy %d
issues.attachment.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie`
issues.attachment.download=`Kliknij, aby pobrać "%s"`
issues.subscribe=Subskrybuj
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 086cea74fa..ac30ff3e74 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -1552,7 +1552,7 @@ issues.label.filter_sort.alphabetically=Alfabeticamente
issues.label.filter_sort.reverse_alphabetically=Alfabeticamente inverso
issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior tamanho
-issues.num_participants=%d participante(s)
+issues.num_participants_few=%d participante(s)
issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
issues.attachment.download=`Clique para baixar "%s"`
issues.subscribe=Inscrever-se
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 3538ac9460..09b3fc3153 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -1548,7 +1548,7 @@ issues.label.filter_sort.alphabetically=por ordem alfabética
issues.label.filter_sort.reverse_alphabetically=por ordem alfabética inversa
issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior tamanho
-issues.num_participants=%d Participantes
+issues.num_participants_few=%d Participantes
issues.attachment.open_tab=`Clique para ver "%s" num separador novo`
issues.attachment.download=`Clique para descarregar "%s"`
issues.subscribe=Subscrever
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index addc2f8110..34321b18f9 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -1581,7 +1581,8 @@ issues.label.filter_sort.alphabetically=По алфавиту
issues.label.filter_sort.reverse_alphabetically=С конца алфавита
issues.label.filter_sort.by_size=Меньший размер
issues.label.filter_sort.reverse_by_size=Больший размер
-issues.num_participants=%d участвующих
+issues.num_participants_one=%d участник
+issues.num_participants_few=%d участников
issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
issues.subscribe=Подписаться
diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini
index 7cb1768d22..ac837173e4 100644
--- a/options/locale/locale_si-LK.ini
+++ b/options/locale/locale_si-LK.ini
@@ -1132,7 +1132,7 @@ issues.label.filter_sort.alphabetically=අකාරාදී
issues.label.filter_sort.reverse_alphabetically=අකාරාදී ප්රතිවිකුණුම්
issues.label.filter_sort.by_size=කුඩාම ප්රමාණය
issues.label.filter_sort.reverse_by_size=විශාලම ප්රමාණය
-issues.num_participants=සහභාගිවන්නන් %d
+issues.num_participants_few=සහභාගිවන්නන් %d
issues.attachment.open_tab=`නව වගුවක "%s" බැලීමට ක්ලික් කරන්න`
issues.attachment.download=`"%s" බාගැනීමට ඔබන්න`
issues.subscribe=දායක වන්න
diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini
index 38382a6f66..e7cb248aaa 100644
--- a/options/locale/locale_sv-SE.ini
+++ b/options/locale/locale_sv-SE.ini
@@ -972,7 +972,7 @@ issues.label.filter_sort.alphabetically=Alfabetiskt A-Ö
issues.label.filter_sort.reverse_alphabetically=Alfabetiskt Ö-A
issues.label.filter_sort.by_size=Minsta storlek
issues.label.filter_sort.reverse_by_size=Största storlek
-issues.num_participants=%d Deltagare
+issues.num_participants_few=%d Deltagare
issues.attachment.open_tab=`Klicka för att se "%s" i en ny flik`
issues.attachment.download=`Klicka för att hämta "%s"`
issues.subscribe=Prenumerera
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index 74ef77eb19..fa8a1b687f 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -1539,7 +1539,7 @@ issues.label.filter_sort.alphabetically=Alfabetik
issues.label.filter_sort.reverse_alphabetically=Ters alfabetik
issues.label.filter_sort.by_size=En küçük boyut
issues.label.filter_sort.reverse_by_size=En büyük boyut
-issues.num_participants=%d Katılımcı
+issues.num_participants_few=%d Katılımcı
issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
issues.attachment.download=`"%s" indirmek için tıkla`
issues.subscribe=Abone Ol
diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini
index 0a79e54010..d1040ac2b5 100644
--- a/options/locale/locale_uk-UA.ini
+++ b/options/locale/locale_uk-UA.ini
@@ -1248,7 +1248,7 @@ issues.label.filter_sort.alphabetically=За алфавітом
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
issues.label.filter_sort.by_size=Найменший розмір
issues.label.filter_sort.reverse_by_size=Найбільший розмір
-issues.num_participants=%d учасників
+issues.num_participants_few=%d учасників
issues.attachment.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
issues.attachment.download=`Натисніть щоб завантажити "%s"`
issues.subscribe=Підписатися
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 4ca2e70f21..b0e3b383b6 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -1597,7 +1597,7 @@ issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母逆序排序
issues.label.filter_sort.by_size=最小尺寸
issues.label.filter_sort.reverse_by_size=最大尺寸
-issues.num_participants=%d 名参与者
+issues.num_participants_few=%d 名参与者
issues.attachment.open_tab=`在新的标签页中查看 '%s'`
issues.attachment.download=`点击下载 '%s'`
issues.subscribe=订阅
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index 5c1e234392..ab0a075d42 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -467,7 +467,7 @@ issues.label_edit=編輯
issues.label_delete=刪除
issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
-issues.num_participants=%d 參與者
+issues.num_participants_few=%d 參與者
issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
issues.attachment.download=`點擊下載 '%s'`
issues.subscribe=訂閱
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 5bfff77fd2..25e98ed025 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -1433,7 +1433,7 @@ issues.label.filter_sort.alphabetically=按字母順序排序
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
issues.label.filter_sort.by_size=檔案由小到大
issues.label.filter_sort.reverse_by_size=檔案由大到小
-issues.num_participants=%d 參與者
+issues.num_participants_few=%d 參與者
issues.attachment.open_tab=`在新分頁中查看「%s」`
issues.attachment.download=`點擊下載「%s」`
issues.subscribe=訂閱
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 1414ac45ee..badad6ac47 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -263,7 +263,7 @@
{{if .Participants}}
- {{ctx.Locale.Tr "repo.issues.num_participants" .NumParticipants}}
+ {{ctx.Locale.TrN .NumParticipants "repo.issues.num_participants_one" "repo.issues.num_participants_few" .NumParticipants}}
{{range .Participants}}
From 190383dbf82d9fa3e1c5a89078604bc04b29ae75 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 14:53:56 +0100
Subject: [PATCH 032/426] [BUG] Don't delete inactive emails explicitly
- `user_model.DeleteInactiveEmailAddresses` related code was added in
Gogs as part to delete inactive users, however since then the related
code to delete users has changed and this code now already delete email
addresses of the user, it's therefore not needed anymore to
`DeleteInactiveEmailAddresses`.
- The call to `DeleteInactiveEmailAddresses` can actually cause issues.
As the associated user might not have been deleted, because it
was not older than the specified `olderThan` argument. Therefore causing
a database inconsistency and lead to internal server errors if the user
tries to activate their account.
- Adds unit test to verify correct behavior (fails without this patch).
---
models/user/email_address.go | 8 --------
services/user/user.go | 2 +-
services/user/user_test.go | 32 ++++++++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/models/user/email_address.go b/models/user/email_address.go
index 45bcc54aa3..85824fcdcb 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -278,14 +278,6 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) {
return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
}
-// DeleteInactiveEmailAddresses deletes inactive email addresses
-func DeleteInactiveEmailAddresses(ctx context.Context) error {
- _, err := db.GetEngine(ctx).
- Where("is_activated = ?", false).
- Delete(new(EmailAddress))
- return err
-}
-
// ActivateEmail activates the email address to given user.
func ActivateEmail(ctx context.Context, email *EmailAddress) error {
ctx, committer, err := db.TxContext(ctx)
diff --git a/services/user/user.go b/services/user/user.go
index f2648db409..9dc4f6fe62 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -304,5 +304,5 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
}
}
- return user_model.DeleteInactiveEmailAddresses(ctx)
+ return nil
}
diff --git a/services/user/user_test.go b/services/user/user_test.go
index 2ebcded925..9013208ed0 100644
--- a/services/user/user_test.go
+++ b/services/user/user_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"testing"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/auth"
@@ -16,6 +17,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -184,3 +186,33 @@ func TestCreateUser_Issue5882(t *testing.T) {
assert.NoError(t, DeleteUser(db.DefaultContext, v.user, false))
}
}
+
+func TestDeleteInactiveUsers(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ // Add an inactive user older than a minute, with an associated email_address record.
+ oldUser := &user_model.User{Name: "OldInactive", LowerName: "oldinactive", Email: "old@example.com", CreatedUnix: timeutil.TimeStampNow().Add(-120)}
+ _, err := db.GetEngine(db.DefaultContext).NoAutoTime().Insert(oldUser)
+ assert.NoError(t, err)
+ oldEmail := &user_model.EmailAddress{UID: oldUser.ID, IsPrimary: true, Email: "old@example.com", LowerEmail: "old@example.com"}
+ err = db.Insert(db.DefaultContext, oldEmail)
+ assert.NoError(t, err)
+
+ // Add an inactive user that's not older than a minute, with an associated email_address record.
+ newUser := &user_model.User{Name: "NewInactive", LowerName: "newinactive", Email: "new@example.com"}
+ err = db.Insert(db.DefaultContext, newUser)
+ assert.NoError(t, err)
+ newEmail := &user_model.EmailAddress{UID: newUser.ID, IsPrimary: true, Email: "new@example.com", LowerEmail: "new@example.com"}
+ err = db.Insert(db.DefaultContext, newEmail)
+ assert.NoError(t, err)
+
+ err = DeleteInactiveUsers(db.DefaultContext, time.Minute)
+ assert.NoError(t, err)
+
+ // User older than a minute should be deleted along with their email address.
+ unittest.AssertExistsIf(t, false, oldUser)
+ unittest.AssertExistsIf(t, false, oldEmail)
+
+ // User not older than a minute shouldn't be deleted and their emaill address should still exist.
+ unittest.AssertExistsIf(t, true, newUser)
+ unittest.AssertExistsIf(t, true, newEmail)
+}
From 722e0dbac13d6dd3df66a9d1f2dbca096c57f7e7 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 15:38:31 +0100
Subject: [PATCH 033/426] [THEME] Revert darker tone on labels
- In #1782 I made these darker to make some elements look good, but this
variable is used quite broadly which causes some issues. So reverting
the darker tone to the more light tone (which is identical with what's
the behavior before #1782 was merged).
- Resolves https://codeberg.org/forgejo/forgejo/issues/2879
---
web_src/css/themes/theme-forgejo-dark.css | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/web_src/css/themes/theme-forgejo-dark.css b/web_src/css/themes/theme-forgejo-dark.css
index 819a366f27..0533344a8c 100644
--- a/web_src/css/themes/theme-forgejo-dark.css
+++ b/web_src/css/themes/theme-forgejo-dark.css
@@ -226,9 +226,9 @@
--color-nav-bg: var(--steel-900);
--color-nav-hover-bg: var(--steel-600);
--color-label-text: #fff;
- --color-label-bg: #393939;
- --color-label-hover-bg: #5f5f5f;
- --color-label-active-bg: #4c4c4c;
+ --color-label-bg: #cacaca5b;
+ --color-label-hover-bg: #cacacaa0;
+ --color-label-active-bg: #cacacaff;
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-active-line: var(--color-primary-alpha-20);
From f579bde69df2d4d7520c819f9b9eb198029e0670 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Fri, 29 Mar 2024 23:48:47 +0100
Subject: [PATCH 034/426] [CHORE] Cleanup dependency
- Remove `gitea.com/lunny/dingtalk_webhook` as dependency, we only use
two structs which are small enough to be recreated in Forgejo and don't
need to rely on the dependency.
- Existing tests (thanks @oliverpool) prove that this has no effect.
---
assets/go-licenses.json | 5 -----
go.mod | 1 -
go.sum | 2 --
services/webhook/dingtalk.go | 22 +++++++++++++++++-----
4 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index ad3ab6c4c1..d1062d747b 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -64,11 +64,6 @@
"path": "gitea.com/go-chi/session/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License."
},
- {
- "name": "gitea.com/lunny/dingtalk_webhook",
- "path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
- "licenseText": "Copyright (c) 2016 The Gitea Authors\nCopyright (c) 2015 The Gogs Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
- },
{
"name": "gitea.com/lunny/levelqueue",
"path": "gitea.com/lunny/levelqueue/LICENSE",
diff --git a/go.mod b/go.mod
index dbefea608c..5c39436eca 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,6 @@ require (
gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
- gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
diff --git a/go.sum b/go.sum
index ae1f89b43f..5ab9cfc5f9 100644
--- a/go.sum
+++ b/go.sum
@@ -62,8 +62,6 @@ gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
-gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
-gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go
index 562e3e86eb..f4bc715e08 100644
--- a/services/webhook/dingtalk.go
+++ b/services/webhook/dingtalk.go
@@ -16,8 +16,6 @@ import (
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms"
-
- dingtalk "gitea.com/lunny/dingtalk_webhook"
)
type dingtalkHandler struct{}
@@ -42,8 +40,22 @@ func (dingtalkHandler) FormFields(bind func(any)) FormFields {
}
type (
- // DingtalkPayload represents
- DingtalkPayload dingtalk.Payload
+ // DingtalkPayload represents an dingtalk payload.
+ DingtalkPayload struct {
+ MsgType string `json:"msgtype"`
+ Text struct {
+ Content string `json:"content"`
+ } `json:"text"`
+ ActionCard DingtalkActionCard `json:"actionCard"`
+ }
+
+ DingtalkActionCard struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ HideAvatar string `json:"hideAvatar"`
+ SingleTitle string `json:"singleTitle"`
+ SingleURL string `json:"singleURL"`
+ }
)
// Create implements PayloadConvertor Create method
@@ -195,7 +207,7 @@ func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, err
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
return DingtalkPayload{
MsgType: "actionCard",
- ActionCard: dingtalk.ActionCard{
+ ActionCard: DingtalkActionCard{
Text: strings.TrimSpace(text),
Title: strings.TrimSpace(title),
HideAvatar: "0",
From 6c5534737731aa5c7663989aac8f7cce00e428e8 Mon Sep 17 00:00:00 2001
From: Gusted
Date: Sat, 30 Mar 2024 01:56:25 +0100
Subject: [PATCH 035/426] [BRANDING] Move docs.gitea.com to forgejo.org/docs
- Replace links from docs.gitea.com with forgejo.org/docs for those
where the relevant links are available on the Forgejo documentation.
- Resolves #2892
---
options/locale/locale_en-US.ini | 2 +-
templates/install.tmpl | 2 +-
templates/repo/actions/no_workflows.tmpl | 4 ++--
templates/repo/migrate/gitea.tmpl | 2 +-
templates/user/settings/applications.tmpl | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 17a8180ec9..c83f386f2a 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3148,7 +3148,7 @@ auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API con
auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints
auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled
auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me
-auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://docs.gitea.com/development/oauth2-provider
+auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://forgejo.org/docs/latest/user/oauth2-provider
auths.tip.yandex = Create a new application at https://oauth.yandex.com/client/new. Select following permissions from the "Yandex.Passport API" section: "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender"
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
diff --git a/templates/install.tmpl b/templates/install.tmpl
index b3aea39ee5..faec8cb587 100644
--- a/templates/install.tmpl
+++ b/templates/install.tmpl
@@ -8,7 +8,7 @@
{{template "base/alert" .}}
-
{{ctx.Locale.Tr "install.docker_helper" "https://docs.gitea.com/installation/install-with-docker"}}
+
{{ctx.Locale.Tr "install.docker_helper" "https://forgejo.org/docs/latest/admin/installation-docker/"}}
diff --git a/templates/repo/migrate/gitea.tmpl b/templates/repo/migrate/gitea.tmpl
index 143f220449..f2a3ae4348 100644
--- a/templates/repo/migrate/gitea.tmpl
+++ b/templates/repo/migrate/gitea.tmpl
@@ -21,7 +21,7 @@
{{template "repo/migrate/options" .}}
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl
index e43cf2ebbe..e6b70fb6d3 100644
--- a/templates/user/settings/applications.tmpl
+++ b/templates/user/settings/applications.tmpl
@@ -75,7 +75,7 @@
{{ctx.Locale.Tr "settings.select_permissions"}}
- {{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}
+ {{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}
Date: Sat, 30 Mar 2024 03:58:50 +0100
Subject: [PATCH 036/426] [FEAT] Remove add organization on dashboard switcher
- Similair to #2593 that removed subtle add button in a place where it's
strictly not needed.
- Remove the "Add organization" item in the 'context dashboard switcher' dropdown.
---
templates/user/dashboard/navbar.tmpl | 5 -----
1 file changed, 5 deletions(-)
diff --git a/templates/user/dashboard/navbar.tmpl b/templates/user/dashboard/navbar.tmpl
index bdd26ec464..b2ee198b0a 100644
--- a/templates/user/dashboard/navbar.tmpl
+++ b/templates/user/dashboard/navbar.tmpl
@@ -35,11 +35,6 @@
{{end}}
- {{if .SignedUser.CanCreateOrganization}}
-
- {{svg "octicon-plus"}} {{ctx.Locale.Tr "new_org"}}
-
- {{end}}
From a517e4aeb1146aea392d6e7f5bdfe5ed8513a61d Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Sat, 30 Mar 2024 06:05:07 +0000
Subject: [PATCH 037/426] Update module github.com/minio/minio-go/v7 to v7.0.69
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 5c39436eca..db3560d3fb 100644
--- a/go.mod
+++ b/go.mod
@@ -74,7 +74,7 @@ require (
github.com/meilisearch/meilisearch-go v0.26.1
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
- github.com/minio/minio-go/v7 v7.0.66
+ github.com/minio/minio-go/v7 v7.0.69
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
diff --git a/go.sum b/go.sum
index 5ab9cfc5f9..44d5f1a268 100644
--- a/go.sum
+++ b/go.sum
@@ -614,8 +614,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
-github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
+github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
+github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
From 0cb9ea64a659c7664ee25d1b44aacd00b99c6a2f Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Sat, 30 Mar 2024 06:05:12 +0000
Subject: [PATCH 038/426] Update module github.com/opencontainers/image-spec to
v1.1.0
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 5c39436eca..cc3528800d 100644
--- a/go.mod
+++ b/go.mod
@@ -80,7 +80,7 @@ require (
github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
- github.com/opencontainers/image-spec v1.1.0-rc6
+ github.com/opencontainers/image-spec v1.1.0
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.18.0
github.com/quasoft/websspi v1.1.2
diff --git a/go.sum b/go.sum
index 5ab9cfc5f9..b3fae2a8d8 100644
--- a/go.sum
+++ b/go.sum
@@ -668,8 +668,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
-github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
From c82bef515e81997b2691ebd7ab5449adda060e8d Mon Sep 17 00:00:00 2001
From: silverwind
Date: Sun, 24 Mar 2024 17:42:49 +0100
Subject: [PATCH 039/426] Migrate margin and padding helpers to tailwind
(#30043)
This will conclude the refactor of 1:1 class replacements to tailwind,
except `gt-hidden`. Commands ran:
```bash
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-0#tw-$1$2-0#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-1#tw-$1$2-0.5#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-2#tw-$1$2-1#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-3#tw-$1$2-2#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-4#tw-$1$2-4#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-5#tw-$1$2-8#g' {web_src/js,templates,routers,services}/**/*
```
(cherry picked from commit 68ec9b48592fe88765bcc3a73093d43c98b315de)
Conflicts:
routers/web/repo/view.go
templates/base/head_navbar.tmpl
templates/repo/code/recently_pushed_new_branches.tmpl
templates/repo/diff/box.tmpl
templates/repo/diff/compare.tmpl
templates/repo/diff/conversation.tmpl
templates/repo/header.tmpl
templates/repo/issue/filter_list.tmpl
templates/repo/issue/view_content/conversation.tmpl
templates/repo/issue/view_content/sidebar.tmpl
templates/repo/settings/options.tmpl
templates/repo/view_file.tmpl
templates/shared/user/blocked_users.tmpl
templates/status/500.tmpl
web_src/js/components/DashboardRepoList.vue
resolved by prefering Forgejo version and applying the
commands to all files
---
routers/web/repo/blame.go | 2 +-
routers/web/repo/issue_content_history.go | 2 +-
routers/web/repo/view.go | 4 +-
services/auth/source/oauth2/providers.go | 2 +-
services/auth/source/oauth2/providers_base.go | 4 +-
.../auth/source/oauth2/providers_openid.go | 2 +-
templates/admin/config.tmpl | 4 +-
templates/admin/config_settings.tmpl | 2 +-
templates/admin/cron.tmpl | 2 +-
templates/admin/dashboard.tmpl | 2 +-
templates/admin/emails/list.tmpl | 4 +-
templates/admin/org/list.tmpl | 4 +-
templates/admin/queue_manage.tmpl | 2 +-
templates/admin/repo/unadopted.tmpl | 4 +-
templates/admin/self_check.tmpl | 6 +-
templates/admin/stacktrace-row.tmpl | 10 +-
templates/admin/user/edit.tmpl | 4 +-
templates/base/head_navbar.tmpl | 28 +++---
templates/base/paginate.tmpl | 8 +-
templates/devtest/fetch-action.tmpl | 2 +-
templates/devtest/flex-list.tmpl | 4 +-
templates/devtest/gitea-ui.tmpl | 12 +--
templates/explore/repo_list.tmpl | 2 +-
templates/explore/search.tmpl | 4 +-
templates/home.tmpl | 2 +-
templates/install.tmpl | 16 +--
templates/org/follow_unfollow.tmpl | 2 +-
templates/org/header.tmpl | 4 +-
templates/org/home.tmpl | 2 +-
templates/org/projects/list.tmpl | 2 +-
templates/org/team/members.tmpl | 2 +-
templates/org/team/new.tmpl | 6 +-
templates/org/team/repositories.tmpl | 2 +-
templates/package/metadata/alpine.tmpl | 2 +-
templates/package/metadata/cargo.tmpl | 10 +-
templates/package/metadata/chef.tmpl | 6 +-
templates/package/metadata/composer.tmpl | 6 +-
templates/package/metadata/conan.tmpl | 8 +-
templates/package/metadata/conda.tmpl | 8 +-
templates/package/metadata/container.tmpl | 14 +--
templates/package/metadata/helm.tmpl | 4 +-
templates/package/metadata/maven.tmpl | 6 +-
templates/package/metadata/npm.tmpl | 8 +-
templates/package/metadata/nuget.tmpl | 6 +-
templates/package/metadata/pub.tmpl | 6 +-
templates/package/metadata/pypi.tmpl | 6 +-
templates/package/metadata/rpm.tmpl | 4 +-
templates/package/metadata/rubygems.tmpl | 6 +-
templates/package/metadata/swift.tmpl | 2 +-
templates/package/metadata/vagrant.tmpl | 6 +-
templates/package/shared/list.tmpl | 2 +-
templates/package/shared/versionlist.tmpl | 2 +-
templates/package/view.tmpl | 14 +--
templates/projects/list.tmpl | 8 +-
templates/projects/view.tmpl | 8 +-
templates/repo/actions/list.tmpl | 2 +-
templates/repo/actions/runs_list.tmpl | 4 +-
templates/repo/blame.tmpl | 6 +-
templates/repo/branch/list.tmpl | 40 ++++----
templates/repo/branch_dropdown.tmpl | 6 +-
.../code/recently_pushed_new_branches.tmpl | 2 +-
.../repo/commit_load_branches_and_tags.tmpl | 10 +-
templates/repo/commit_page.tmpl | 66 ++++++-------
templates/repo/commits.tmpl | 2 +-
templates/repo/commits_list.tmpl | 10 +-
templates/repo/commits_list_small.tmpl | 2 +-
templates/repo/commits_table.tmpl | 4 +-
templates/repo/diff/box.tmpl | 20 ++--
templates/repo/diff/comment_form.tmpl | 2 +-
templates/repo/diff/comments.tmpl | 2 +-
templates/repo/diff/compare.tmpl | 10 +-
templates/repo/diff/conversation.tmpl | 16 +--
templates/repo/diff/new_review.tmpl | 2 +-
templates/repo/diff/stats.tmpl | 2 +-
templates/repo/diff/whitespace_dropdown.tmpl | 8 +-
templates/repo/editor/commit_form.tmpl | 2 +-
templates/repo/editor/patch.tmpl | 2 +-
templates/repo/empty.tmpl | 2 +-
templates/repo/file_info.tmpl | 2 +-
templates/repo/find/files.tmpl | 4 +-
templates/repo/flags.tmpl | 2 +-
templates/repo/forks.tmpl | 4 +-
templates/repo/graph.tmpl | 14 +--
templates/repo/graph/commits.tmpl | 10 +-
templates/repo/header_fork.tmpl | 4 +-
templates/repo/home.tmpl | 22 ++---
.../repo/issue/branch_selector_field.tmpl | 4 +-
templates/repo/issue/card.tmpl | 20 ++--
templates/repo/issue/fields/checkboxes.tmpl | 2 +-
templates/repo/issue/fields/textarea.tmpl | 2 +-
templates/repo/issue/filter_actions.tmpl | 4 +-
templates/repo/issue/filter_list.tmpl | 6 +-
templates/repo/issue/filters.tmpl | 2 +-
templates/repo/issue/labels.tmpl | 2 +-
.../repo/issue/labels/edit_delete_label.tmpl | 4 +-
templates/repo/issue/labels/label_list.tmpl | 2 +-
.../issue/labels/labels_selector_field.tmpl | 2 +-
.../repo/issue/milestone/select_menu.tmpl | 4 +-
templates/repo/issue/milestone_issues.tmpl | 6 +-
templates/repo/issue/milestones.tmpl | 4 +-
templates/repo/issue/new_form.tmpl | 22 ++---
templates/repo/issue/openclose.tmpl | 8 +-
.../repo/issue/view_content/attachments.tmpl | 4 +-
.../repo/issue/view_content/comments.tmpl | 2 +-
.../repo/issue/view_content/conversation.tmpl | 22 ++---
templates/repo/issue/view_content/pull.tmpl | 4 +-
.../view_content/pull_merge_instruction.tmpl | 2 +-
.../repo/issue/view_content/sidebar.tmpl | 76 +++++++-------
.../repo/issue/view_content/watching.tmpl | 4 +-
templates/repo/issue/view_title.tmpl | 14 +--
templates/repo/latest_commit.tmpl | 4 +-
templates/repo/migrate/migrate.tmpl | 6 +-
templates/repo/projects/view.tmpl | 2 +-
templates/repo/pulls/tab_menu.tmpl | 2 +-
templates/repo/pulse.tmpl | 12 +--
templates/repo/release/list.tmpl | 18 ++--
templates/repo/release/new.tmpl | 6 +-
templates/repo/settings/branches.tmpl | 2 +-
templates/repo/settings/deploy_keys.tmpl | 2 +-
templates/repo/settings/githooks.tmpl | 6 +-
templates/repo/settings/options.tmpl | 10 +-
templates/repo/settings/protected_branch.tmpl | 8 +-
templates/repo/settings/units/issues.tmpl | 4 +-
templates/repo/settings/units/wiki.tmpl | 2 +-
.../repo/settings/webhook/base_list.tmpl | 8 +-
templates/repo/sub_menu.tmpl | 2 +-
templates/repo/tag/list.tmpl | 20 ++--
templates/repo/view_file.tmpl | 12 +--
templates/repo/view_list.tmpl | 2 +-
templates/repo/wiki/new.tmpl | 2 +-
templates/repo/wiki/revision.tmpl | 2 +-
templates/repo/wiki/start.tmpl | 2 +-
templates/shared/actions/runner_edit.tmpl | 8 +-
templates/shared/issuelist.tmpl | 4 +-
templates/shared/repo_search.tmpl | 2 +-
templates/shared/search/code/results.tmpl | 4 +-
templates/shared/searchbottom.tmpl | 6 +-
templates/shared/secrets/add_list.tmpl | 2 +-
templates/shared/user/authorlink.tmpl | 2 +-
templates/shared/user/blocked_users.tmpl | 83 ++++++++++++++++
templates/shared/user/profile_big_avatar.tmpl | 6 +-
templates/shared/variables/variable_list.tmpl | 4 +-
templates/status/500.tmpl | 8 +-
templates/user/auth/signin_inner.tmpl | 4 +-
templates/user/auth/signup_inner.tmpl | 4 +-
templates/user/auth/webauthn.tmpl | 2 +-
templates/user/auth/webauthn_error.tmpl | 2 +-
templates/user/dashboard/feeds.tmpl | 2 +-
templates/user/dashboard/issues.tmpl | 4 +-
templates/user/dashboard/milestones.tmpl | 8 +-
.../user/notification/notification_div.tmpl | 28 +++---
.../notification_subscriptions.tmpl | 4 +-
templates/user/overview/package_versions.tmpl | 2 +-
templates/user/overview/packages.tmpl | 2 +-
templates/user/profile.tmpl | 2 +-
templates/user/settings/account.tmpl | 4 +-
templates/user/settings/applications.tmpl | 14 +--
.../applications_oauth2_edit_form.tmpl | 2 +-
.../settings/applications_oauth2_list.tmpl | 4 +-
templates/user/settings/grants_oauth2.tmpl | 2 +-
templates/user/settings/keys_gpg.tmpl | 2 +-
templates/user/settings/keys_ssh.tmpl | 2 +-
templates/user/settings/profile.tmpl | 4 +-
templates/user/settings/repos.tmpl | 20 ++--
web_src/css/helpers.css | 98 -------------------
web_src/js/components/DashboardRepoList.vue | 42 ++++----
web_src/js/components/DiffCommitSelector.vue | 2 +-
web_src/js/components/DiffFileList.vue | 8 +-
web_src/js/components/DiffFileTree.vue | 2 +-
.../js/components/PullRequestMergeForm.vue | 6 +-
web_src/js/components/RepoActionView.vue | 14 +--
.../js/components/RepoBranchTagSelector.vue | 10 +-
web_src/js/components/RepoCodeFrequency.vue | 2 +-
web_src/js/components/RepoContributors.vue | 4 +-
web_src/js/components/RepoRecentCommits.vue | 2 +-
.../components/ScopedAccessTokenSelector.vue | 2 +-
web_src/js/features/repo-diff-commit.js | 2 +-
web_src/js/features/repo-findfile.js | 2 +-
web_src/js/features/repo-home.js | 2 +-
web_src/js/features/repo-issue-content.js | 2 +-
web_src/js/features/repo-issue-list.js | 2 +-
web_src/js/features/repo-legacy.js | 6 +-
web_src/js/features/tribute.js | 2 +-
183 files changed, 691 insertions(+), 706 deletions(-)
create mode 100644 templates/shared/user/blocked_users.tmpl
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 809a5009bb..eea3d4dc00 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -260,7 +260,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
if commit.User != nil {
avatar = string(avatarUtils.Avatar(commit.User, 18))
} else {
- avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "gt-mr-3"))
+ avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "tw-mr-2"))
}
br.Avatar = gotemplate.HTML(avatar)
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index dfee2863b5..c817d6aa96 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -70,7 +70,7 @@ func GetContentHistoryList(ctx *context.Context) {
}
src := html.EscapeString(item.UserAvatarLink)
- class := avatars.DefaultAvatarClass + " gt-mr-3"
+ class := avatars.DefaultAvatarClass + " tw-mr-2"
name := html.EscapeString(username)
avatarHTML := string(templates.AvatarHTML(src, 28, class, username))
timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale))
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index becb8748cd..a65a100212 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -938,9 +938,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
schema, _, _ := strings.Cut(app.OpenURL, ":")
var iconHTML template.HTML
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
- iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "gt-mr-3")
+ iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "tw-mr-2")
} else {
- iconHTML = svg.RenderHTML("gitea-git", 16, "gt-mr-3") // TODO: it could support user's customized icon in the future
+ iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
}
tmplApps = append(tmplApps, map[string]any{
"DisplayName": app.DisplayName,
diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go
index c3edae4ab6..6ed6c184eb 100644
--- a/services/auth/source/oauth2/providers.go
+++ b/services/auth/source/oauth2/providers.go
@@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
if p.iconURL != "" {
- img := fmt.Sprintf(` `,
+ img := fmt.Sprintf(` `,
size,
size,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go
index 5b6694487b..9d4ab106e5 100644
--- a/services/auth/source/oauth2/providers_base.go
+++ b/services/auth/source/oauth2/providers_base.go
@@ -35,10 +35,10 @@ func (b *BaseProvider) IconHTML(size int) template.HTML {
case "github":
svgName = "octicon-mark-github"
}
- svgHTML := svg.RenderHTML(svgName, size, "gt-mr-3")
+ svgHTML := svg.RenderHTML(svgName, size, "tw-mr-2")
if svgHTML == "" {
log.Error("No SVG icon for oauth2 provider %q", b.name)
- svgHTML = svg.RenderHTML("gitea-openid", size, "gt-mr-3")
+ svgHTML = svg.RenderHTML("gitea-openid", size, "tw-mr-2")
}
return svgHTML
}
diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go
index a4dcfcafc7..285876d5ac 100644
--- a/services/auth/source/oauth2/providers_openid.go
+++ b/services/auth/source/oauth2/providers_openid.go
@@ -29,7 +29,7 @@ func (o *OpenIDProvider) DisplayName() string {
// IconHTML returns icon HTML for this provider
func (o *OpenIDProvider) IconHTML(size int) template.HTML {
- return svg.RenderHTML("gitea-openid", size, "gt-mr-3")
+ return svg.RenderHTML("gitea-openid", size, "tw-mr-2")
}
// CreateGothProvider creates a GothProvider from this Provider
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 0c944fcb8f..1e94935a16 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -231,7 +231,7 @@
{{ctx.Locale.Tr "admin.config.mailer_user"}}
{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}
- {{ctx.Locale.Tr "admin.config.send_test_mail"}}
+ {{ctx.Locale.Tr "admin.config.send_test_mail"}}