Skip to content

Commit

Permalink
Add support for commit cross references (#22645)
Browse files Browse the repository at this point in the history
Fixes #22628

This PR adds cross references for commits by using the format
`owner/repo@commit` . References are rendered like
[go-gitea/lgtm@6fe88302](#dummy).

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
  • Loading branch information
KN4CK3R and lunny committed Jan 30, 2023
1 parent 3ff5a6a commit d0d257b
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 0 deletions.
21 changes: 21 additions & 0 deletions modules/markup/html.go
Expand Up @@ -164,6 +164,7 @@ var defaultProcessors = []processor{
linkProcessor,
mentionProcessor,
issueIndexPatternProcessor,
commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor,
emailAddressProcessor,
emojiProcessor,
Expand All @@ -190,6 +191,7 @@ var commitMessageProcessors = []processor{
linkProcessor,
mentionProcessor,
issueIndexPatternProcessor,
commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor,
emailAddressProcessor,
emojiProcessor,
Expand Down Expand Up @@ -221,6 +223,7 @@ var commitMessageSubjectProcessors = []processor{
linkProcessor,
mentionProcessor,
issueIndexPatternProcessor,
commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor,
emojiShortCodeProcessor,
emojiProcessor,
Expand Down Expand Up @@ -257,6 +260,7 @@ func RenderIssueTitle(
) (string, error) {
return renderProcessString(ctx, []processor{
issueIndexPatternProcessor,
commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor,
emojiShortCodeProcessor,
emojiProcessor,
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 19 additions & 0 deletions modules/references/references.go
Expand Up @@ -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
Expand Down Expand Up @@ -92,6 +95,7 @@ type RenderizableReference struct {
Issue string
Owner string
Name string
CommitSha string
IsPull bool
RefLocation *RefSpan
Action XRefAction
Expand Down Expand Up @@ -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)
Expand Down
61 changes: 61 additions & 0 deletions modules/references/references_test.go
Expand Up @@ -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
Expand Down

0 comments on commit d0d257b

Please sign in to comment.