mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30: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,
 | 
						linkProcessor,
 | 
				
			||||||
	mentionProcessor,
 | 
						mentionProcessor,
 | 
				
			||||||
	issueIndexPatternProcessor,
 | 
						issueIndexPatternProcessor,
 | 
				
			||||||
 | 
						commitCrossReferencePatternProcessor,
 | 
				
			||||||
	sha1CurrentPatternProcessor,
 | 
						sha1CurrentPatternProcessor,
 | 
				
			||||||
	emailAddressProcessor,
 | 
						emailAddressProcessor,
 | 
				
			||||||
	emojiProcessor,
 | 
						emojiProcessor,
 | 
				
			||||||
@@ -190,6 +191,7 @@ var commitMessageProcessors = []processor{
 | 
				
			|||||||
	linkProcessor,
 | 
						linkProcessor,
 | 
				
			||||||
	mentionProcessor,
 | 
						mentionProcessor,
 | 
				
			||||||
	issueIndexPatternProcessor,
 | 
						issueIndexPatternProcessor,
 | 
				
			||||||
 | 
						commitCrossReferencePatternProcessor,
 | 
				
			||||||
	sha1CurrentPatternProcessor,
 | 
						sha1CurrentPatternProcessor,
 | 
				
			||||||
	emailAddressProcessor,
 | 
						emailAddressProcessor,
 | 
				
			||||||
	emojiProcessor,
 | 
						emojiProcessor,
 | 
				
			||||||
@@ -221,6 +223,7 @@ var commitMessageSubjectProcessors = []processor{
 | 
				
			|||||||
	linkProcessor,
 | 
						linkProcessor,
 | 
				
			||||||
	mentionProcessor,
 | 
						mentionProcessor,
 | 
				
			||||||
	issueIndexPatternProcessor,
 | 
						issueIndexPatternProcessor,
 | 
				
			||||||
 | 
						commitCrossReferencePatternProcessor,
 | 
				
			||||||
	sha1CurrentPatternProcessor,
 | 
						sha1CurrentPatternProcessor,
 | 
				
			||||||
	emojiShortCodeProcessor,
 | 
						emojiShortCodeProcessor,
 | 
				
			||||||
	emojiProcessor,
 | 
						emojiProcessor,
 | 
				
			||||||
@@ -257,6 +260,7 @@ func RenderIssueTitle(
 | 
				
			|||||||
) (string, error) {
 | 
					) (string, error) {
 | 
				
			||||||
	return renderProcessString(ctx, []processor{
 | 
						return renderProcessString(ctx, []processor{
 | 
				
			||||||
		issueIndexPatternProcessor,
 | 
							issueIndexPatternProcessor,
 | 
				
			||||||
 | 
							commitCrossReferencePatternProcessor,
 | 
				
			||||||
		sha1CurrentPatternProcessor,
 | 
							sha1CurrentPatternProcessor,
 | 
				
			||||||
		emojiShortCodeProcessor,
 | 
							emojiShortCodeProcessor,
 | 
				
			||||||
		emojiProcessor,
 | 
							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
 | 
					// fullSha1PatternProcessor renders SHA containing URLs
 | 
				
			||||||
func fullSha1PatternProcessor(ctx *RenderContext, node *html.Node) {
 | 
					func fullSha1PatternProcessor(ctx *RenderContext, node *html.Node) {
 | 
				
			||||||
	if ctx.Metas == nil {
 | 
						if ctx.Metas == nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,9 @@ var (
 | 
				
			|||||||
	// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
 | 
						// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
 | 
				
			||||||
	// e.g. gogits/gogs#12345
 | 
						// e.g. gogits/gogs#12345
 | 
				
			||||||
	crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
 | 
						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 let's us find the trailing space
 | 
				
			||||||
	spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`)
 | 
						spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`)
 | 
				
			||||||
	// timeLogPattern matches string for time tracking
 | 
						// timeLogPattern matches string for time tracking
 | 
				
			||||||
@@ -92,6 +95,7 @@ type RenderizableReference struct {
 | 
				
			|||||||
	Issue          string
 | 
						Issue          string
 | 
				
			||||||
	Owner          string
 | 
						Owner          string
 | 
				
			||||||
	Name           string
 | 
						Name           string
 | 
				
			||||||
 | 
						CommitSha      string
 | 
				
			||||||
	IsPull         bool
 | 
						IsPull         bool
 | 
				
			||||||
	RefLocation    *RefSpan
 | 
						RefLocation    *RefSpan
 | 
				
			||||||
	Action         XRefAction
 | 
						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.
 | 
					// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string.
 | 
				
			||||||
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
 | 
					func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
 | 
				
			||||||
	match := pattern.FindStringSubmatchIndex(content)
 | 
						match := pattern.FindStringSubmatchIndex(content)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -303,6 +303,67 @@ func TestFindAllMentions(t *testing.T) {
 | 
				
			|||||||
	}, res)
 | 
						}, 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) {
 | 
					func TestRegExp_mentionPattern(t *testing.T) {
 | 
				
			||||||
	trueTestCases := []struct {
 | 
						trueTestCases := []struct {
 | 
				
			||||||
		pat string
 | 
							pat string
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user