diff --git a/modules/markup/html.go b/modules/markup/html.go
index 6b5a8e32d4..73ae768d76 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 5cbbf8313c..1022e5af3d 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 835cee3a36..1159e14b37 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