diff --git a/CHANGELOG b/CHANGELOG
index ad15ed43b74416a8739e39f3322826e992f78f0d..7b2f152865645728e0f8defe49a789bbc02286d3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@ v 8.3.0 (unreleased)
   - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
   - Fix 500 error when update group member permission
   - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
+  - Recognize issue/MR/snippet/commit links as references
   - Add ignore whitespace change option to commit view
   - Fire update hook from GitLab
   - Don't show project fork event as "imported"
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 931d52055f8314c63cbd5e3b2976f71e862c030f..e66b9c628c79d4053d927e553acedab6a6f95976 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -87,7 +87,11 @@ module IssuesHelper
   end
 
   def merge_requests_sentence(merge_requests)
-    merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+    # Sorting based on the `!123` or `group/project!123` reference will sort
+    # local merge requests first.
+    merge_requests.map do |merge_request|
+      merge_request.to_reference(@project)
+    end.sort.to_sentence(last_word_connector: ', or ')
   end
 
   def url_to_emoji(name)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 7f3a61a5e386bc7bad385872c3a33dfc82674e28..6c32647594d5a0642d70f52ed57cb93b5989badb 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -39,7 +39,11 @@ module MergeRequestsHelper
   end
 
   def issues_sentence(issues)
-    issues.map(&:to_reference).to_sentence
+    # Sorting based on the `#123` or `group/project#123` reference will sort
+    # local issues first.
+    issues.map do |issue|
+      issue.to_reference(@project)
+    end.sort.to_sentence
   end
 
   def mr_change_branches_path(merge_request)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 492f6be1ce39be2995c71c57ec2dfa0f2ce44c98..c0998a45709191d5c9b34086dca2699f9bc011c8 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -78,11 +78,23 @@ class Commit
     }x
   end
 
+  def self.link_reference_pattern
+    super("commit", /(?<commit>\h{6,40})/)
+  end
+
   def to_reference(from_project = nil)
     if cross_project_reference?(from_project)
-      "#{project.to_reference}@#{id}"
+      project.to_reference + self.class.reference_prefix + self.id
+    else
+      self.id
+    end
+  end
+
+  def reference_link_text(from_project = nil)
+    if cross_project_reference?(from_project)
+      project.to_reference + self.class.reference_prefix + self.short_id
     else
-      id
+      self.short_id
     end
   end
 
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 86fc9eb01a3e0077ce1a20a289c1a37474c109db..14e7971fa066175d79e5cdff056158ce5687698e 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -2,36 +2,38 @@
 #
 # Examples:
 #
-#   range = CommitRange.new('f3f85602...e86e1013')
+#   range = CommitRange.new('f3f85602...e86e1013', project)
 #   range.exclude_start?  # => false
 #   range.reference_title # => "Commits f3f85602 through e86e1013"
 #   range.to_s            # => "f3f85602...e86e1013"
 #
-#   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
+#   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
 #   range.exclude_start?  # => true
 #   range.reference_title # => "Commits f3f85602^ through e86e1013"
 #   range.to_param        # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
 #   range.to_s            # => "f3f85602..e86e1013"
 #
-#   # Assuming `project` is a Project with a repository containing both commits:
-#   range.project = project
+#   # Assuming the specified project has a repository containing both commits:
 #   range.valid_commits? # => true
 #
 class CommitRange
   include ActiveModel::Conversion
   include Referable
 
-  attr_reader :sha_from, :notation, :sha_to
+  attr_reader :commit_from, :notation, :commit_to
+  attr_reader :ref_from, :ref_to
 
   # Optional Project model
   attr_accessor :project
 
-  # See `exclude_start?`
-  attr_reader :exclude_start
-
-  # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+  # The beginning and ending refs can be named or SHAs, and
   # the range notation can be double- or triple-dot.
-  PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+  REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
+  PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
+
+  # In text references, the beginning and ending refs can only be SHAs
+  # between 6 and 40 hex characters.
+  STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
 
   def self.reference_prefix
     '@'
