diff --git a/modules/markup/html.go b/modules/markup/html.go index 6b5a8e32d4ef..73ae768d76c0 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -164,6 +164,7 @@ var defaultProcessors = []processor{ linkProcessor, mentionProcessor, issueIndexPatternProcessor, + commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emailAddressProcessor, emojiProcessor, @@ -190,6 +191,7 @@ var commitMessageProcessors = []processor{ linkProcessor, mentionProcessor, issueIndexPatternProcessor, + commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emailAddressProcessor, emojiProcessor, @@ -221,6 +223,7 @@ var commitMessageSubjectProcessors = []processor{ linkProcessor, mentionProcessor, issueIndexPatternProcessor, + commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emojiShortCodeProcessor, emojiProcessor, @@ -257,6 +260,7 @@ func RenderIssueTitle( ) (string, error) { return renderProcessString(ctx, []processor{ issueIndexPatternProcessor, + commitCrossReferencePatternProcessor, sha1CurrentPatternProcessor, emojiShortCodeProcessor, emojiProcessor, @@ -907,6 +911,23 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { } } +func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { + next := node.NextSibling + + for node != nil && node != next { + found, ref := references.FindRenderizableCommitCrossReference(node.Data) + if !found { + return + } + + reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) + link := createLink(util.URLJoin(setting.AppSubURL, ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") + + replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) + node = node.NextSibling.NextSibling + } +} + // fullSha1PatternProcessor renders SHA containing URLs func fullSha1PatternProcessor(ctx *RenderContext, node *html.Node) { if ctx.Metas == nil { diff --git a/modules/references/references.go b/modules/references/references.go index 5cbbf8313c94..1022e5af3d17 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -37,6 +37,9 @@ var ( // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. gogits/gogs#12345 crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) + // crossReferenceCommitPattern matches a string that references a commit in a different repository + // e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters) + crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,40})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) // spaceTrimmedPattern let's us find the trailing space spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`) // timeLogPattern matches string for time tracking @@ -92,6 +95,7 @@ type RenderizableReference struct { Issue string Owner string Name string + CommitSha string IsPull bool RefLocation *RefSpan Action XRefAction @@ -350,6 +354,21 @@ func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *Rende } } +// FindRenderizableCommitCrossReference returns the first unvalidated commit cross reference found in a string. +func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableReference) { + m := crossReferenceCommitPattern.FindStringSubmatchIndex(content) + if len(m) < 8 { + return false, nil + } + + return true, &RenderizableReference{ + Owner: content[m[2]:m[3]], + Name: content[m[4]:m[5]], + CommitSha: content[m[6]:m[7]], + RefLocation: &RefSpan{Start: m[0], End: m[1]}, + } +} + // FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string. func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) { match := pattern.FindStringSubmatchIndex(content) diff --git a/modules/references/references_test.go b/modules/references/references_test.go index 835cee3a3648..1159e14b37ad 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -303,6 +303,67 @@ func TestFindAllMentions(t *testing.T) { }, res) } +func TestFindRenderizableCommitCrossReference(t *testing.T) { + cases := []struct { + Input string + Expected *RenderizableReference + }{ + { + Input: "", + Expected: nil, + }, + { + Input: "test", + Expected: nil, + }, + { + Input: "go-gitea/gitea@test", + Expected: nil, + }, + { + Input: "go-gitea/gitea@ab1234", + Expected: nil, + }, + { + Input: "go-gitea/gitea@abcd1234", + Expected: &RenderizableReference{ + Owner: "go-gitea", + Name: "gitea", + CommitSha: "abcd1234", + RefLocation: &RefSpan{Start: 0, End: 23}, + }, + }, + { + Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd1234", + Expected: &RenderizableReference{ + Owner: "go-gitea", + Name: "gitea", + CommitSha: "abcd1234abcd1234abcd1234abcd1234abcd1234", + RefLocation: &RefSpan{Start: 0, End: 55}, + }, + }, + { + Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd12340", // longer than 40 characters + Expected: nil, + }, + { + Input: "test go-gitea/gitea@abcd1234 test", + Expected: &RenderizableReference{ + Owner: "go-gitea", + Name: "gitea", + CommitSha: "abcd1234", + RefLocation: &RefSpan{Start: 4, End: 29}, + }, + }, + } + + for _, c := range cases { + found, ref := FindRenderizableCommitCrossReference(c.Input) + assert.Equal(t, ref != nil, found) + assert.Equal(t, c.Expected, ref) + } +} + func TestRegExp_mentionPattern(t *testing.T) { trueTestCases := []struct { pat string