mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	Add support for commit cross references (#22645)
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>
This commit is contained in:
		@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user