feat: vendor gitea 1.16.2

This commit is contained in:
2026-06-02 08:36:01 +00:00
parent 247cd60f69
commit 54798b4615
2145 changed files with 162965 additions and 126447 deletions
@@ -41,11 +41,10 @@ func (g *ASTTransformer) applyElementDir(n ast.Node) {
// Transform transforms the given AST tree.
func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
firstChild := node.FirstChild()
tocMode := ""
ctx := pc.Get(renderContextKey).(*markup.RenderContext)
rc := pc.Get(renderConfigKey).(*RenderConfig)
tocList := make([]Header, 0, 20)
tocMode := ""
if rc.yamlNode != nil {
metaNode := rc.toMetaNode(g)
if metaNode != nil {
@@ -60,8 +59,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
switch v := n.(type) {
case *ast.Heading:
g.transformHeading(ctx, v, reader, &tocList)
case *ast.Paragraph:
g.applyElementDir(v)
case *ast.List:
@@ -79,19 +76,18 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
return ast.WalkContinue, nil
})
showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main"
showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar
if len(tocList) > 0 && (showTocInMain || showTocInSidebar) {
if showTocInMain {
tocNode := createTOCNode(tocList, rc.Lang, nil)
node.InsertBefore(node, firstChild, tocNode)
} else {
tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"})
ctx.SidebarTocNode = tocNode
if ctx.RenderOptions.EnableHeadingIDGeneration {
showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main"
showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar
switch {
case showTocInMain:
ctx.TocShowInSection = markup.TocShowInMain
case showTocInSidebar:
ctx.TocShowInSection = markup.TocShowInSidebar
}
}
if len(rc.Lang) > 0 {
if rc.Lang != "" {
node.SetAttributeString("lang", []byte(rc.Lang))
}
}
@@ -5,6 +5,7 @@
package markdown
import (
"bytes"
"errors"
"html/template"
"io"
@@ -20,11 +21,12 @@ import (
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
meta "github.com/yuin/goldmark-meta"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
@@ -57,7 +59,7 @@ func (l *limitWriter) Write(data []byte) (int, error) {
// newParserContext creates a parser.Context with the render context set
func newParserContext(ctx *markup.RenderContext) parser.Context {
pc := parser.NewContext(parser.WithIDs(newPrefixedIDs()))
pc := parser.NewContext()
pc.Set(renderContextKey, ctx)
return pc
}
@@ -101,12 +103,48 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C
}
}
type goldmarkEmphasisParser struct {
parser.InlineParser
}
func goldmarkNewEmphasisParser() parser.InlineParser {
return &goldmarkEmphasisParser{parser.NewEmphasisParser()}
}
func (s *goldmarkEmphasisParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
if len(line) > 1 && line[0] == '_' {
// a special trick to avoid parsing emphasis in filenames like "module/__init__.py"
end := bytes.IndexByte(line[1:], '_')
mark := bytes.Index(line, []byte("_.py"))
// check whether the "end" matches "_.py" or "__.py"
if mark != -1 && (end == mark || end == mark-1) {
return nil
}
}
return s.InlineParser.Parse(parent, block, pc)
}
func goldmarkDefaultParser() parser.Parser {
return parser.NewParser(parser.WithBlockParsers(parser.DefaultBlockParsers()...),
parser.WithInlineParsers([]util.PrioritizedValue{
util.Prioritized(parser.NewCodeSpanParser(), 100),
util.Prioritized(parser.NewLinkParser(), 200),
util.Prioritized(parser.NewAutoLinkParser(), 300),
util.Prioritized(parser.NewRawHTMLParser(), 400),
util.Prioritized(goldmarkNewEmphasisParser(), 500),
}...),
parser.WithParagraphTransformers(parser.DefaultParagraphTransformers()...),
)
}
// SpecializedMarkdown sets up the Gitea specific markdown extensions
func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
// TODO: it could use a pool to cache the renderers to reuse them with different contexts
// at the moment it is fast enough (see the benchmarks)
r := &GlodmarkRender{ctx: ctx}
r.goldmarkMarkdown = goldmark.New(
goldmark.WithParser(goldmarkDefaultParser()),
goldmark.WithExtensions(
extension.NewTable(extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)),
extension.Strikethrough,
@@ -127,11 +165,9 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
ParseBlockDollar: setting.Markdown.MathCodeBlockOptions.ParseBlockDollar,
ParseBlockSquareBrackets: setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets, // this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping
}),
meta.Meta,
),
goldmark.WithParserOptions(
parser.WithAttribute(),
parser.WithAutoHeadingID(),
parser.WithASTTransformers(util.Prioritized(NewASTTransformer(&ctx.RenderInternal), 10000)),
),
goldmark.WithRendererOptions(html.WithUnsafe()),
@@ -202,30 +238,24 @@ func init() {
markup.RegisterRenderer(Renderer{})
}
// Renderer implements markup.Renderer
type Renderer struct{}
var _ markup.PostProcessRenderer = (*Renderer)(nil)
// Name implements markup.Renderer
func (Renderer) Name() string {
return MarkupName
}
// NeedPostProcess implements markup.PostProcessRenderer
func (Renderer) NeedPostProcess() bool { return true }
// Extensions implements markup.Renderer
func (Renderer) Extensions() []string {
return setting.Markdown.FileExtensions
func (Renderer) FileNamePatterns() []string {
return setting.Markdown.FileNamePatterns
}
// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{}
}
// Render implements markup.Renderer
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
return render(ctx, input, output)
}
@@ -240,7 +270,9 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
var buf strings.Builder
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
return "", err
log.Warn("Unable to RenderString: %v, content: %s", err, giteautil.TruncateRunes(content, 200))
err = nil
return template.HTML(template.HTMLEscapeString(content)), err
}
return template.HTML(buf.String()), nil
}
@@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -23,7 +22,6 @@ const (
AppURL = "http://localhost:3000/"
testRepoOwnerName = "user13"
testRepoName = "repo11"
FullURL = AppURL + testRepoOwnerName + "/" + testRepoName + "/"
)
// these values should match the const above
@@ -47,8 +45,9 @@ func TestRender_StandardLinks(t *testing.T) {
func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL
const baseLink = "http://localhost:3000/user13/repo11"
render := func(input, expected string) {
buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input)
buffer, err := markdown.RenderString(markup.NewTestRenderContext(baseLink), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
}
@@ -56,7 +55,7 @@ func TestRender_Images(t *testing.T) {
url := "../../.images/src/02/train.jpg"
title := "Train"
href := "https://gitea.io"
result := util.URLJoin(FullURL, url)
result := baseLink + "/.images/src/02/train.jpg" // resolved link should not go out of the base link
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
render(
@@ -88,6 +87,8 @@ func TestRender_Images(t *testing.T) {
}
func TestTotal_RenderString(t *testing.T) {
const FullURL = AppURL + testRepoOwnerName + "/" + testRepoName + "/"
setting.AppURL = AppURL
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
// Test cases without ambiguous links (It is not right to copy a whole file here, instead it should clearly test what is being tested)
@@ -258,7 +259,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
},
})
for i := range sameCases {
line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i])
line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas).WithEnableHeadingIDGeneration(true), sameCases[i])
assert.NoError(t, err)
assert.Equal(t, testAnswers[i], string(line))
}
@@ -428,9 +429,12 @@ test
---
test
`,
`- item1
- item2
`<hr/>
<ul>
<li>item1</li>
<li>item2</li>
</ul>
<hr/>
<p>test</p>
`,
},
@@ -442,8 +446,8 @@ anything
---
test
`,
`anything
`<hr/>
<h2>anything</h2>
<p>test</p>
`,
},
@@ -470,18 +474,33 @@ foo: bar
</details><ul>
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
</ul>
`,
},
// we have our own frontmatter parser, don't need to use github.com/yuin/goldmark-meta
{
"InvalidFrontmatter",
`---
foo
`,
`<hr/>
<p>foo</p>
`,
},
}
for _, test := range testcases {
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
res, err := markdown.RenderString(markup.NewTestRenderContext(), tt.input)
assert.NoError(t, err, "Unexpected error in testcase: %q", tt.name)
assert.Equal(t, tt.expected, string(res), "Unexpected result in testcase %q", tt.name)
})
}
}
func TestRenderLinks(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, AppURL)()
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
input := ` space @mention-user${SPACE}${SPACE}
/just/a/path.bin
https://example.com/file.bin
@@ -519,9 +538,9 @@ mail@domain.com
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a>
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a>
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span>
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
@@ -529,10 +548,21 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
#123
space</p>
`
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input)
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
t.Run("LocalCommitAndCompare", func(t *testing.T) {
input := `http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash`
expected := `<p><a href="http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
<a href="http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a></p>
`
result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input)
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
})
}
func TestMarkdownLink(t *testing.T) {
@@ -545,5 +575,28 @@ func TestMarkdownLink(t *testing.T) {
assert.Equal(t, `<p><a href="/base/foo" rel="nofollow">link1</a>
<a href="/base/foo" rel="nofollow">link2</a>
<a href="#user-content-foo" rel="nofollow">link3</a></p>
`, string(result))
input = "https://example.com/__init__.py"
result, err = markdown.RenderString(markup.NewTestRenderContext("/base", localMetas), input)
assert.NoError(t, err)
assert.Equal(t, `<p><a href="https://example.com/__init__.py" rel="nofollow">https://example.com/__init__.py</a></p>
`, string(result))
}
func TestMarkdownUlDir(t *testing.T) {
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, false)()
result, err := markdown.RenderString(markup.NewTestRenderContext(), `
* a
* b
`)
assert.NoError(t, err)
assert.Equal(t, `<ul dir="auto">
<li>a
<ul>
<li>b</li>
</ul>
</li>
</ul>
`, string(result))
}
@@ -60,8 +60,8 @@ func ExtractMetadata(contents string, out any) (string, error) {
return string(body), err
}
// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
// and returns the frontmatter metadata separated from the markdown content
// ExtractMetadataBytes consumes a Markdown content, parses YAML frontmatter,
// and returns the frontmatter metadata separated from the Markdown content
func ExtractMetadataBytes(contents []byte, out any) ([]byte, error) {
var front, body []byte
@@ -1,59 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markdown
import (
"bytes"
"fmt"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast"
)
type prefixedIDs struct {
values container.Set[string]
}
// Generate generates a new element id.
func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte {
dft := []byte("id")
if kind == ast.KindHeading {
dft = []byte("heading")
}
return p.GenerateWithDefault(value, dft)
}
// GenerateWithDefault generates a new element id.
func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
result := common.CleanValue(value)
if len(result) == 0 {
result = dft
}
if !bytes.HasPrefix(result, []byte("user-content-")) {
result = append([]byte("user-content-"), result...)
}
if p.values.Add(util.UnsafeBytesToString(result)) {
return result
}
for i := 1; ; i++ {
newResult := fmt.Sprintf("%s-%d", result, i)
if p.values.Add(newResult) {
return []byte(newResult)
}
}
}
// Put puts a given element id to the used ids table.
func (p *prefixedIDs) Put(value []byte) {
p.values.Add(util.UnsafeBytesToString(value))
}
func newPrefixedIDs() *prefixedIDs {
return &prefixedIDs{
values: make(container.Set[string]),
}
}
@@ -1,59 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markdown
import (
"net/url"
"code.gitea.io/gitea/modules/translation"
"github.com/yuin/goldmark/ast"
)
// Header holds the data about a header.
type Header struct {
Level int
Text string
ID string
}
func createTOCNode(toc []Header, lang string, detailsAttrs map[string]string) ast.Node {
details := NewDetails()
summary := NewSummary()
for k, v := range detailsAttrs {
details.SetAttributeString(k, []byte(v))
}
summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).TrString("toc"))))
details.AppendChild(details, summary)
ul := ast.NewList('-')
details.AppendChild(details, ul)
currentLevel := 6
for _, header := range toc {
if header.Level < currentLevel {
currentLevel = header.Level
}
}
for _, header := range toc {
for currentLevel > header.Level {
ul = ul.Parent().(*ast.List)
currentLevel--
}
for currentLevel < header.Level {
newL := ast.NewList('-')
ul.AppendChild(ul, newL)
currentLevel++
ul = newL
}
li := ast.NewListItem(currentLevel * 2)
a := ast.NewLink()
a.Destination = []byte("#" + url.QueryEscape(header.ID))
a.AppendChild(a, ast.NewString([]byte(header.Text)))
li.AppendChild(li, a)
ul.AppendChild(ul, li)
}
return details
}
@@ -1,32 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markdown
import (
"fmt"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
)
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]Header) {
for _, attr := range v.Attributes() {
if _, ok := attr.Value.([]byte); !ok {
v.SetAttribute(attr.Name, fmt.Appendf(nil, "%v", attr.Value))
}
}
txt := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated
header := Header{
Text: util.UnsafeBytesToString(txt),
Level: v.Level,
}
if id, found := v.AttributeString("id"); found {
header.ID = util.UnsafeBytesToString(id.([]byte))
}
*tocList = append(*tocList, header)
g.applyElementDir(v)
}
@@ -81,5 +81,16 @@ func (g *ASTTransformer) transformList(_ *markup.RenderContext, v *ast.List, rc
v.AppendChild(v, newChild)
}
}
g.applyElementDir(v)
nestedList := false
for p := v.Parent(); p != nil; p = p.Parent() {
if _, ok := p.(*ast.List); ok {
nestedList = true
break
}
}
if !nestedList {
// "dir=auto" should be only added to top-level "ul". https://github.com/go-gitea/gitea/issues/35058
g.applyElementDir(v)
}
}