From 51879941dc80e6521eb650d06d17edaca53f6060 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 7 Nov 2026 22:43:07 +0000 Subject: [PATCH] enhance: improve commit and compare link rendering (#40154) Commit, compare and SHA links render the SHA in code with the rest of the URL after it, and links to other repositories get an `owner/repo@` prefix like references do. - Abbreviated SHAs, branch compares and `` autolinks now render as commit links - Pull request commit URLs no longer break into an issue reference plus stray text - Only routes that take a SHA are shortened, so file and API URLs stay plain Replaces https://github.com/go-gitea/gitea/pull/36725 Commit links before and after --- modules/markup/html.go | 35 ++-- modules/markup/html_commit.go | 211 ++++++----------------- modules/markup/html_internal_test.go | 66 +------ modules/markup/html_issue.go | 19 +- modules/markup/html_test.go | 55 ++++-- modules/markup/markdown/markdown_test.go | 4 +- modules/templates/util_render_test.go | 8 +- 7 files changed, 121 insertions(+), 277 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index abef1cdf287..41a8590a5d9 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -30,15 +30,14 @@ const ( ) type globalVarsType struct { - hashCurrentPattern *regexp.Regexp - shortLinkPattern *regexp.Regexp - anyHashPattern *regexp.Regexp - comparePattern *regexp.Regexp - emailRegex *regexp.Regexp - emojiShortCodeRegex *regexp.Regexp - issueFullPattern *regexp.Regexp - filesChangedFullPattern *regexp.Regexp - codePreviewPattern *regexp.Regexp + hashCurrentPattern *regexp.Regexp + shortLinkPattern *regexp.Regexp + compareURLPattern *regexp.Regexp + commitURLPattern *regexp.Regexp + emailRegex *regexp.Regexp + emojiShortCodeRegex *regexp.Regexp + issueFullPattern *regexp.Regexp + codePreviewPattern *regexp.Regexp tagCleaner *regexp.Regexp nulCleaner *strings.Replacer @@ -61,11 +60,9 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax v.shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`) - // anyHashPattern splits url containing SHA into parts - v.anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})((\.\w+)*)(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`) - - // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" - v.comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`) + // repo sub-paths of the compare page and of the routes that take a commit SHA + v.compareURLPattern = regexp.MustCompile(`^/compare/(.+?)(\.\.\.?)(.+?)((?:\.diff|\.patch)?)$`) + v.commitURLPattern = regexp.MustCompile(`^/(?:(?:[a-z-]+/)?commit|blob|archive|pulls/[0-9]+/commits)/([0-9a-f]{7,64})((?:[./].*)?)$`) // emailRegex is definitely not perfect with edge cases, // it is still accepted by the CommonMark specification, as well as the HTML5 spec: @@ -80,9 +77,6 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // example: https://domain/org/repo/pulls/27#hash v.issueFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`) - // example: https://domain/org/repo/pulls/27/files#hash - v.filesChangedFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`) - // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) @@ -102,9 +96,7 @@ type processor func(ctx *RenderContext, node *html.Node) func PostProcessDefault(ctx *RenderContext, input io.Reader, output io.Writer) error { procs := []processor{ fullIssuePatternProcessor, - comparePatternProcessor, codePreviewPatternProcessor, - fullHashPatternProcessor, shortLinkProcessor, linkProcessor, mentionProcessor, @@ -122,8 +114,6 @@ func PostProcessDefault(ctx *RenderContext, input io.Reader, output io.Writer) e func PostProcessCommitMessage(ctx *RenderContext, content template.HTML) template.HTML { procs := []processor{ fullIssuePatternProcessor, - comparePatternProcessor, - fullHashPatternProcessor, linkProcessor, mentionProcessor, issueIndexPatternProcessor, @@ -147,8 +137,6 @@ var emojiProcessors = []processor{ func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content string) template.HTML { procs := []processor{ fullIssuePatternProcessor, - comparePatternProcessor, - fullHashPatternProcessor, mentionProcessor, issueIndexPatternProcessor, commitCrossReferencePatternProcessor, @@ -361,6 +349,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod } if node.Data == "a" { + shortenCommitLink(ctx, node) processNodeA(ctx, node) // only use emoji processors for the content in the "A" tag, // because the content there is not processable, for example: the content is a commit id or a full URL. diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index a9c4852026a..584d58d0352 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -9,6 +9,7 @@ import ( "strings" "gitea.dev/modules/base" + "gitea.dev/modules/git" "gitea.dev/modules/httplib" "gitea.dev/modules/references" @@ -16,176 +17,65 @@ import ( "golang.org/x/net/html/atom" ) -type anyHashPatternResult struct { - PosStart int - PosEnd int - FullURL string - CommitID string - CommitExt string - SubPath string - QueryParams string - QueryHash string +func setCommitLinkContent(anchor *html.Node, prefix, refs, rest string) { + anchor.Attr = append(anchor.Attr, html.Attribute{Key: "class", Val: "commit"}) // plain, so markdown strips it and commit messages keep it for CSS + anchor.FirstChild.Data = prefix + code := &html.Node{Type: html.ElementNode, Data: atom.Code.String()} + code.AppendChild(&html.Node{Type: html.TextNode, Data: refs}) + anchor.AppendChild(code) + if rest != "" { + anchor.AppendChild(&html.Node{Type: html.TextNode, Data: rest}) + } } -func createCodeLink(href, content, class string) *html.Node { - a := &html.Node{ - Type: html.ElementNode, - Data: atom.A.String(), - Attr: []html.Attribute{{Key: "href", Val: href}}, - } - - if class != "" { - a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) - } - - text := &html.Node{ - Type: html.TextNode, - Data: content, - } - - code := &html.Node{ - Type: html.ElementNode, - Data: atom.Code.String(), - } - - code.AppendChild(text) - a.AppendChild(code) - return a +func createCommitLink(ctx *RenderContext, href, prefix, sha string) *html.Node { + anchor := createLink(ctx, href, "", "") + setCommitLinkContent(anchor, prefix, base.ShortSha(sha), "") + return anchor } -func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) { - m := globalVars().anyHashPattern.FindStringSubmatchIndex(s) - if m == nil { - return ret, false +func otherRepoPrefix(ctx *RenderContext, owner, repo string) string { + if strings.EqualFold(owner, ctx.RenderOptions.Metas["user"]) && strings.EqualFold(repo, ctx.RenderOptions.Metas["repo"]) { + return "" } - - pos := 0 - - ret.PosStart, ret.PosEnd = m[pos], m[pos+1] - pos += 2 - - ret.FullURL = s[ret.PosStart:ret.PosEnd] - if strings.HasSuffix(ret.FullURL, ".") { - // if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence. - ret.PosEnd-- - ret.FullURL = ret.FullURL[:len(ret.FullURL)-1] - for i := range m { - m[i] = min(m[i], ret.PosEnd) - } - } - - ret.CommitID = s[m[pos]:m[pos+1]] - pos += 2 - - ret.CommitExt = s[m[pos]:m[pos+1]] - pos += 4 - - if m[pos] > 0 { - ret.SubPath = s[m[pos]:m[pos+1]] - } - pos += 2 - - if m[pos] > 0 { - ret.QueryParams = s[m[pos]:m[pos+1]] - } - pos += 2 - - if m[pos] > 0 { - ret.QueryHash = s[m[pos]:m[pos+1]][1:] - } - return ret, true + return owner + "/" + repo + "@" } -// fullHashPatternProcessor renders SHA containing URLs -func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) { - if ctx.RenderOptions.Metas == nil { +// compare refs can also be branch or tag names, so only full SHAs are shortened +func shortCompareRef(ref string) string { + if git.IsStringValidObjectID(nil, ref) { + return base.ShortSha(ref) + } + return ref +} + +// linkified URLs and "" autolinks reach here as links whose text is their own URL +func shortenCommitLink(ctx *RenderContext, anchor *html.Node) { + text := anchor.FirstChild + if ctx.RenderOptions.Metas == nil || text == nil || text != anchor.LastChild || text.Type != html.TextNode || !strings.Contains(text.Data, "://") { return } - nodeStop := node.NextSibling - for node != nodeStop { - if node.Type != html.TextNode { - node = node.NextSibling - continue - } - ret, ok := anyHashPatternExtract(node.Data) - if !ok { - node = node.NextSibling - continue - } - text := base.ShortSha(ret.CommitID) - if ret.CommitExt != "" { - text += ret.CommitExt - } - if ret.SubPath != "" { - text += ret.SubPath - } - if ret.QueryHash != "" { - text += " (" + ret.QueryHash + ")" - } - // only turn commit links to the current instance into hash link - if !httplib.IsCurrentGiteaSiteURL(ctx, ret.FullURL) { - node = node.NextSibling - continue - } - replaceContent(node, ret.PosStart, ret.PosEnd, createCodeLink(ret.FullURL, text, "commit")) - node = node.NextSibling.NextSibling - } -} - -func comparePatternProcessor(ctx *RenderContext, node *html.Node) { - if ctx.RenderOptions.Metas == nil { + if !slices.ContainsFunc(anchor.Attr, func(attr html.Attribute) bool { return attr.Key == "href" && attr.Val == text.Data }) { return } - nodeStop := node.NextSibling - for node != nodeStop { - if node.Type != html.TextNode { - node = node.NextSibling - continue - } - m := globalVars().comparePattern.FindStringSubmatchIndex(node.Data) - if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match - node = node.NextSibling - continue - } - - urlFull := node.Data[m[0]:m[1]] - text1 := base.ShortSha(node.Data[m[2]:m[3]]) - textDots := base.ShortSha(node.Data[m[4]:m[5]]) - text2 := base.ShortSha(node.Data[m[6]:m[7]]) - - hash := "" - if m[9] > 0 { - hash = node.Data[m[8]:m[9]][1:] - } - - 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] - if hash != "" { - hash = hash[:len(hash)-1] - } else if text2 != "" { - text2 = text2[:len(text2)-1] - } - } - - // only turn compare links to the current instance into hash link - if !httplib.IsCurrentGiteaSiteURL(ctx, urlFull) { - node = node.NextSibling - continue - } - - text := text1 + textDots + text2 - if hash != "" { - text += " (" + hash + ")" - } - replaceContent(node, start, end, createCodeLink(urlFull, text, "compare")) - node = node.NextSibling.NextSibling + siteURL := httplib.ParseGiteaSiteURL(ctx, text.Data) + if siteURL == nil { + return } + var refs, rest string + if match := globalVars().compareURLPattern.FindStringSubmatch(siteURL.RepoSubPath); match != nil { + refs, rest = shortCompareRef(match[1])+match[2]+shortCompareRef(match[3]), match[4] + } else if match := globalVars().commitURLPattern.FindStringSubmatch(siteURL.RepoSubPath); match != nil { + refs, rest = base.ShortSha(match[1]), match[2] + } else { + return + } + if idx := strings.IndexAny(text.Data, "?#"); idx != -1 { + rest += text.Data[idx:] + } + // quoting the link keeps its URL instead of the short text + anchor.Attr = slices.DeleteFunc(anchor.Attr, func(attr html.Attribute) bool { return attr.Key == "data-markdown-generated-content" }) + setCommitLinkContent(anchor, otherRepoPrefix(ctx, siteURL.OwnerName, siteURL.RepoName), refs, rest) } // hashCurrentPatternProcessor renders SHA1 strings to corresponding links that @@ -220,7 +110,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { } link := fmt.Sprintf("/:root/%s/%s/commit/%s", ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], hash) - replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) + replaceContent(node, m[2], m[3], createCommitLink(ctx, link, "", hash)) start = 0 node = node.NextSibling.NextSibling } @@ -235,9 +125,8 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { return } - refText := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) linkHref := fmt.Sprintf("/:root/%s/%s/commit/%s", ref.Owner, ref.Name, ref.CommitSha) - link := createLink(ctx, linkHref, refText, "commit") + link := createCommitLink(ctx, linkHref, otherRepoPrefix(ctx, ref.Owner, ref.Name), ref.CommitSha) replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) node = node.NextSibling.NextSibling diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 248f1066ba2..bb5736cb7dd 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -281,11 +281,6 @@ func TestRender_AutoLink(t *testing.T) { err := PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) - - buffer.Reset() - err = PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) } // render valid issue URLs @@ -296,7 +291,7 @@ func TestRender_AutoLink(t *testing.T) { tmp := TestRepoURL + "commit/d8a994ef243349f321568f9e36d5c3f444b99cae" test(tmp, "d8a994ef24") tmp += "#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + test(tmp, "d8a994ef24#diff-2") // render other commit URLs tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" @@ -355,65 +350,6 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) { } } -func TestRegExp_anySHA1Pattern(t *testing.T) { - testCases := map[string]anyHashPatternResult{ - "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": { - CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1", - SubPath: "/test/unit/event.js", - QueryHash: "L2703", - }, - "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": { - CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1", - SubPath: "/test/unit/event.js", - }, - "https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": { - CommitID: "0705be475092aede1eddae01319ec931fb9c65fc", - }, - "https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": { - CommitID: "0705be475092aede1eddae01319ec931fb9c65fc", - SubPath: "/src", - }, - "https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": { - CommitID: "d8a994ef243349f321568f9e36d5c3f444b99cae", - QueryHash: "diff-2", - }, - "non-url": {}, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b#L1-L2": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - QueryHash: "L1-L2", - }, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678.": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - }, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678/sub.": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - SubPath: "/sub", - }, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b.": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - }, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b&c=d": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - }, - "http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678#hash.": { - CommitID: "1234567812345678123456781234567812345678123456781234567812345678", - QueryHash: "hash", - }, - } - - for k, v := range testCases { - ret, ok := anyHashPatternExtract(k) - if v.CommitID == "" { - assert.False(t, ok) - } else { - assert.Equal(t, strings.TrimSuffix(k, "."), ret.FullURL) - assert.Equal(t, v.CommitID, ret.CommitID) - assert.Equal(t, v.SubPath, ret.SubPath) - assert.Equal(t, v.QueryHash, ret.QueryHash) - } - } -} - func TestRegExp_shortLinkPattern(t *testing.T) { trueTestCases := []string{ "[[stuff]]", diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index e676517cd9b..1aabfffd8d7 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -31,17 +31,23 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { if ctx.RenderOptions.Metas == nil { return } + start := 0 next := node.NextSibling - for node != nil && node != next { - m := globalVars().issueFullPattern.FindStringSubmatchIndex(node.Data) + for node != nil && node != next && start < len(node.Data) { + m := globalVars().issueFullPattern.FindStringSubmatchIndex(node.Data[start:]) if m == nil { return } + for idx := range m { + if m[idx] != -1 { + m[idx] += start + } + } - mDiffView := globalVars().filesChangedFullPattern.FindStringSubmatchIndex(node.Data) - // leave it as it is if the link is from "Files Changed" tab in PR Diff View https://domain/org/repo/pulls/27/files - if mDiffView != nil { - return + // leave links to sub-pages like https://domain/org/repo/pulls/27/files or .../pulls/27/commits/{sha} to the other processors + if m[1] < len(node.Data) && node.Data[m[1]] == '/' { + start = m[1] + continue } link := node.Data[m[0]:m[1]] @@ -71,6 +77,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { text = matchOrg + "/" + matchRepo + text replaceContent(node, m[0], m[1], createLink(ctx, link, text, "ref-issue")) } + start = 0 node = node.NextSibling.NextSibling } } diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 66456c88977..f2ec1572e7b 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -60,14 +60,14 @@ func TestRender_Commits(t *testing.T) { test(sha[:7], `

65f1bf2

`) test(sha[:39], `

65f1bf27bc

`) test(commit, `

65f1bf27bc

`) - test(tree, `

65f1bf27bc/src

`) + test(tree, `

`+tree+`

`) - test(file, `

65f1bf27bc/example.txt

`) - test(fileWithExtra, `

65f1bf27bc/example.txt:

`) - test(fileWithHash, `

65f1bf27bc/example.txt (L2)

`) - test(fileWithHasExtra, `

65f1bf27bc/example.txt (L2):

`) + test(file, `

65f1bf27bc/example.txt

`) + test(fileWithExtra, `

65f1bf27bc/example.txt:

`) + test(fileWithHash, `

65f1bf27bc/example.txt#L2

`) + test(fileWithHasExtra, `

65f1bf27bc/example.txt#L2:

`) test(commitCompare, `

65f1bf27bc...65f1bf27bc

`) - test(commitCompareWithHash, `

65f1bf27bc...65f1bf27bc (L2)

`) + test(commitCompareWithHash, `

65f1bf27bc...65f1bf27bc#L2

`) test("commit "+sha, `

commit 65f1bf27bc

`) test("/home/gitea/"+sha, "

/home/gitea/"+sha+"

") @@ -79,6 +79,32 @@ func TestRender_Commits(t *testing.T) { test(sha[:14]+".", `

`+expected14+`.

`) test(sha[:14]+",", `

`+expected14+`,

`) test("["+sha[:14]+"]", `

[`+expected14+`]

`) + + commitShort := repo + "commit/" + sha[:7] + pullCommit := repo + "pulls/1/commits/" + sha + otherCommit := markup.TestAppURL + "user2/repo1/commit/" + sha + branchCompare := repo + "compare/main...feature/x" + githubCommit := "https://github.com/a/b/commit/" + sha + test(pullCommit+" fixes "+repo+"issues/2", `

65f1bf27bc fixes #2

`) + test(commitShort+"#diff-2.", `

65f1bf2#diff-2.

`) + test(commitShort+"?w=1", `

65f1bf2?w=1

`) + test(otherCommit, `

user2/repo1@65f1bf27bc

`) + test(branchCompare, `

main...feature/x

`) + test(githubCommit+" "+commitShort+" "+commit, `

`+githubCommit+` 65f1bf2 65f1bf27bc

`) + test("<"+commitShort+"> ["+commit+"]("+commit+")", `

65f1bf2 65f1bf27bc

`) + test("[[label|"+commitShort+"]]", `

label

`) + for input, attrs := range map[string]string{ + commitShort + ".patch": ` rel="nofollow">65f1bf2.patch`, + repo + "compare/" + sha + ".." + sha: ` rel="nofollow">65f1bf27bc..65f1bf27bc`, + commitCompare + ".diff?w=1": ` rel="nofollow">65f1bf27bc...65f1bf27bc.diff?w=1`, + repo + "compare/20240101...20240201": ` rel="nofollow">20240101...20240201`, + repo + "src/commit/" + sha + "/README.md": ` rel="nofollow">65f1bf27bc/README.md`, + repo + "commit/" + sha[:6]: ` rel="nofollow">` + repo + "commit/" + sha[:6], + repo + "src/branch/main/sub-dir/commit/" + sha[:7]: ` rel="nofollow">` + repo + "src/branch/main/sub-dir/commit/" + sha[:7], + repo + "src/branch/main/" + sha + ".txt": ` rel="nofollow">` + repo + "src/branch/main/" + sha + ".txt", + } { + test(input, `

`) + } } func TestRender_CrossReferences(t *testing.T) { @@ -113,17 +139,13 @@ func TestRender_CrossReferences(t *testing.T) { inputURL := setting.AppURL + "a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3" test( inputURL, - `

0123456789/foo.txt (L2-L3)

`) + `

a/b@0123456789/foo.txt?a=b#L2-L3

`) - inputURL = setting.AppURL + "repo/owner/archive/0123456789012345678901234567890123456789.tar.gz" - test( - inputURL, - `

0123456789.tar.gz

`) + inputURL = setting.AppURL + "owner/repo/archive/0123456789012345678901234567890123456789.tar.gz?key=val" + test(inputURL, `

owner/repo@0123456789.tar.gz?key=val

`) - inputURL = setting.AppURL + "owner/repo/commit/0123456789012345678901234567890123456789.patch?key=val" - test( - inputURL, - `

0123456789.patch

`) + test("test-owner/test-repo@"+"0123456789012345678901234567890123456789", `

test-owner/test-repo@0123456789

`) + test(testRepoOwnerName+"/"+testRepoName+"@"+"0123456789012345678901234567890123456789", `

0123456789

`) } func TestRender_links(t *testing.T) { @@ -178,6 +200,7 @@ func TestRender_links(t *testing.T) { {"://", `

://

`}, {"www", `

www

`}, {"ftps://gitea.com", `

ftps://gitea.com

`}, + {markup.TestAppURL + "user13/repo11/commit/65f1bf2", `

` + markup.TestAppURL + `user13/repo11/commit/65f1bf2

`}, } { test(testCase.input, testCase.expected) } @@ -459,7 +482,7 @@ func TestIssue18471(t *testing.T) { err := markup.PostProcessDefault(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res) assert.NoError(t, err) - assert.Equal(t, `783b039...da951ce`, res.String()) + assert.Equal(t, `org/repo@783b039...da951ce`, res.String()) } func TestRender_TeamMention(t *testing.T) { diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index e9dd6cb1267..35ae2cff967 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -555,8 +555,8 @@ space

input := `http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash` - expected := `

88fc37a3c0 -88fc37a3c0...12fc37a3c0 (hash)

+ expected := `

user/repo@88fc37a3c0 +user/repo@88fc37a3c0...12fc37a3c0#hash

` result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input) assert.NoError(t, err) diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 8378d2f928d..e1596f800e3 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -102,9 +102,9 @@ func TestRenderRepoComment(t *testing.T) { ![remote image](https://example.com/image.jpg) [[local image|image.jpg]] [[remote link|https://example.com/image.jpg]] -88fc37a3c0...12fc37a3c0 (hash) +user/repo@88fc37a3c0...12fc37a3c0#hash com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare -88fc37a3c0 +user/repo@88fc37a3c0 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 👍 mail@domain.com @@ -226,9 +226,9 @@ func TestRenderMarkdownToHtml(t *testing.T) { remote image local image remote link -88fc37a3c0...12fc37a3c0 (hash) +user/repo@88fc37a3c0...12fc37a3c0#hash com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare -88fc37a3c0 +user/repo@88fc37a3c0 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 👍 mail@domain.com