@@ -43,27 +45,40 @@ class CommitRange
   def self.reference_pattern
     %r{
       (?:#{Project.reference_pattern}#{reference_prefix})?
-      (?<commit_range>#{PATTERN})
+      (?<commit_range>#{STRICT_PATTERN})
     }x
   end
 
+  def self.link_reference_pattern
+    super("compare", /(?<commit_range>#{PATTERN})/)
+  end
+
   # Initialize a CommitRange
   #
   # range_string - The String commit range.
   # project      - An optional Project model.
   #
   # Raises ArgumentError if `range_string` does not match `PATTERN`.
-  def initialize(range_string, project = nil)
+  def initialize(range_string, project)
+    @project = project
+
     range_string.strip!
 
-    unless range_string.match(/\A#{PATTERN}\z/)
+    unless range_string =~ /\A#{PATTERN}\z/
       raise ArgumentError, "invalid CommitRange string format: #{range_string}"
     end
 
-    @exclude_start = !range_string.include?('...')
-    @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
+    @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
 
-    @project = project
+    if project.valid_repo?
+      @commit_from = project.commit(@ref_from)
+      @commit_to   = project.commit(@ref_to)
+    end
+
+    if valid_commits?
+      @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
+      @ref_to   = Commit.truncate_sha(sha_to)   if sha_to.start_with?(@ref_to)
+    end
   end
 
   def inspect
@@ -71,15 +86,24 @@ class CommitRange
   end
 
   def to_s
-    "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
+    sha_from + notation + sha_to
   end
 
+  alias_method :id, :to_s
+
   def to_reference(from_project = nil)
-    # Not using to_s because we want the full SHAs
-    reference = sha_from + notation + sha_to
+    if cross_project_reference?(from_project)
+      project.to_reference + self.class.reference_prefix + self.id
+    else
+      self.id
+    end
+  end
+
+  def reference_link_text(from_project = nil)
+    reference = ref_from + notation + ref_to
 
     if cross_project_reference?(from_project)
-      reference = project.to_reference + '@' + reference
+      reference = project.to_reference + self.class.reference_prefix + reference
     end
 
     reference
@@ -87,46 +111,58 @@ class CommitRange
 
   # Returns a String for use in a link's title attribute
   def reference_title
-    "Commits #{suffixed_sha_from} through #{sha_to}"
+    "Commits #{sha_start} through #{sha_to}"
   end
 
   # Return a Hash of parameters for passing to a URL helper
   #
   # See `namespace_project_compare_url`
   def to_param
-    { from: suffixed_sha_from, to: sha_to }
+    { from: sha_start, to: sha_to }
   end
 
   def exclude_start?
-    exclude_start
+    @notation == '..'
   end
 
   # Check if both the starting and ending commit IDs exist in a project's
   # repository
-  #
-  # project - An optional Project to check (default: `project`)
-  def valid_commits?(project = project)
-    return nil   unless project.present?
-    return false unless project.valid_repo?
-
-    commit_from.present? && commit_to.present?
+  def valid_commits?
+    commit_start.present? && commit_end.present?
   end
 
   def persisted?
     true
   end
 
-  def commit_from
-    @commit_from ||= project.repository.commit(suffixed_sha_from)
+  def sha_from
+    return nil unless @commit_from
+
+    @commit_from.id
+  end
+
+  def sha_to
+    return nil unless @commit_to
+
+    @commit_to.id
   end
 
-  def commit_to
-    @commit_to ||= project.repository.commit(sha_to)
+  def sha_start
+    return nil unless sha_from
+
+    exclude_start? ? sha_from + '^' : sha_from
   end
 
-  private
+  def commit_start
+    return nil unless sha_start
 
-  def suffixed_sha_from
-    sha_from + (exclude_start? ? '^' : '')
+    if exclude_start?
+      @commit_start ||= project.commit(sha_start)
+    else
+      commit_from
+    end
   end
+
+  alias_method :sha_end, :sha_to
+  alias_method :commit_end, :commit_to
 end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 193c91f1742940f4c87c1149ca2dec0d325a1c86..634a8d0f2747d3f327aed4eca3e167fbe8040480 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -62,13 +62,18 @@ module Mentionable
     return [] if text.blank?
 
     refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
-    (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
+    refs = (refs.issues + refs.merge_requests + refs.commits)
+
+    # We're using this method instead of Array diffing because that requires
+    # both of the object's `hash` values to be the same, which may not be the
+    # case for otherwise identical Commit objects.
+    refs.reject { |ref| ref == local_reference }
   end
 
   # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
   def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
     refs = referenced_mentionables(author, text)
-    
+
     # We're using this method instead of Array diffing because that requires
     # both of the object's `hash` values to be the same, which may not be the
     # case for otherwise identical Commit objects.
@@ -111,7 +116,7 @@ module Mentionable
     # Only include changed fields that are mentionable
     source.select { |key, val| mentionable.include?(key) }
   end
-  
+
   # Determine whether or not a cross-reference Note has already been created between this Mentionable and
   # the specified target.
   def cross_reference_exists?(target)
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index cced66cc1e4916ef84ea41395ab12aad7a4bacfb..ce064f675ae0387afbbf4a978d8e9baa13e18eb0 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -21,6 +21,10 @@ module Referable
     ''
   end
 
+  def reference_link_text(from_project = nil)
+    to_reference(from_project)
+  end
+
   module ClassMethods
     # The character that prefixes the actual reference identifier
     #
@@ -44,6 +48,25 @@ module Referable
     def reference_pattern
       raise NotImplementedError, "#{self} does not implement #{__method__}"
     end
+
+    def link_reference_pattern(route, pattern)
+      %r{
+        (?<url>
+          #{Regexp.escape(Gitlab.config.gitlab.url)}
+          \/#{Project.reference_pattern}
+          \/#{Regexp.escape(route)}
+          \/#{pattern}
+          (?<path>
+            (\/[a-z0-9_=-]+)*
+          )?
+          (?<query>
+            \?[a-z0-9_=-]+
+            (&[a-z0-9_=-]+)*
+          )?
+          (?<anchor>\#[a-z0-9_-]+)?
+        )
+      }x
+    end
   end
 
   private
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 721831080334cefe53755d5df5d130af734a8931..187b6482b6cdc31b2658761699f9568314ecf8b0 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("issues", /(?<issue>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1b3d6079d2cdeeef0e9d9926a446cfb8276cbb32..2a4aee7e5d95e4501cf7ccee7a13fc4518f9c08b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("merge_requests", /(?<merge_request>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
       issues = commits.flat_map { |c| c.closes_issues(current_user) }
       issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
                   closed_by_message(description))
-      issues.uniq.sort_by(&:id)
+      issues.uniq
     else
       []
     end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b0831982aa799511194ae567a68a54e47e9470ce..f876be7a4c8540ad104e1c086282def4183dd6dc 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("snippets", /(?<snippet>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{id}"
 
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 7e2bc834176a7b832ccf183a4f03cfa09253f074..09c159510cd376651fd741947a06742774873677 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -125,7 +125,7 @@ class SystemNoteService
   # Returns the created Note object
   def self.change_status(noteable, project, author, status, source)
     body = "Status changed to #{status}"
-    body += " by #{source.gfm_reference}" if source
+    body += " by #{source.gfm_reference(project)}" if source
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index aef352029d06d5425d2bee39b108af321c49dfb5..917d5181689d99a75627e1cc79b264f533e9e97a 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,3 @@
 .issue-closed-by-widget
   = icon('check')
-  This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
+  This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 62619241001731fc30d64de0224c83ede6342355..63d8ae174365bc7f6613a109a42671745b573191 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
 Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
 Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
 Settings.gitlab['default_projects_features'] ||= {}
 Settings.gitlab['webhook_timeout'] ||= 10
 Settings.gitlab['max_attachment_size'] ||= 10
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index aeec595782c53fb012dcfc3fa00e67d37db27861..9bef9037ad6dda28ed0480d3a75d7afde0840c9e 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -1,6 +1,12 @@
 module Gitlab
   class ClosingIssueExtractor
-    ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
+    ISSUE_CLOSING_REGEX = begin
+      link_pattern = URI.regexp(%w(http https))
+
+      pattern = Gitlab.config.gitlab.issue_closing_pattern
+      pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
+      Regexp.new(pattern).freeze
+    end
 
     def initialize(project, current_user = nil)
       @extractor = Gitlab::ReferenceExtractor.new(project, current_user)
@@ -9,10 +15,12 @@ module Gitlab
     def closed_by_message(message)
       return [] if message.nil?
 
-      closing_statements = message.scan(ISSUE_CLOSING_REGEX).
-        map { |ref| ref[0] }.join(" ")
+      closing_statements = []
+      message.scan(ISSUE_CLOSING_REGEX) do
+        closing_statements << Regexp.last_match[0]
+      end
 
-      @extractor.analyze(closing_statements)
+      @extractor.analyze(closing_statements.join(" "))
 
       @extractor.issues
     end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index b082bfc434bca43f89185a53e5e72db89015f1a6..886a09f52af7d0b1fb36751a0377f044bc3a02af 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -178,7 +178,6 @@ module Gitlab
         Gitlab::Markdown::SanitizationFilter,
 
         Gitlab::Markdown::UploadLinkFilter,
-        Gitlab::Markdown::RelativeLinkFilter,
         Gitlab::Markdown::EmojiFilter,
         Gitlab::Markdown::TableOfContentsFilter,
         Gitlab::Markdown::AutolinkFilter,
@@ -193,6 +192,8 @@ module Gitlab
         Gitlab::Markdown::CommitReferenceFilter,
         Gitlab::Markdown::LabelReferenceFilter,
 
+        Gitlab::Markdown::RelativeLinkFilter,
+
         Gitlab::Markdown::TaskListFilter
       ]
     end
diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb
index fd5b7eb9332e72715286415138f48740b727dbe3..9488e980c086f22756e2f59eb6ec2fee3d9ff501 100644
--- a/lib/gitlab/markdown/abstract_reference_filter.rb
+++ b/lib/gitlab/markdown/abstract_reference_filter.rb
@@ -2,8 +2,8 @@ require 'gitlab/markdown'
 
 module Gitlab
   module Markdown
-    # Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
-    # All this functionality moved to this class
+    # Issues, Merge Requests, Snippets, Commits and Commit Ranges share
+    # similar functionality in reference filtering.
     class AbstractReferenceFilter < ReferenceFilter
       include CrossProjectReference
 
@@ -26,21 +26,20 @@ module Gitlab
 
       # Public: Find references in text (like `!123` for merge requests)
       #
-      #   AnyReferenceFilter.references_in(text) do |match, object|
-      #     "<a href=...>PREFIX#{object}</a>"
+      #   AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
+      #     object = find_object(project_ref, id)
+      #     "<a href=...>#{object.to_reference}</a>"
       #   end
       #
-      # PREFIX - symbol that detects reference (like ! for merge requests)
-      # object - reference object (snippet, merget request etc)
       # text - String text to search.
       #
-      # Yields the String match, the Integer referenced object ID, and an optional String
-      # of the external project reference.
+      # Yields the String match, the Integer referenced object ID, an optional String
+      # of the external project reference, and all of the matchdata.
       #
       # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(object_class.reference_pattern) do |match|
-          yield match, $~[object_sym].to_i, $~[:project]
+      def self.references_in(text, pattern = object_class.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[object_sym].to_i, $~[:project], $~
         end
       end
 
@@ -61,8 +60,27 @@ module Gitlab
       end
 
       def call
+        # `#123`
         replace_text_nodes_matching(object_class.reference_pattern) do |content|
-          object_link_filter(content)
+          object_link_filter(content, object_class.reference_pattern)
+        end
+
+        # `[Issue](#123)`, which is turned into
+        # `<a href="#123">Issue</a>`
+        replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
+          object_link_filter(link, object_class.reference_pattern, link_text: text)
+        end
+
+        # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
+        # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
+        replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
+          object_link_filter(text, object_class.link_reference_pattern)
+        end
+
+        # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
+        # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
+        replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
+          object_link_filter(link, object_class.link_reference_pattern, link_text: text)
         end
       end
 
@@ -70,30 +88,57 @@ module Gitlab
       # to the referenced object's details page.
       #
       # text - String text to replace references in.
+      # pattern - Reference pattern to match against.
+      # link_text - Original content of the link being replaced.
       #
       # Returns a String with references replaced with links. All links
       # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
-      def object_link_filter(text)
-        references_in(text) do |match, id, project_ref|
+      def object_link_filter(text, pattern, link_text: nil)
+        references_in(text, pattern) do |match, id, project_ref, matches|
           project = project_from_ref(project_ref)
 
           if project && object = find_object(project, id)
-            title = escape_once("#{object_title}: #{object.title}")
+            title = escape_once(object_link_title(object))
             klass = reference_class(object_sym)
-            data  = data_attribute(project: project.id, object_sym => object.id)
-            url = url_for_object(object, project)
+
+            data  = data_attribute(
+              original:     link_text || match,
+              project:      project.id,
+              object_sym => object.id
+            )
+
+            url = matches[:url] if matches.names.include?("url")
+            url ||= url_for_object(object, project)
+
+            text = link_text
+            unless text
+              text = object.reference_link_text(context[:project])
+
+              extras = object_link_text_extras(object, matches)
+              text += " (#{extras.join(", ")})" if extras.any?
+            end
 
             %(<a href="#{url}" #{data}
                  title="#{title}"
-                 class="#{klass}">#{match}</a>)
+                 class="#{klass}">#{text}</a>)
           else
             match
           end
         end
       end
 
-      def object_title
-        object_class.name.titleize
+      def object_link_text_extras(object, matches)
+        extras = []
+
+        if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/
+          extras << "comment #{$1}"
+        end
+
+        extras
+      end
+
+      def object_link_title(object)
+        "#{object_class.name.titleize}: #{object.title}"
       end
     end
   end
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index e070edae0a4c43f7747c4a207955f051ebbe265f..36b3258ef761077974e1d070684bae07d5bf6dd6 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -5,24 +5,14 @@ module Gitlab
     # HTML filter that replaces commit range references with links.
     #
     # This filter supports cross-project references.
-    class CommitRangeReferenceFilter < ReferenceFilter
-      include CrossProjectReference
+    class CommitRangeReferenceFilter < AbstractReferenceFilter
+      def self.object_class
+        CommitRange
+      end
 
-      # Public: Find commit range references in text
-      #
-      #   CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
-      #     "<a href=...>#{commit_range}</a>"
-      #   end
-      #
-      # text - String text to search.
-      #
-      # Yields the String match, the String commit range, and an optional String
-      # of the external project reference.
-      #
-      # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(CommitRange.reference_pattern) do |match|
-          yield match, $~[:commit_range], $~[:project]
+      def self.references_in(text, pattern = CommitRange.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[:commit_range], $~[:project], $~
         end
       end
 
@@ -31,9 +21,9 @@ module Gitlab
         return unless project
 
         id = node.attr("data-commit-range")
-        range = CommitRange.new(id, project)
+        range = find_object(project, id)
 
-        return unless range.valid_commits?
+        return unless range
 
         { commit_range: range }
       end
@@ -44,49 +34,25 @@ module Gitlab
         @commit_map = {}
       end
 
-      def call
-        replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
-          commit_range_link_filter(content)
-        end
-      end
-
-      # Replace commit range references in text with links to compare the commit
-      # ranges.
-      #
-      # text - String text to replace references in.
-      #
-      # Returns a String with commit range references replaced with links. All
-      # links have `gfm` and `gfm-commit_range` class names attached for
-      # styling.
-      def commit_range_link_filter(text)
-        self.class.references_in(text) do |match, id, project_ref|
-          project = self.project_from_ref(project_ref)
-
-          range = CommitRange.new(id, project)
-
-          if range.valid_commits?
-            url = url_for_commit_range(project, range)
-
-            title = range.reference_title
-            klass = reference_class(:commit_range)
-            data  = data_attribute(project: project.id, commit_range: id)
+      def self.find_object(project, id)
+        range = CommitRange.new(id, project)
 
-            project_ref += '@' if project_ref
+        range.valid_commits? ? range : nil
+      end
 
-            %(<a href="#{url}" #{data}
-                 title="#{title}"
-                 class="#{klass}">#{project_ref}#{range}</a>)
-          else
-            match
-          end
-        end
+      def find_object(*args)
+        self.class.find_object(*args)
       end
 
-      def url_for_commit_range(project, range)
+      def url_for_object(range, project)
         h = Gitlab::Application.routes.url_helpers
         h.namespace_project_compare_url(project.namespace, project,
                                         range.to_param.merge(only_path: context[:only_path]))
       end
+
+      def object_link_title(range)
+        range.reference_title
+      end
     end
   end
 end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index 8cdbeb1f9cf7101b0201a65003c125e9f8e5ffcf..b4036578e60ed753f5baa6c16e16ee98a0bab6cd 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -5,24 +5,14 @@ module Gitlab
     # HTML filter that replaces commit references with links.
     #
     # This filter supports cross-project references.
-    class CommitReferenceFilter < ReferenceFilter
-      include CrossProjectReference
+    class CommitReferenceFilter < AbstractReferenceFilter
+      def self.object_class
+        Commit
+      end
 
-      # Public: Find commit references in text
-      #
-      #   CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
-      #     "<a href=...>#{commit}</a>"
-      #   end
-      #
-      # text - String text to search.
-      #
-      # Yields the String match, the String commit identifier, and an optional
-      # String of the external project reference.
-      #
-      # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(Commit.reference_pattern) do |match|
-          yield match, $~[:commit], $~[:project]
+      def self.references_in(text, pattern = Commit.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[:commit], $~[:project], $~
         end
       end
 
@@ -31,58 +21,32 @@ module Gitlab
         return unless project
 
         id = node.attr("data-commit")
-        commit = commit_from_ref(project, id)
+        commit = find_object(project, id)
 
         return unless commit
 
         { commit: commit }
       end
 
-      def call
-        replace_text_nodes_matching(Commit.reference_pattern) do |content|
-          commit_link_filter(content)
-        end
-      end
-
-      # Replace commit references in text with links to the commit specified.
-      #
-      # text - String text to replace references in.
-      #
-      # Returns a String with commit references replaced with links. All links
-      # have `gfm` and `gfm-commit` class names attached for styling.
-      def commit_link_filter(text)
-        self.class.references_in(text) do |match, id, project_ref|
-          project = self.project_from_ref(project_ref)
-
-          if commit = self.class.commit_from_ref(project, id)
-            url = url_for_commit(project, commit)
-
-            title = escape_once(commit.link_title)
-            klass = reference_class(:commit)
-            data  = data_attribute(project: project.id, commit: id)
-
-            project_ref += '@' if project_ref
-
-            %(<a href="#{url}" #{data}
-                 title="#{title}"
-                 class="#{klass}">#{project_ref}#{commit.short_id}</a>)
-          else
-            match
-          end
-        end
-      end
-
-      def self.commit_from_ref(project, id)
+      def self.find_object(project, id)
         if project && project.valid_repo?
           project.commit(id)
         end
       end
 
-      def url_for_commit(project, commit)
+      def find_object(*args)
+        self.class.find_object(*args)
+      end
+
+      def url_for_object(commit, project)
         h = Gitlab::Application.routes.url_helpers
         h.namespace_project_commit_url(project.namespace, project, commit,
                                         only_path: context[:only_path])
       end
+
+      def object_link_title(commit)
+        commit.link_title
+      end
     end
   end
 end
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index 8f86f13976abc847adb81542675fb4177a970c1d..14bdf5521fc5fd1ddae486ba496d323cb75a1c18 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -30,6 +30,10 @@ module Gitlab
         replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
           issue_link_filter(content)
         end
+
+        replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
+          issue_link_filter(link, link_text: text)
+        end
       end
 
       # Replace `JIRA-123` issue references in text with links to the referenced
@@ -39,7 +43,7 @@ module Gitlab
       #
       # Returns a String with `JIRA-123` references replaced with links. All
       # links have `gfm` and `gfm-issue` class names attached for styling.
-      def issue_link_filter(text)
+      def issue_link_filter(text, link_text: nil)
         project = context[:project]
 
         self.class.references_in(text) do |match, issue|
@@ -49,9 +53,11 @@ module Gitlab
           klass = reference_class(:issue)
           data  = data_attribute(project: project.id)
 
+          text = link_text || match
+
           %(<a href="#{url}" #{data}
                title="#{title}"
-               class="#{klass}">#{match}</a>)
+               class="#{klass}">#{text}</a>)
         end
       end
 
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb
index 29e51b6ade672d056230e683490d3d4ea95d6dec..e09dfcb83c87ac8d5cc295ca2e4b81d3b3ff196c 100644
--- a/lib/gitlab/markdown/external_link_filter.rb
+++ b/lib/gitlab/markdown/external_link_filter.rb
@@ -8,9 +8,9 @@ module Gitlab
     class ExternalLinkFilter < HTML::Pipeline::Filter
       def call
         doc.search('a').each do |node|
-          next unless node.has_attribute?('href')
+          link = node.attr('href')
 
-          link = node.attribute('href').value
+          next unless link
 
           # Skip non-HTTP(S) links
           next unless link.start_with?('http')
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index 13581b8fb136671a0c51b8dd74e8e1e2a0b22f65..a2026eecaeb9018488378dc619f99d71dad4037d 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -30,6 +30,10 @@ module Gitlab
         replace_text_nodes_matching(Label.reference_pattern) do |content|
           label_link_filter(content)
         end
+
+        replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
+          label_link_filter(link, link_text: text)
+        end
       end
 
       # Replace label references in text with links to the label specified.
@@ -38,7 +42,7 @@ module Gitlab
       #
       # Returns a String with label references replaced with links. All links
       # have `gfm` and `gfm-label` class names attached for styling.
-      def label_link_filter(text)
+      def label_link_filter(text, link_text: nil)
         project = context[:project]
 
         self.class.references_in(text) do |match, id, name|
@@ -47,10 +51,16 @@ module Gitlab
           if label = project.labels.find_by(params)
             url = url_for_label(project, label)
             klass = reference_class(:label)
-            data = data_attribute(project: project.id, label: label.id)
+            data = data_attribute(
+              original: link_text || match,
+              project: project.id,
+              label: label.id
+            )
+
+            text = link_text || render_colored_label(label)
 
             %(<a href="#{url}" #{data}
-                 class="#{klass}">#{render_colored_label(label)}</a>)
+                 class="#{klass}">#{text}</a>)
           else
             match
           end
@@ -59,8 +69,8 @@ module Gitlab
 
       def url_for_label(project, label)
         h = Gitlab::Application.routes.url_helpers
-        h.namespace_project_issues_path(project.namespace, project,
-                                        label_name: label.name)
+        h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
+                                                                    only_path:  context[:only_path])
       end
 
       def render_colored_label(label)
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index 1f47f03c94ebf5f75041c23b2b80cee7fcc0cb4d..de71fc76a9b3e8f07e108242d6b4b5464ef7a39d 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -20,6 +20,16 @@ module Gitlab
         h.namespace_project_merge_request_url(project.namespace, project, mr,
                                             only_path: context[:only_path])
       end
+
+      def object_link_text_extras(object, matches)
+        extras = super
+
+        if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
+          extras.unshift "diffs"
+        end
+
+        extras
+      end
     end
   end
 end
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb
index a1f3a8a8ebfc180a02fcf64b437947f9f6426e07..bea714a01e75d4d18140ef1db16fd86de3cb5646 100644
--- a/lib/gitlab/markdown/redactor_filter.rb
+++ b/lib/gitlab/markdown/redactor_filter.rb
@@ -12,7 +12,10 @@ module Gitlab
       def call
         doc.css('a.gfm').each do |node|
           unless user_can_reference?(node)
-            node.replace(node.text)
+            # The reference should be replaced by the original text,
+            # which is not always the same as the rendered text.
+            text = node.attr('data-original') || node.text
+            node.replace(text)
           end
         end
 
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index a4c560f578cae024b180fb3d05c0f0dd6e85f215..b6d93e05ec7987ac198679902451b394c319ff86 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -122,6 +122,80 @@ module Gitlab
         doc
       end
 
+      # Iterate through the document's link nodes, yielding the current node's
+      # content if:
+      #
+      # * The `project` context value is present AND
+      # * The node's content matches `pattern`
+      #
+      # pattern - Regex pattern against which to match the node's content
+      #
+      # Yields the current node's String contents. The result of the block will
+      # replace the node and update the current document.
+      #
+      # Returns the updated Nokogiri::HTML::DocumentFragment object.
+      def replace_link_nodes_with_text(pattern)
+        return doc if project.nil?
+
+        doc.search('a').each do |node|
+          klass = node.attr('class')
+          next if klass && klass.include?('gfm')
+
+          link = node.attr('href')
+          text = node.text
+
+          next unless link && text
+
+          link = URI.decode(link)
+          # Ignore ending punctionation like periods or commas
+          next unless link == text && text =~ /\A#{pattern}/
+
+          html = yield text
+
+          next if html == text
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
+      # Iterate through the document's link nodes, yielding the current node's
+      # content if:
+      #
+      # * The `project` context value is present AND
+      # * The node's HREF matches `pattern`
+      #
+      # pattern - Regex pattern against which to match the node's HREF
+      #
+      # Yields the current node's String HREF and String content.
+      # The result of the block will replace the node and update the current document.
+      #
+      # Returns the updated Nokogiri::HTML::DocumentFragment object.
+      def replace_link_nodes_with_href(pattern)
+        return doc if project.nil?
+
+        doc.search('a').each do |node|
+          klass = node.attr('class')
+          next if klass && klass.include?('gfm')
+
+          link = node.attr('href')
+          text = node.text
+
+          next unless link && text
+          link = URI.decode(link)
+          next unless link && link =~ /\A#{pattern}\z/
+
+          html = yield link, text
+
+          next if html == link
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
       # Ensure that a :project key exists in context
       #
       # Note that while the key might exist, its value could be nil!
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
index 632be4d754255bbf4803dbe3c6f2e12ace6a4b9a..692c51fd324ad8783ed9204159c3e259aa278023 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -17,6 +17,9 @@ module Gitlab
         return doc unless linkable_files?
 
         doc.search('a').each do |el|
+          klass = el.attr('class')
+          next if klass && klass.include?('gfm')
+          
           process_link_attr el.attribute('href')
         end
 
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index ab5e1f6fe9eda11e4794b8fb6db570546b1eca26..0a20d9c034729e660e51f1f211307f1b98bd2f76 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -52,6 +52,10 @@ module Gitlab
         replace_text_nodes_matching(User.reference_pattern) do |content|
           user_link_filter(content)
         end
+
+        replace_link_nodes_with_href(User.reference_pattern) do |link, text|
+          user_link_filter(link, link_text: text)
+        end
       end
 
       # Replace `@user` user references in text with links to the referenced
@@ -61,12 +65,12 @@ module Gitlab
       #
       # Returns a String with `@user` references replaced with links. All links
       # have `gfm` and `gfm-project_member` class names attached for styling.
-      def user_link_filter(text)
+      def user_link_filter(text, link_text: nil)
         self.class.references_in(text) do |match, username|
           if username == 'all'
-            link_to_all
+            link_to_all(link_text: link_text)
           elsif namespace = Namespace.find_by(path: username)
-            link_to_namespace(namespace) || match
+            link_to_namespace(namespace, link_text: link_text) || match
           else
             match
           end
@@ -83,36 +87,36 @@ module Gitlab
         reference_class(:project_member)
       end
 
-      def link_to_all
+      def link_to_all(link_text: nil)
         project = context[:project]
         url = urls.namespace_project_url(project.namespace, project,
                                          only_path: context[:only_path])
         data = data_attribute(project: project.id)
-        text = User.reference_prefix + 'all'
+        text = link_text || User.reference_prefix + 'all'
 
         link_tag(url, data, text)
       end
 
-      def link_to_namespace(namespace)
+      def link_to_namespace(namespace, link_text: nil)
         if namespace.is_a?(Group)
-          link_to_group(namespace.path, namespace)
+          link_to_group(namespace.path, namespace, link_text: link_text)
         else
-          link_to_user(namespace.path, namespace)
+          link_to_user(namespace.path, namespace, link_text: link_text)
         end
       end
 
-      def link_to_group(group, namespace)
+      def link_to_group(group, namespace, link_text: nil)
         url = urls.group_url(group, only_path: context[:only_path])
         data = data_attribute(group: namespace.id)
-        text = Group.reference_prefix + group
+        text = link_text || Group.reference_prefix + group
 
         link_tag(url, data, text)
       end
 
-      def link_to_user(user, namespace)
+      def link_to_user(user, namespace, link_text: nil)
         url = urls.user_url(user, only_path: context[:only_path])
         data = data_attribute(user: namespace.owner_id)
-        text = User.reference_prefix + user
+        text = link_text || User.reference_prefix + user
 
         link_tag(url, data, text)
       end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index da8df8a3025550e5a23d73c19c303fd0894464ef..3c3478a12719e9a66c639918a41fe2b2740a6223 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -41,14 +41,14 @@ module Gitlab
     # Returns the results Array for the requested filter type
     def pipeline_result(filter_type)
       return [] if @text.blank?
-      
+
       klass  = "#{filter_type.to_s.camelize}ReferenceFilter"
       filter = Gitlab::Markdown.const_get(klass)
 
       context = {
         project: project,
         current_user: current_user,
-        
+
         # We don't actually care about the links generated
         only_path: true,
         ignore_blockquotes: true,
@@ -58,7 +58,15 @@ module Gitlab
         reference_filter:     filter
       }
 
-      pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
+      # We need to autolink first to finds links to referables, and to prevent
+      # numeric anchors to be parsed as issue references.
+      filters = [
+        Gitlab::Markdown::AutolinkFilter,
+        filter,
+        Gitlab::Markdown::ReferenceGathererFilter
+      ]
+
+      pipeline = HTML::Pipeline.new(filters, context)
       result = pipeline.call(@text)
 
       values = result[:references][filter_type].uniq
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 41d12afa9ce8f41561c7f960f9e87f567bec0a06..e8dfc5c0eb173c5a8677ba7def2e8c09612338c0 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Ignores invalid: <%= User.reference_prefix %>fake_user
 - Ignored in code: `<%= user.to_reference %>`
 - Ignored in links: [Link to <%= user.to_reference %>](#user-link)
+- Link to user by reference: [User](<%= user.to_reference %>)
 
 #### IssueReferenceFilter
 
@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Issue in another project: <%= xissue.to_reference(project) %>
 - Ignored in code: `<%= issue.to_reference %>`
 - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
+- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Link to issue by reference: [Issue](<%= issue.to_reference %>)
+- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
 
 #### MergeRequestReferenceFilter
 
@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Merge request in another project: <%= xmerge_request.to_reference(project) %>
 - Ignored in code: `<%= merge_request.to_reference %>`
 - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
+- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
+- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
 
 #### SnippetReferenceFilter
 
@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Snippet in another project: <%= xsnippet.to_reference(project) %>
 - Ignored in code: `<%= snippet.to_reference %>`
 - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
+- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
+- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
 
 #### CommitRangeReferenceFilter
 
@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Range in another project: <%= xcommit_range.to_reference(project) %>
 - Ignored in code: `<%= commit_range.to_reference %>`
 - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
+- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Link to range by reference: [Range](<%= commit_range.to_reference %>)
+- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
 
 #### CommitReferenceFilter
 
@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Commit in another project: <%= xcommit.to_reference(project) %>
 - Ignored in code: `<%= commit.to_reference %>`
 - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
+- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Link to commit by reference: [Commit](<%= commit.to_reference %>)
+- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
 
 #### LabelReferenceFilter
 
@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Label by name in quotes: <%= label.to_reference(:name) %>
 - Ignored in code: `<%= simple_label.to_reference %>`
 - Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
+- Link to label by reference: [Label](<%= label.to_reference %>)
 
 ### Task Lists
 
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 21254f778d3f39798b1a88f276aea8f35e718926..fe1b94a484e98ec6ca3bfbea73912e45915ed92f 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -2,11 +2,18 @@ require 'spec_helper'
 
 describe Gitlab::ClosingIssueExtractor do
   let(:project)   { create(:project) }
+  let(:project2)   { create(:project) }
   let(:issue)     { create(:issue, project: project) }
+  let(:issue2)     { create(:issue, project: project2) }
   let(:reference) { issue.to_reference }
+  let(:cross_reference) { issue2.to_reference(project) }
 
   subject { described_class.new(project, project.creator) }
 
+  before do
+    project2.team << [project.creator, :master]
+  end
+
   describe "#closed_by_message" do
     context 'with a single reference' do
       it do
@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
       end
     end
 
+    context "with a cross-project reference" do
+      it do
+        message = "Closes #{cross_reference}"
+        expect(subject.closed_by_message(message)).to eq([issue2])
+      end
+    end
+
+    context "with a cross-project URL" do
+      it do
+        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+        expect(subject.closed_by_message(message)).to eq([issue2])
+      end
+    end
+
+    context "with an invalid URL" do
+      it do
+        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+        expect(subject.closed_by_message(message)).to eq([])
+      end
+    end
+
     context 'with multiple references' do
       let(:other_issue) { create(:issue, project: project) }
       let(:third_issue) { create(:issue, project: project) }
@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
         expect(subject.closed_by_message(message)).
             to match_array([issue, other_issue, third_issue])
       end
+
+      it "fetches cross-project references" do
+        message = "Closes #{reference} and #{cross_reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue, issue2])
+      end
+
+      it "fetches cross-project URL references" do
+        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue, issue2])
+      end
+
+      it "ignores invalid cross-project URL references" do
+        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue])
+      end
     end
   end
+
+  def urls
+    Gitlab::Application.routes.url_helpers
+  end
 end
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index e5b8d723fe5614652cc98000511f8250d554d1d7..9ce63f9af464e18f10e155e5903ada0c409397cc 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -5,11 +5,11 @@ module Gitlab::Markdown
     include FilterSpecHelper
 
     let(:project) { create(:project, :public) }
-    let(:commit1) { project.commit }
-    let(:commit2) { project.commit("HEAD~2") }
+    let(:commit1) { project.commit("HEAD~2") }
+    let(:commit2) { project.commit }
 
-    let(:range)  { CommitRange.new("#{commit1.id}...#{commit2.id}") }
-    let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
+    let(:range)  { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
+    let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
 
     it 'requires project context' do
       expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -18,7 +18,7 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -27,14 +27,14 @@ module Gitlab::Markdown
       let(:reference2) { range2.to_reference }
 
       it 'links to a valid two-dot reference' do
-        doc = filter("See #{reference2}")
+        doc = reference_filter("See #{reference2}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
       end
 
       it 'links to a valid three-dot reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
@@ -46,14 +46,14 @@ module Gitlab::Markdown
 
         exp = commit1.short_id + '...' + commit2.short_id
 
-        expect(filter("See #{reference}").css('a').first.text).to eq exp
-        expect(filter("See #{reference2}").css('a').first.text).to eq exp
+        expect(reference_filter("See #{reference}").css('a').first.text).to eq exp
+        expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp
       end
 
       it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
+        doc = reference_filter("See (#{reference}.)")
 
-        exp = Regexp.escape(range.to_s)
+        exp = Regexp.escape(range.reference_link_text)
         expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
       end
 
@@ -62,21 +62,22 @@ module Gitlab::Markdown
 
         expect(project).to receive(:valid_repo?).and_return(true)
         expect(project.repository).to receive(:commit).with(commit1.id.reverse)
-        expect(filter(act).to_html).to eq exp
+        expect(project.repository).to receive(:commit).with(commit2.id)
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'includes a title attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         expect(doc.css('a').first.attr('title')).to eq range.reference_title
       end
 
       it 'includes default classes' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
       end
 
       it 'includes a data-project attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-project')
@@ -84,15 +85,15 @@ module Gitlab::Markdown
       end
 
       it 'includes a data-commit-range attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-commit-range')
-        expect(link.attr('data-commit-range')).to eq range.to_reference
+        expect(link.attr('data-commit-range')).to eq range.to_s
       end
 
       it 'supports an :only_path option' do
-        doc = filter("See #{reference}", only_path: true)
+        doc = reference_filter("See #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
 
         expect(link).not_to match %r(https?://)
@@ -115,25 +116,63 @@ module Gitlab::Markdown
       end
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
+        doc = reference_filter("Fixed (#{reference}.)")
 
-        exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
+        exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
         expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
       end
 
       it 'ignores invalid commit IDs on the referenced project' do
         exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
 
         exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("See #{reference}")
+        expect(result[:references][:commit_range]).not_to be_empty
+      end
+    end
+
+    context 'cross-project URL reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, :public, namespace: namespace) }
+      let(:range)  { CommitRange.new("#{commit1.id}...master", project) }
+      let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
+
+      before do
+        range.project = project2
+      end
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq reference
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Fixed (#{reference}.)")
+
+        exp = Regexp.escape(range.reference_link_text(project))
+        expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid commit IDs on the referenced project' do
+        exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+        expect(reference_filter(act).to_html).to eq exp
+
+        exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index d080efbf3d4fa011a7cea580601322c7120c2189..462a41b47562958a43497a0aa289da384dee2ec4 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -14,7 +14,7 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -24,7 +24,7 @@ module Gitlab::Markdown
       # Let's test a variety of commit SHA sizes just to be paranoid
       [6, 8, 12, 18, 20, 32, 40].each do |size|
         it "links to a valid reference of #{size} characters" do
-          doc = filter("See #{reference[0...size]}")
+          doc = reference_filter("See #{reference[0...size]}")
 
           expect(doc.css('a').first.text).to eq commit.short_id
           expect(doc.css('a').first.attr('href')).
@@ -33,15 +33,15 @@ module Gitlab::Markdown
       end
 
       it 'always uses the short ID as the link text' do
-        doc = filter("See #{commit.id}")
+        doc = reference_filter("See #{commit.id}")
         expect(doc.text).to eq "See #{commit.short_id}"
 
-        doc = filter("See #{commit.id[0...6]}")
+        doc = reference_filter("See #{commit.id[0...6]}")
         expect(doc.text).to eq "See #{commit.short_id}"
       end
 
       it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
+        doc = reference_filter("See (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
       end
 
@@ -51,28 +51,28 @@ module Gitlab::Markdown
 
         expect(project).to receive(:valid_repo?).and_return(true)
         expect(project.repository).to receive(:commit).with(invalid)
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'includes a title attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         expect(doc.css('a').first.attr('title')).to eq commit.link_title
       end
 
       it 'escapes the title attribute' do
         allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
 
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         expect(doc.text).to eq "See #{commit.short_id}"
       end
 
       it 'includes default classes' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
       end
 
       it 'includes a data-project attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-project')
@@ -80,7 +80,7 @@ module Gitlab::Markdown
       end
 
       it 'includes a data-commit attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-commit')
@@ -88,7 +88,7 @@ module Gitlab::Markdown
       end
 
       it 'supports an :only_path context' do
-        doc = filter("See #{reference}", only_path: true)
+        doc = reference_filter("See #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
 
         expect(link).not_to match %r(https?://)
@@ -108,14 +108,14 @@ module Gitlab::Markdown
       let(:reference) { commit.to_reference(project) }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
+        doc = reference_filter("Fixed (#{reference}.)")
 
         exp = Regexp.escape(project2.to_reference)
         expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
@@ -123,7 +123,37 @@ module Gitlab::Markdown
 
       it 'ignores invalid commit IDs on the referenced project' do
         exp = act = "Committed #{invalidate_reference(reference)}"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("See #{reference}")
+        expect(result[:references][:commit]).not_to be_empty
+      end
+    end
+
+    context 'cross-project URL reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, :public, namespace: namespace) }
+      let(:commit)    { project2.commit }
+      let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Fixed (#{reference}.)")
+
+        expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid commit IDs on the referenced project' do
+        act = "Committed #{invalidate_reference(reference)}"
+        expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
       end
 
       it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 94c80ae6611aeb2221ecabc6b57d6971de02887c..078ff3ed4b2de9c43050d00c4b0b34ddded492aa 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -18,7 +18,7 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -29,18 +29,18 @@ module Gitlab::Markdown
         expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
 
         exp = act = "Issue #{reference}"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'links to a valid reference' do
-        doc = filter("Fixed #{reference}")
+        doc = reference_filter("Fixed #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq helper.url_for_issue(issue.iid, project)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
+        doc = reference_filter("Fixed (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
@@ -48,28 +48,28 @@ module Gitlab::Markdown
         invalid = invalidate_reference(reference)
         exp = act = "Fixed #{invalid}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'includes a title attribute' do
-        doc = filter("Issue #{reference}")
+        doc = reference_filter("Issue #{reference}")
         expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
       end
 
       it 'escapes the title attribute' do
         issue.update_attribute(:title, %{"></a>whatever<a title="})
 
-        doc = filter("Issue #{reference}")
+        doc = reference_filter("Issue #{reference}")
         expect(doc.text).to eq "Issue #{reference}"
       end
 
       it 'includes default classes' do
-        doc = filter("Issue #{reference}")
+        doc = reference_filter("Issue #{reference}")
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
       end
 
       it 'includes a data-project attribute' do
-        doc = filter("Issue #{reference}")
+        doc = reference_filter("Issue #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-project')
@@ -77,7 +77,7 @@ module Gitlab::Markdown
       end
 
       it 'includes a data-issue attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-issue')
@@ -85,7 +85,7 @@ module Gitlab::Markdown
       end
 
       it 'supports an :only_path context' do
-        doc = filter("Issue #{reference}", only_path: true)
+        doc = reference_filter("Issue #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
 
         expect(link).not_to match %r(https?://)
@@ -109,25 +109,97 @@ module Gitlab::Markdown
           with(issue.iid).and_return(nil)
 
         exp = act = "Issue #{reference}"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq helper.url_for_issue(issue.iid, project2)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
+        doc = reference_filter("Fixed (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
       it 'ignores invalid issue IDs on the referenced project' do
         exp = act = "Fixed #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Fixed #{reference}")
+        expect(result[:references][:issue]).to eq [issue]
+      end
+    end
+
+    context 'cross-project URL reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+      let(:issue)     { create(:issue, project: project2) }
+      let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq reference
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Fixed (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Fixed #{reference}")
+        expect(result[:references][:issue]).to eq [issue]
+      end
+    end
+
+    context 'cross-project reference in link href' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+      let(:issue)     { create(:issue, project: project2) }
+      let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq helper.url_for_issue(issue.iid, project2)
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Fixed (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Fixed #{reference}")
+        expect(result[:references][:issue]).to eq [issue]
+      end
+    end
+
+    context 'cross-project URL in link href' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+      let(:issue)     { create(:issue, project: project2) }
+      let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Fixed (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
       end
 
       it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index ae286c8be2b2123b9bae9c88f5e6e8f4b3b20e60..ef6dd524aba51efbb71c3243eed03ff527d1376a 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -16,17 +16,17 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Label #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
     it 'includes default classes' do
-      doc = filter("Label #{reference}")
+      doc = reference_filter("Label #{reference}")
       expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
     end
 
     it 'includes a data-project attribute' do
-      doc = filter("Label #{reference}")
+      doc = reference_filter("Label #{reference}")
       link = doc.css('a').first
 
       expect(link).to have_attribute('data-project')
@@ -34,7 +34,7 @@ module Gitlab::Markdown
     end
 
     it 'includes a data-label attribute' do
-      doc = filter("See #{reference}")
+      doc = reference_filter("See #{reference}")
       link = doc.css('a').first
 
       expect(link).to have_attribute('data-label')
@@ -42,7 +42,7 @@ module Gitlab::Markdown
     end
 
     it 'supports an :only_path context' do
-      doc = filter("Label #{reference}", only_path: true)
+      doc = reference_filter("Label #{reference}", only_path: true)
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
@@ -56,33 +56,33 @@ module Gitlab::Markdown
 
     describe 'label span element' do
       it 'includes default classes' do
-        doc = filter("Label #{reference}")
+        doc = reference_filter("Label #{reference}")
         expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
       end
 
       it 'includes a style attribute' do
-        doc = filter("Label #{reference}")
+        doc = reference_filter("Label #{reference}")
         expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
       end
     end
 
     context 'Integer-based references' do
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_path(project.namespace, project, label_name: label.name)
+          namespace_project_issues_url(project.namespace, project, label_name: label.name)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
+        doc = reference_filter("Label (#{reference}.)")
         expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
       end
 
       it 'ignores invalid label IDs' do
         exp = act = "Label #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -91,22 +91,22 @@ module Gitlab::Markdown
       let(:reference) { "#{Label.reference_prefix}#{label.name}" }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_path(project.namespace, project, label_name: label.name)
+          namespace_project_issues_url(project.namespace, project, label_name: label.name)
         expect(doc.text).to eq 'See gfm'
       end
 
       it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
+        doc = reference_filter("Label (#{reference}.)")
         expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
       end
 
       it 'ignores invalid label names' do
         exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -115,29 +115,66 @@ module Gitlab::Markdown
       let(:reference) { label.to_reference(:name) }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_path(project.namespace, project, label_name: label.name)
+          namespace_project_issues_url(project.namespace, project, label_name: label.name)
         expect(doc.text).to eq 'See gfm references'
       end
 
       it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
+        doc = reference_filter("Label (#{reference}.)")
         expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
       end
 
       it 'ignores invalid label names' do
         exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
     describe 'edge cases' do
       it 'gracefully handles non-references matching the pattern' do
         exp = act = '(format nil "~0f" 3.0) ; 3.0'
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+    end
+
+    describe 'referencing a label in a link href' do
+      let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).to eq urls.
+          namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Label (#{reference}.)")
+        expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\)))
+      end
+
+      it 'includes a data-project attribute' do
+        doc = reference_filter("Label #{reference}")
+        link = doc.css('a').first
+
+        expect(link).to have_attribute('data-project')
+        expect(link.attr('data-project')).to eq project.id.to_s
+      end
+
+      it 'includes a data-label attribute' do
+        doc = reference_filter("See #{reference}")
+        link = doc.css('a').first
+
+        expect(link).to have_attribute('data-label')
+        expect(link.attr('data-label')).to eq label.id.to_s
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Label #{reference}")
+        expect(result[:references][:label]).to eq [label]
       end
     end
   end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 3ef6cdfff33b11ca5f39a87c5f069608c497b904..4a23205112714e719f9806144dcf0e1500cbe7a9 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -14,7 +14,7 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -22,42 +22,42 @@ module Gitlab::Markdown
       let(:reference) { merge.to_reference }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).to eq urls.
           namespace_project_merge_request_url(project.namespace, project, merge)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Merge (#{reference}.)")
+        doc = reference_filter("Merge (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
       it 'ignores invalid merge IDs' do
         exp = act = "Merge #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'includes a title attribute' do
-        doc = filter("Merge #{reference}")
+        doc = reference_filter("Merge #{reference}")
         expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
       end
 
       it 'escapes the title attribute' do
         merge.update_attribute(:title, %{"></a>whatever<a title="})
 
-        doc = filter("Merge #{reference}")
+        doc = reference_filter("Merge #{reference}")
         expect(doc.text).to eq "Merge #{reference}"
       end
 
       it 'includes default classes' do
-        doc = filter("Merge #{reference}")
+        doc = reference_filter("Merge #{reference}")
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
       end
 
       it 'includes a data-project attribute' do
-        doc = filter("Merge #{reference}")
+        doc = reference_filter("Merge #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-project')
@@ -65,7 +65,7 @@ module Gitlab::Markdown
       end
 
       it 'includes a data-merge-request attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-merge-request')
@@ -73,7 +73,7 @@ module Gitlab::Markdown
       end
 
       it 'supports an :only_path context' do
-        doc = filter("Merge #{reference}", only_path: true)
+        doc = reference_filter("Merge #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
 
         expect(link).not_to match %r(https?://)
@@ -89,26 +89,50 @@ module Gitlab::Markdown
     context 'cross-project reference' do
       let(:namespace) { create(:namespace, name: 'cross-reference') }
       let(:project2)  { create(:project, :public, namespace: namespace) }
-      let(:merge)     { create(:merge_request, source_project: project2) }
+      let(:merge)     { create(:merge_request, source_project: project2, target_project: project2) }
       let(:reference) { merge.to_reference(project) }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_merge_request_url(project2.namespace,
-                                                        project, merge)
+                                                        project2, merge)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Merge (#{reference}.)")
+        doc = reference_filter("Merge (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
       it 'ignores invalid merge IDs on the referenced project' do
         exp = act = "Merge #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Merge #{reference}")
+        expect(result[:references][:merge_request]).to eq [merge]
+      end
+    end
+
+    context 'cross-project URL reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, :public, namespace: namespace) }
+      let(:merge)     { create(:merge_request, source_project: project2, target_project: project2) }
+      let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq reference
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Merge (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
       end
 
       it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index 9d9652dba46b1a3f0badd95de2cabdaa41cc6266..3a9acc9d6d4b6598b79910d9d72e415883232369 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -15,48 +15,48 @@ module Gitlab::Markdown
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
     context 'internal reference' do
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).to eq urls.
           namespace_project_snippet_url(project.namespace, project, snippet)
       end
 
       it 'links with adjacent text' do
-        doc = filter("Snippet (#{reference}.)")
+        doc = reference_filter("Snippet (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
       it 'ignores invalid snippet IDs' do
         exp = act = "Snippet #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
 
       it 'includes a title attribute' do
-        doc = filter("Snippet #{reference}")
+        doc = reference_filter("Snippet #{reference}")
         expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
       end
 
       it 'escapes the title attribute' do
         snippet.update_attribute(:title, %{"></a>whatever<a title="})
 
-        doc = filter("Snippet #{reference}")
+        doc = reference_filter("Snippet #{reference}")
         expect(doc.text).to eq "Snippet #{reference}"
       end
 
       it 'includes default classes' do
-        doc = filter("Snippet #{reference}")
+        doc = reference_filter("Snippet #{reference}")
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
       end
 
       it 'includes a data-project attribute' do
-        doc = filter("Snippet #{reference}")
+        doc = reference_filter("Snippet #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-project')
@@ -64,7 +64,7 @@ module Gitlab::Markdown
       end
 
       it 'includes a data-snippet attribute' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-snippet')
@@ -72,7 +72,7 @@ module Gitlab::Markdown
       end
 
       it 'supports an :only_path context' do
-        doc = filter("Snippet #{reference}", only_path: true)
+        doc = reference_filter("Snippet #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
 
         expect(link).not_to match %r(https?://)
@@ -92,21 +92,51 @@ module Gitlab::Markdown
       let(:reference) { snippet.to_reference(project) }
 
       it 'links to a valid reference' do
-        doc = filter("See #{reference}")
+        doc = reference_filter("See #{reference}")
 
         expect(doc.css('a').first.attr('href')).
           to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
       end
 
       it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
+        doc = reference_filter("See (#{reference}.)")
         expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
       end
 
       it 'ignores invalid snippet IDs on the referenced project' do
         exp = act = "See #{invalidate_reference(reference)}"
 
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Snippet #{reference}")
+        expect(result[:references][:snippet]).to eq [snippet]
+      end
+    end
+
+    context 'cross-project URL reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+      let(:snippet)   { create(:project_snippet, project: project2) }
+      let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+
+      it 'links to a valid reference' do
+        doc = reference_filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("See (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid snippet IDs on the referenced project' do
+        act = "See #{invalidate_reference(reference)}"
+
+        expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
       end
 
       it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index d9e0d7c42db7fd1bc83981d4b5bdeb0600596fbd..25379f0670ef198bde870d35d9d8aacc3c307bca 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -14,13 +14,13 @@ module Gitlab::Markdown
 
     it 'ignores invalid users' do
       exp = act = "Hey #{invalidate_reference(reference)}"
-      expect(filter(act).to_html).to eq(exp)
+      expect(reference_filter(act).to_html).to eq(exp)
     end
 
     %w(pre code a style).each do |elem|
       it "ignores valid references contained inside '#{elem}' element" do
         exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
+        expect(reference_filter(act).to_html).to eq exp
       end
     end
 
@@ -32,7 +32,7 @@ module Gitlab::Markdown
       end
 
       it 'supports a special @all mention' do
-        doc = filter("Hey #{reference}")
+        doc = reference_filter("Hey #{reference}")
         expect(doc.css('a').length).to eq 1
         expect(doc.css('a').first.attr('href'))
           .to eq urls.namespace_project_url(project.namespace, project)
@@ -46,26 +46,26 @@ module Gitlab::Markdown
 
     context 'mentioning a user' do
       it 'links to a User' do
-        doc = filter("Hey #{reference}")
+        doc = reference_filter("Hey #{reference}")
         expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
       end
 
       it 'links to a User with a period' do
         user = create(:user, name: 'alphA.Beta')
 
-        doc = filter("Hey #{user.to_reference}")
+        doc = reference_filter("Hey #{user.to_reference}")
         expect(doc.css('a').length).to eq 1
       end
 
       it 'links to a User with an underscore' do
         user = create(:user, name: 'ping_pong_king')
 
-        doc = filter("Hey #{user.to_reference}")
+        doc = reference_filter("Hey #{user.to_reference}")
         expect(doc.css('a').length).to eq 1
       end
 
       it 'includes a data-user attribute' do
-        doc = filter("Hey #{reference}")
+        doc = reference_filter("Hey #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-user')
@@ -83,12 +83,12 @@ module Gitlab::Markdown
       let(:reference) { group.to_reference }
 
       it 'links to the Group' do
-        doc = filter("Hey #{reference}")
+        doc = reference_filter("Hey #{reference}")
         expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
       end
 
       it 'includes a data-group attribute' do
-        doc = filter("Hey #{reference}")
+        doc = reference_filter("Hey #{reference}")
         link = doc.css('a').first
 
         expect(link).to have_attribute('data-group')
@@ -102,21 +102,48 @@ module Gitlab::Markdown
     end
 
     it 'links with adjacent text' do
-      doc = filter("Mention me (#{reference}.)")
+      doc = reference_filter("Mention me (#{reference}.)")
       expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
     end
 
     it 'includes default classes' do
-      doc = filter("Hey #{reference}")
+      doc = reference_filter("Hey #{reference}")
       expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
     end
 
     it 'supports an :only_path context' do
-      doc = filter("Hey #{reference}", only_path: true)
+      doc = reference_filter("Hey #{reference}", only_path: true)
       link = doc.css('a').first.attr('href')
 
       expect(link).not_to match %r(https?://)
       expect(link).to eq urls.user_path(user)
     end
+
+    context 'referencing a user in a link href' do
+      let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
+
+      it 'links to a User' do
+        doc = reference_filter("Hey #{reference}")
+        expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+      end
+
+      it 'links with adjacent text' do
+        doc = reference_filter("Mention me (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/)
+      end
+
+      it 'includes a data-user attribute' do
+        doc = reference_filter("Hey #{reference}")
+        link = doc.css('a').first
+
+        expect(link).to have_attribute('data-user')
+        expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
+      end
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Hey #{reference}")
+        expect(result[:references][:user]).to eq [user]
+      end
+    end
   end
 end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 1031af097bd0c396d043c3186f459b3f82dc16ac..3c1009a2eb087267cbd3d674a42077ca9163556c 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -7,50 +7,72 @@ describe CommitRange do
     it { is_expected.to include_module(Referable) }
   end
 
-  let(:sha_from) { 'f3f85602' }
-  let(:sha_to)   { 'e86e1013' }
+  let!(:project) { create(:project, :public) }
+  let!(:commit1) { project.commit("HEAD~2") }
+  let!(:commit2) { project.commit }
 
-  let(:range)  { described_class.new("#{sha_from}...#{sha_to}") }
-  let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
+  let(:sha_from) { commit1.short_id }
+  let(:sha_to)   { commit2.short_id }
+
+  let(:full_sha_from) { commit1.id }
+  let(:full_sha_to)   { commit2.id }
+
+  let(:range)  { described_class.new("#{sha_from}...#{sha_to}", project) }
+  let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
 
   it 'raises ArgumentError when given an invalid range string' do
-    expect { described_class.new("Foo") }.to raise_error(ArgumentError)
+    expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
   end
 
   describe '#to_s' do
     it 'is correct for three-dot syntax' do
-      expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
+      expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
     end
 
     it 'is correct for two-dot syntax' do
-      expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}"
+      expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}"
     end
   end
 
   describe '#to_reference' do
-    let(:project) { double('project', to_reference: 'namespace1/project') }
+    let(:cross) { create(:project) }
+
+    it 'returns a String reference to the object' do
+      expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}"
+    end
+
+    it 'returns a String reference to the object' do
+      expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}"
+    end
+
+    it 'supports a cross-project reference' do
+      expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}"
+    end
+  end
 
-    before do
-      range.project = project
+  describe '#reference_link_text' do
+    let(:cross) { create(:project) }
+
+    it 'returns a String reference to the object' do
+      expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
     end
 
     it 'returns a String reference to the object' do
-      expect(range.to_reference).to eq range.to_s
+      expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
     end
 
     it 'supports a cross-project reference' do
-      cross = double('project')
-      expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
+      expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
     end
   end
 
   describe '#reference_title' do
     it 'returns the correct String for three-dot ranges' do
-      expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
+      expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
     end
 
     it 'returns the correct String for two-dot ranges' do
-      expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}"
+      expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
     end
   end
 
@@ -60,11 +82,11 @@ describe CommitRange do
     end
 
     it 'includes the correct values for a three-dot range' do
-      expect(range.to_param).to eq({ from: sha_from, to: sha_to })
+      expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to })
     end
 
     it 'includes the correct values for a two-dot range' do
-      expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
+      expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to })
     end
   end
 
@@ -79,64 +101,37 @@ describe CommitRange do
   end
 
   describe '#valid_commits?' do
-    context 'without a project' do
-      it 'returns nil' do
-        expect(range.valid_commits?).to be_nil
+    context 'with a valid repo' do
+      before do
+        expect(project).to receive(:valid_repo?).and_return(true)
       end
-    end
-
-    it 'accepts an optional project argument' do
-      project1 = double('project1').as_null_object
-      project2 = double('project2').as_null_object
-
-      # project1 gets assigned through the accessor, but ignored when not given
-      # as an argument to `valid_commits?`
-      expect(project1).not_to receive(:present?)
-      range.project = project1
-
-      # project2 gets passed to `valid_commits?`
-      expect(project2).to receive(:present?).and_return(false)
 
-      range.valid_commits?(project2)
-    end
-
-    context 'with a project' do
-      let(:project) { double('project', repository: double('repository')) }
+      it 'is false when `sha_from` is invalid' do
+        expect(project).to receive(:commit).with(sha_from).and_return(nil)
+        expect(project).to receive(:commit).with(sha_to).and_call_original
 
-      context 'with a valid repo' do
-        before do
-          expect(project).to receive(:valid_repo?).and_return(true)
-          range.project = project
-        end
+        expect(range).not_to be_valid_commits
+      end
 
-        it 'is false when `sha_from` is invalid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(false)
-          expect(project.repository).not_to receive(:commit).with(sha_to)
-          expect(range).not_to be_valid_commits
-        end
+      it 'is false when `sha_to` is invalid' do
+        expect(project).to receive(:commit).with(sha_from).and_call_original
+        expect(project).to receive(:commit).with(sha_to).and_return(nil)
 
-        it 'is false when `sha_to` is invalid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
-          expect(project.repository).to receive(:commit).with(sha_to).and_return(false)
-          expect(range).not_to be_valid_commits
-        end
+        expect(range).not_to be_valid_commits
+      end
 
-        it 'is true when both `sha_from` and `sha_to` are valid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
-          expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
-          expect(range).to be_valid_commits
-        end
+      it 'is true when both `sha_from` and `sha_to` are valid' do
+        expect(range).to be_valid_commits
       end
+    end
 
-      context 'without a valid repo' do
-        before do
-          expect(project).to receive(:valid_repo?).and_return(false)
-          range.project = project
-        end
+    context 'without a valid repo' do
+      before do
+        expect(project).to receive(:valid_repo?).and_return(false)
+      end
 
-        it 'returns false' do
-          expect(range).not_to be_valid_commits
-        end
+      it 'returns false' do
+        expect(range).not_to be_valid_commits
       end
     end
   end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 90be93249511f5b8eef45d4b5d65587758c37553..974b52c1833ab9fd01ee366019eb167ec4e9c628 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -24,6 +24,17 @@ describe Commit do
     end
   end
 
+  describe '#reference_link_text' do
+    it 'returns a String reference to the object' do
+      expect(commit.reference_link_text).to eq commit.short_id
+    end
+
+    it 'supports a cross-project reference' do
+      cross = double('project')
+      expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}"
+    end
+  end
+
   describe '#title' do
     it "returns no_commit_message when safe_message is blank" do
       allow(commit).to receive(:safe_message).and_return('')
@@ -77,14 +88,10 @@ eos
     let(:other_issue) { create :issue, project: other_project }
 
     it 'detects issues that this commit is marked as closing' do
-      allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
-      expect(commit.closes_issues).to eq([issue])
-    end
-
-    it 'does not detect issues from other projects' do
       ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
-      allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
-      expect(commit.closes_issues).to be_empty
+      allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+      expect(commit.closes_issues).to include(issue)
+      expect(commit.closes_issues).to include(other_issue)
     end
   end
 
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 97e5c270a59b994222f05f7067702a9665bcec0a..91e3bee13c1a987d28e22fd493ff8bebadbf8284 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -35,11 +35,24 @@ module FilterSpecHelper
     pipeline.call(body)
   end
 
-  def reference_pipeline_result(body, contexts = {})
+  def reference_pipeline(contexts = {})
     contexts.reverse_merge!(project: project) if defined?(project)
 
-    pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
-    pipeline.call(body)
+    filters = [
+      Gitlab::Markdown::AutolinkFilter,
+      described_class,
+      Gitlab::Markdown::ReferenceGathererFilter
+    ]
+
+    HTML::Pipeline.new(filters, contexts)
+  end
+
+  def reference_pipeline_result(body, contexts = {})
+    reference_pipeline(contexts).call(body)
+  end
+
+  def reference_filter(html, contexts = {})
+    reference_pipeline(contexts).to_document(html)
   end
 
   # Modify a String reference to make it invalid
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index bedc1a7f1db47960b8c8c53137782fdf9fbb5a5f..d6d3062a197a84ba7ef8ce33cdecf8c035fdc254 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -93,6 +93,10 @@ class MarkdownFeature
     end
   end
 
+  def urls
+    Gitlab::Application.routes.url_helpers
+  end
+
   def raw_markdown
     markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
     ERB.new(markdown).result(binding)
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7500d0fdf80bbb0d7ce04d3cc384ba0cb7e1828d..7eadcd58c1fcb061489712c10cc59026a597fe61 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -71,7 +71,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4)
     end
   end
 
@@ -80,7 +80,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-issue', count: 6)
     end
   end
 
@@ -89,7 +89,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6)
       expect(actual).to have_selector('em a.gfm-merge_request')
     end
   end
@@ -99,7 +99,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5)
     end
   end
 
@@ -108,7 +108,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5)
     end
   end
 
@@ -117,7 +117,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-commit', count: 5)
     end
   end
 
@@ -126,7 +126,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-label', count: 4)
     end
   end
 
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 3bb568f4d494858c432f259cf37c20ef7465f535..33d2b14583c61b52de1bea228aae2d0ed61daf4f 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -10,12 +10,12 @@ def common_mentionable_setup
 
   let(:mentioned_issue)  { create(:issue, project: project) }
   let!(:mentioned_mr)     { create(:merge_request, :simple, source_project: project) }
-  let(:mentioned_commit) { project.commit }
+  let(:mentioned_commit) { project.commit("HEAD~1") }
 
   let(:ext_proj)   { create(:project, :public) }
   let(:ext_issue)  { create(:issue, project: ext_proj) }
   let(:ext_mr)     { create(:merge_request, :simple, source_project: ext_proj) }
-  let(:ext_commit) { ext_proj.commit }
+  let(:ext_commit) { ext_proj.commit("HEAD~2") }
 
   # Override to add known commits to the repository stub.
   let(:extra_commits) { [] }
@@ -45,14 +45,11 @@ def common_mentionable_setup
   before do
     # Wire the project's repository to return the mentioned commit, and +nil+
     # for any unrecognized commits.
-    commitmap = {
-      mentioned_commit.id => mentioned_commit
-    }
-    extra_commits.each { |c| commitmap[c.short_id] = c }
-
-    allow(Project).to receive(:find).and_call_original
-    allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
-    allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
+    allow_any_instance_of(::Repository).to receive(:commit).and_call_original
+    allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
+    extra_commits.each do |commit|
+      allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
+    end
 
     set_mentionable_text.call(ref_string)
   end