diff --git a/app/models/commit.rb b/app/models/commit.rb
index 085f4e6398f97803842a7ae57d3f3d6f7b3e44a2..2c244fc0410f959c409535bc9b14318ca529a8b7 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -62,6 +62,19 @@ class Commit
     (self.class === other) && (raw == other.raw)
   end
 
+  def self.reference_prefix
+    '@'
+  end
+
+  # Pattern used to extract commit references from text
+  #
+  # The SHA can be between 6 and 40 hex characters.
+  #
+  # This pattern supports cross-project references.
+  def self.reference_pattern
+    %r{(?:#{Project.reference_pattern}#{reference_prefix})?(?<commit>\h{6,40})}
+  end
+
   def to_reference(from_project = nil)
     if cross_project_reference?(from_project)
       "#{project.to_reference}@#{id}"
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index fb1f6d09be6817d750f4089bf079fceadcb07bd1..86fc9eb01a3e0077ce1a20a289c1a37474c109db 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -29,10 +29,24 @@ class CommitRange
   # See `exclude_start?`
   attr_reader :exclude_start
 
-  # The beginning and ending SHA sums can be between 6 and 40 hex characters,
-  # and the range selection can be double- or triple-dot.
+  # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+  # the range notation can be double- or triple-dot.
   PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
 
+  def self.reference_prefix
+    '@'
+  end
+
+  # Pattern used to extract commit range references from text
+  #
+  # This pattern supports cross-project references.
+  def self.reference_pattern
+    %r{
+      (?:#{Project.reference_pattern}#{reference_prefix})?
+      (?<commit_range>#{PATTERN})
+    }x
+  end
+
   # Initialize a CommitRange
   #
   # range_string - The String commit range.
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index b41df301c3f4f7d8d9d7c54e4505065820905e01..e3c1c6d268efe483a2cc2ce1c86d008d60704ab5 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -35,6 +35,16 @@ module Referable
     def reference_prefix
       ''
     end
+
+    # Regexp pattern used to match references to this object
+    #
+    # This must be overridden by the including class.
+    #
+    # Returns Regexp
+    def reference_pattern
+      raise NotImplementedError,
+        %Q{#{self} does not implement "reference_pattern"}
+    end
   end
 
   private
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 6fda4a2ab775e35ec414b4e0d7229d0450acab10..49f6c95e04586bf2e2d82a8861ed83ed72e9f43b 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -9,10 +9,6 @@ class ExternalIssue
     @issue_identifier.to_s
   end
 
-  def to_reference(_from_project = nil)
-    id
-  end
-
   def id
     @issue_identifier.to_s
   end
@@ -32,4 +28,13 @@ class ExternalIssue
   def project
     @project
   end
+
+  # Pattern used to extract `JIRA-123` issue references from text
+  def self.reference_pattern
+    %r{(?<issue>([A-Z\-]+-)\d+)}
+  end
+
+  def to_reference(_from_project = nil)
+    id
+  end
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index 33d72e0d9ee2654604b7102be1eee8d84ee0bbc2..b4e908c5602d23b577ee4ac1c9362627c4f06b70 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -40,7 +40,11 @@ class Group < Namespace
     end
 
     def reference_prefix
-      '@'
+      User.reference_prefix
+    end
+
+    def reference_pattern
+      User.reference_pattern
     end
   end
 
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 31803b57b3f31058cd7a60ee240bc2afd79c9a0b..ea6b9329b07431592de768a2c9a916fb4f62ada6 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -50,12 +50,22 @@ class Issue < ActiveRecord::Base
     state :closed
   end
 
+  def hook_attrs
+    attributes
+  end
+
   def self.reference_prefix
     '#'
   end
 
-  def hook_attrs
-    attributes
+  # Pattern used to extract `#123` issue references from text
+  #
+  # This pattern supports cross-project references.
+  def self.reference_pattern
+    %r{
+      #{Project.reference_pattern}?
+      #{Regexp.escape(reference_prefix)}(?<issue>\d+)
+    }x
   end
 
   def to_reference(from_project = nil)
diff --git a/app/models/label.rb b/app/models/label.rb
index 013e6bf5978aa62bae4cde92cd281e6c0f9d671b..8980049cef88f1cf699714cb2938c4c693ec244b 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -40,6 +40,22 @@ class Label < ActiveRecord::Base
     '~'
   end
 
+  # Pattern used to extract label references from text
+  #
+  # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad).
+  def self.reference_pattern
+    %r{
+      #{reference_prefix}
+      (?:
+        (?<label_id>\d+)   | # Integer-based label ID, or
+        (?<label_name>
+          [A-Za-z0-9_-]+   | # String-based single-word label title
+          ['"][^&\?,]+['"]   # String-based multi-word label surrounded in quotes
+        )
+      )
+    }x
+  end
+
   # Returns the String necessary to reference this Label in Markdown
   #
   # format - Symbol format to use (default: :id, optional: :name)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 60b0ce6c018166c9d8372f3598b1839e1c35a34f..6c90d09b8662adfa83481393384c455aa9a14b92 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -140,6 +140,16 @@ class MergeRequest < ActiveRecord::Base
     '!'
   end
 
+  # Pattern used to extract `!123` merge request references from text
+  #
+  # This pattern supports cross-project references.
+  def self.reference_pattern
+    %r{
+      #{Project.reference_pattern}?
+      #{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
+    }x
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
diff --git a/app/models/project.rb b/app/models/project.rb
index c943114449aa051a21c076a2056809f8e9a8cbbe..3c9f0dad28bc550dc152cc524c273657bbbd3ffe 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -248,6 +248,11 @@ class Project < ActiveRecord::Base
         order_by(method)
       end
     end
+
+    def reference_pattern
+      name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+      %r{(?<project>#{name_pattern}/#{name_pattern})}
+    end
   end
 
   def team
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 8c3167833aaeb6314f95f7b858c9ac240f300fed..d1619071f4908f6defa8845c7fe063ee0de2693b 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -56,6 +56,16 @@ class Snippet < ActiveRecord::Base
     '$'
   end
 
+  # Pattern used to extract `$123` snippet references from text
+  #
+  # This pattern supports cross-project references.
+  def self.reference_pattern
+    %r{
+      #{Project.reference_pattern}?
+      #{Regexp.escape(reference_prefix)}(?<snippet>\d+)
+    }x
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{id}"
 
diff --git a/app/models/user.rb b/app/models/user.rb
index f546dc015c2c2db7c95c8e34f201c56977f9b648..50ca4bc5acc5941eb5fc914fc94cf39c4cb5f2fb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -253,6 +253,14 @@ class User < ActiveRecord::Base
     def reference_prefix
       '@'
     end
+
+    # Pattern used to extract `@user` user references from text
+    def reference_pattern
+      %r{
+        #{Regexp.escape(reference_prefix)}
+        (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
+      }x
+    end
   end
 
   #
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index 8764f7e474f4111730b227172c6b06f69c0aea29..61591a9914bdb66db9962c4fa6c1a85c9a828b2b 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -19,7 +19,7 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(COMMIT_RANGE_PATTERN) do |match|
+        text.gsub(CommitRange.reference_pattern) do |match|
           yield match, $~[:commit_range], $~[:project]
         end
       end
@@ -30,13 +30,8 @@ module Gitlab
         @commit_map = {}
       end
 
-      # Pattern used to extract commit range references from text
-      #
-      # This pattern supports cross-project references.
-      COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>#{CommitRange::PATTERN})/
-
       def call
-        replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content|
+        replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
           commit_range_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index b20b29f5d0ca07b7736242cf8659d926c9c16b00..f6932e76e7023eb35cfcd78ca45ba49de25f2239 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -19,20 +19,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(COMMIT_PATTERN) do |match|
+        text.gsub(Commit.reference_pattern) do |match|
           yield match, $~[:commit], $~[:project]
         end
       end
 
-      # Pattern used to extract commit references from text
-      #
-      # The SHA1 sum can be between 6 and 40 hex characters.
-      #
-      # This pattern supports cross-project references.
-      COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit>\h{6,40})/
-
       def call
-        replace_text_nodes_matching(COMMIT_PATTERN) do |content|
+        replace_text_nodes_matching(Commit.reference_pattern) do |content|
           commit_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
index c436fabd658397e1e786655bb64636044b43f604..66c256c5104ccd90d3707874aaf856da1d8eb932 100644
--- a/lib/gitlab/markdown/cross_project_reference.rb
+++ b/lib/gitlab/markdown/cross_project_reference.rb
@@ -3,9 +3,6 @@ module Gitlab
     # Common methods for ReferenceFilters that support an optional cross-project
     # reference.
     module CrossProjectReference
-      NAMING_PATTERN  = Gitlab::Regex::NAMESPACE_REGEX_STR
-      PROJECT_PATTERN = "(?<project>#{NAMING_PATTERN}/#{NAMING_PATTERN})"
-
       # Given a cross-project reference string, get the Project record
       #
       # Defaults to value of `context[:project]` if:
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index 0fc3f4cca06915bf835548bf6b1789d53ff39b1f..2e74c6e45e21e4c52ea9720e86c8ceed53da1494 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -16,19 +16,16 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(ISSUE_PATTERN) do |match|
+        text.gsub(ExternalIssue.reference_pattern) do |match|
           yield match, $~[:issue]
         end
       end
 
-      # Pattern used to extract `JIRA-123` issue references from text
-      ISSUE_PATTERN = /(?<issue>([A-Z\-]+-)\d+)/
-
       def call
         # Early return if the project isn't using an external tracker
         return doc if project.nil? || project.default_issues_tracker?
 
-        replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+        replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
           issue_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
index 1e885615163092b9756417e544bad8af59ee8ea5..2815626e2476e5e0ff9418c71f4ffa92373058fe 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/gitlab/markdown/issue_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(ISSUE_PATTERN) do |match|
+        text.gsub(Issue.reference_pattern) do |match|
           yield match, $~[:issue].to_i, $~[:project]
         end
       end
 
-      # Pattern used to extract `#123` issue references from text
-      #
-      # This pattern supports cross-project references.
-      ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?<issue>([a-zA-Z\-]+-)?\d+)/
-
       def call
-        replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+        replace_text_nodes_matching(Issue.reference_pattern) do |content|
           issue_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index 1a77becee89f3ecaf4c1107c419406eafbc389e8..9f8c85b70127e3f5998c41cfc59502955060d3ad 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -15,26 +15,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(LABEL_PATTERN) do |match|
+        text.gsub(Label.reference_pattern) do |match|
           yield match, $~[:label_id].to_i, $~[:label_name]
         end
       end
 
-      # Pattern used to extract label references from text
-      #
-      # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad).
-      LABEL_PATTERN = %r{
-        ~(
-          (?<label_id>\d+)   | # Integer-based label ID, or
-          (?<label_name>
-            [A-Za-z0-9_-]+   | # String-based single-word label title
-            ['"][^&\?,]+['"]   # String-based multi-word label surrounded in quotes
-          )
-        )
-      }x
-
       def call
-        replace_text_nodes_matching(LABEL_PATTERN) do |content|
+        replace_text_nodes_matching(Label.reference_pattern) do |content|
           label_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index 740d72abb3699025c6cdaf25fbb01c5764784d60..fddc050635f4abbc9ce7a2ba517d3dafa7027e2d 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(MERGE_REQUEST_PATTERN) do |match|
+        text.gsub(MergeRequest.reference_pattern) do |match|
           yield match, $~[:merge_request].to_i, $~[:project]
         end
       end
 
-      # Pattern used to extract `!123` merge request references from text
-      #
-      # This pattern supports cross-project references.
-      MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?<merge_request>\d+)/
-
       def call
-        replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content|
+        replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
           merge_request_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
index 64a0a2696f7587867fc4d06024695d64223d252e..f22f08de27c1985a4a6986df0e2da72eb83dc70b 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/gitlab/markdown/snippet_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(SNIPPET_PATTERN) do |match|
+        text.gsub(Snippet.reference_pattern) do |match|
           yield match, $~[:snippet].to_i, $~[:project]
         end
       end
 
-      # Pattern used to extract `$123` snippet references from text
-      #
-      # This pattern supports cross-project references.
-      SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?<snippet>\d+)/
-
       def call
-        replace_text_nodes_matching(SNIPPET_PATTERN) do |content|
+        replace_text_nodes_matching(Snippet.reference_pattern) do |content|
           snippet_link_filter(content)
         end
       end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index 28ec041b1d49735855f5380853474a393e6693af..ca7fd7b03389cff3a7164a9e40782b28e56ac635 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -16,16 +16,13 @@ module Gitlab
       #
       # Returns a String replaced with the return of the block.
       def self.references_in(text)
-        text.gsub(USER_PATTERN) do |match|
+        text.gsub(User.reference_pattern) do |match|
           yield match, $~[:user]
         end
       end
 
-      # Pattern used to extract `@user` user references from text
-      USER_PATTERN = /@(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})/
-
       def call
-        replace_text_nodes_matching(USER_PATTERN) do |content|
+        replace_text_nodes_matching(User.reference_pattern) do |content|
           user_link_filter(content)
         end
       end