From 51879941dc80e6521eb650d06d17edaca53f6060 Mon Sep 17 00:00:00 2001
From: silverwind commit /home/gitea/"+sha+" `+expected14+`. `+expected14+`, [`+expected14+`] `+githubCommit+` a/b@ owner/repo@ test-owner/test-repo@ :// www ftps://gitea.com ` + markup.TestAppURL + `user13/repo11/commit/65f1bf2
---
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 "
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], ``)
test(sha[:39], ``)
test(commit, ``)
- test(tree, ``)
+ test(tree, ``)
- test(file, ``)
- test(fileWithExtra, ``)
- test(fileWithHash, ``)
- test(fileWithHasExtra, ``)
+ test(file, ``)
+ test(fileWithExtra, ``)
+ test(fileWithHash, ``)
+ test(fileWithHasExtra, ``)
test(commitCompare, ``)
- test(commitCompareWithHash, ``)
+ test(commitCompareWithHash, ``)
test("commit "+sha, `65f1bf27bc65f1bf27bc fixes #265f1bf2 65f1bf27bc65f1bf2.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?a=b#L2-L30123456789.tar.gz?key=val0123456789783b039...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
88fc37a3c0
-88fc37a3c0...12fc37a3c0 (hash)
user/repo@88fc37a3c0
+user/repo@88fc37a3c0...12fc37a3c0#hash
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) {
-88fc37a3c0...12fc37a3c0 (hash)
+user/repo@88fc37a3c0...12fc37a3c0#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
-88fc37a3c0
+user/repo@88fc37a3c0
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
👍
mail@domain.com