diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 658134821202f227b264514bbb58161128949021..c6fff1b8ecff8a4ee7a29158f3e6cdbe42167d03 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
                    end
 
     user = current_user if defined?(current_user)
-    gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
+    gfm_body = Gitlab::Markdown.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
 
     fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
     if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -48,37 +48,20 @@ module GitlabMarkdownHelper
   def markdown(text, context = {})
     return "" unless text.present?
 
-    context.reverse_merge!(
-      path:         @path,
-      pipeline:     :default,
-      project:      @project,
-      project_wiki: @project_wiki,
-      ref:          @ref
-    )
-
-    user = current_user if defined?(current_user)
-
+    context[:project] ||= @project
+    
     html = Gitlab::Markdown.render(text, context)
-    Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
-  end
 
-  # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
-  # with a custom pipeline depending on the content being rendered
-  def gfm(text, options = {})
-    return "" unless text.present?
+    context.merge!(
+      current_user:   (current_user if defined?(current_user)),
 
-    options.reverse_merge!(
-      path:         @path,
-      pipeline:     :default,
-      project:      @project,
-      project_wiki: @project_wiki,
-      ref:          @ref
+      # RelativeLinkFilter
+      requested_path: @path,
+      project_wiki:   @project_wiki,
+      ref:            @ref
     )
 
-    user = current_user if defined?(current_user)
-
-    html = Gitlab::Markdown.gfm(text, options)
-    Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
+    Gitlab::Markdown.post_process(html, context)
   end
 
   def asciidoc(text)
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ad63841ccf3fee2d9f7ec3870a7a3637935b2aff..4ba8b84fd923cd308852f4e084544f2bceb85dc4 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
   .commit-row-title
     = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
     ·
-    = gfm event_commit_title(commit[:message]), project: project
+    = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index fbf0a9ec0c306482d3c12046f6ab162d2d44cd88..f3be1da8428de27aba163826ca96412d8c70cf7f 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -50,10 +50,10 @@
 
 .commit-box.gray-content-block.middle-block
   %h3.commit-title
-    = gfm escape_once(@commit.title)
+    = markdown escape_once(@commit.title), pipeline: :single_line
   - if @commit.description.present?
     %pre.commit-description
-      = preserve(gfm(escape_once(@commit.description)))
+      = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
 
 :coffeescript
   $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}")
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index cddd5aa3a83ce29ca27091d82e829f4539110788..9e0b536bb4b28ab731cb685182098541a013d1c1 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -32,7 +32,7 @@
     - if commit.description?
       .commit-row-description.js-toggle-content
         %pre
-          = preserve(gfm(escape_once(commit.description)))
+          = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
 
     .commit-row-info
       = commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5cb814c9ea84cb29579c6479e9d1b8f8010c2e1e..3233c6884cc7f6940b64ddb4c55771be94954bfc 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -37,7 +37,7 @@
 
     .gray-content-block.middle-block
       %h2.issue-title
-        = gfm escape_once(@issue.title)
+        = markdown escape_once(@issue.title), pipeline: :single_line
       %div
         - if @issue.description.present?
           .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index b4f62a758900e92452fa66bba86dce737218a938..448230a377c44d7b366f3abeced6f62f33dba6bc 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,6 +1,6 @@
 .gray-content-block.middle-block
   %h2.issue-title
-    = gfm escape_once(@merge_request.title)
+    = markdown escape_once(@merge_request.title), pipeline: :single_line
 
   %div
     - if @merge_request.description.present?
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0aad9bb3e88e0507eb56af53618271f2e525c499..8629129c0b1dc3a738560c127348c4cee8fdc6df 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -24,4 +24,4 @@
         %i.fa.fa-check
         Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
         = succeed '.' do
-          != gfm(issues_sentence(@closes_issues))
+          != markdown issues_sentence(@closes_issues), pipeline: :gfm
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4eeb0621e526c32f1d824679c9ae115982760b88..302410765fc67ced88873b727b5fdec1671eb47e 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -31,7 +31,7 @@
     %span All issues for this milestone are closed. You may close milestone now.
 
 %h3.issue-title
-  = gfm escape_once(@milestone.title)
+  = markdown escape_once(@milestone.title), pipeline: :single_line
 %div
   - if @milestone.description.present?
     .description
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index f3526ad0747a8dbe31117f5e0dd7308efc9c3db1..6ca919f7f80292ad448a95b606e63a783a88fbc8 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
       = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
         %code= commit.short_id
       = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
-      = gfm escape_once(truncate(commit.title, length: 40))
+      = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
   %td
     %span.pull-right.cgray
       = time_ago_with_tooltip(commit.committed_date)
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index d5b0060dd56806695e31b913a73c05bbb888d3f4..773ebddba2b9c4758368a1d82ec267d72622c83a 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -19,51 +19,15 @@ module Gitlab
     # context  - Hash of context options passed to our HTML Pipeline
     #
     # Returns an HTML-safe String
-    def self.render(markdown, context = {})
-      html = renderer.render(markdown)
-      html = gfm(html, context)
+    def self.render(text, context = {})
+      pipeline = context[:pipeline] || :full
 
-      html.html_safe
-    end
+      html_pipeline = html_pipelines[pipeline]
 
-    # Convert a Markdown String into HTML without going through the HTML
-    # Pipeline.
-    #
-    # Note that because the pipeline is skipped, SanitizationFilter is as well.
-    # Do not output the result of this method to the user.
-    #
-    # markdown - Markdown String
-    #
-    # Returns a String
-    def self.render_without_gfm(markdown)
-      renderer.render(markdown)
-    end
+      transformers = get_context_transformers(pipeline)
+      context = transformers.reduce(context) { |context, transformer| transformer.call(context) }
 
-    # Perform post-processing on an HTML String
-    #
-    # This method is used to perform state-dependent changes to a String of
-    # HTML, such as removing references that the current user doesn't have
-    # permission to make (`RedactorFilter`).
-    #
-    # html     - String to process
-    # options  - Hash of options to customize output
-    #            :pipeline  - Symbol pipeline type
-    #            :project   - Project
-    #            :user      - User object
-    #
-    # Returns an HTML-safe String
-    def self.post_process(html, options)
-      context = {
-        project:      options[:project],
-        current_user: options[:user]
-      }
-      doc = post_processor.to_document(html, context)
-
-      if options[:pipeline] == :atom
-        doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
-      else
-        doc.to_html
-      end.html_safe
+      html_pipeline.to_html(text, context)
     end
 
     # Provide autoload paths for filters to prevent a circular dependency error
@@ -75,6 +39,7 @@ module Gitlab
     autoload :ExternalLinkFilter,           'gitlab/markdown/external_link_filter'
     autoload :IssueReferenceFilter,         'gitlab/markdown/issue_reference_filter'
     autoload :LabelReferenceFilter,         'gitlab/markdown/label_reference_filter'
+    autoload :MarkdownFilter,               'gitlab/markdown/markdown_filter'
     autoload :MergeRequestReferenceFilter,  'gitlab/markdown/merge_request_reference_filter'
     autoload :RedactorFilter,               'gitlab/markdown/redactor_filter'
     autoload :RelativeLinkFilter,           'gitlab/markdown/relative_link_filter'
@@ -85,98 +50,38 @@ module Gitlab
     autoload :TaskListFilter,               'gitlab/markdown/task_list_filter'
     autoload :UserReferenceFilter,          'gitlab/markdown/user_reference_filter'
 
-    # Public: Parse the provided HTML with GitLab-Flavored Markdown
+    # Perform post-processing on an HTML String
+    #
+    # This method is used to perform state-dependent changes to a String of
+    # HTML, such as removing references that the current user doesn't have
+    # permission to make (`RedactorFilter`).
     #
-    # html    - HTML String
-    # options - A Hash of options used to customize output (default: {})
-    #           :no_header_anchors - Disable header anchors in TableOfContentsFilter
-    #           :path              - Current path String
-    #           :pipeline          - Symbol pipeline type
-    #           :project           - Current Project object
-    #           :project_wiki      - Current ProjectWiki object
-    #           :ref               - Current ref String
+    # html     - String to process
+    # context  - Hash of options to customize output
+    #            :pipeline  - Symbol pipeline type
+    #            :project   - Project
+    #            :user      - User object
     #
     # Returns an HTML-safe String
-    def self.gfm(html, options = {})
-      return '' unless html.present?
-
-      @pipeline ||= HTML::Pipeline.new(filters)
-
-      context = {
-        # SanitizationFilter
-        pipeline: options[:pipeline],
-
-        # EmojiFilter
-        asset_host: Gitlab::Application.config.asset_host,
-        asset_root: Gitlab.config.gitlab.base_url,
-
-        # ReferenceFilter
-        only_path: only_path_pipeline?(options[:pipeline]),
-        project:   options[:project],
-
-        # RelativeLinkFilter
-        project_wiki:   options[:project_wiki],
-        ref:            options[:ref],
-        requested_path: options[:path],
-
-        # TableOfContentsFilter
-        no_header_anchors: options[:no_header_anchors]
-      }
+    def self.post_process(html, context)
+      doc = html_pipelines[:post_process].to_document(html, context)
 
-      @pipeline.to_html(html, context).html_safe
-    end
-
-    private
-
-    # Check if a pipeline enables the `only_path` context option
-    #
-    # Returns Boolean
-    def self.only_path_pipeline?(pipeline)
-      case pipeline
-      when :atom, :email
-        false
+      if context[:xhtml]
+        doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
       else
-        true
-      end
-    end
-
-    def self.redcarpet_options
-      # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
-      @redcarpet_options ||= {
-        fenced_code_blocks:  true,
-        footnotes:           true,
-        lax_spacing:         true,
-        no_intra_emphasis:   true,
-        space_after_headers: true,
-        strikethrough:       true,
-        superscript:         true,
-        tables:              true
-      }.freeze
-    end
-
-    def self.renderer
-      @markdown ||= begin
-        renderer = Redcarpet::Render::HTML.new
-        Redcarpet::Markdown.new(renderer, redcarpet_options)
-      end
-    end
-
-    def self.post_processor
-      @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
+        doc.to_html
+      end.html_safe
     end
 
-    # Filters used in our pipeline
-    #
-    # SanitizationFilter should come first so that all generated reference HTML
-    # goes through untouched.
-    #
-    # See https://github.com/jch/html-pipeline#filters for more filters.
-    def self.filters
-      [
+    private
+    FILTERS = {
+      plain_markdown: [
+        Gitlab::Markdown::MarkdownFilter
+      ],
+      gfm: [
         Gitlab::Markdown::SyntaxHighlightFilter,
         Gitlab::Markdown::SanitizationFilter,
 
-        Gitlab::Markdown::RelativeLinkFilter,
         Gitlab::Markdown::EmojiFilter,
         Gitlab::Markdown::TableOfContentsFilter,
         Gitlab::Markdown::AutolinkFilter,
@@ -192,7 +97,90 @@ module Gitlab
         Gitlab::Markdown::LabelReferenceFilter,
 
         Gitlab::Markdown::TaskListFilter
+      ],
+
+      full:           [:plain_markdown, :gfm],
+      atom:           :full,
+      email:          :full,
+      description:    :full,
+      single_line:    :gfm,
+
+      post_process: [
+        Gitlab::Markdown::RelativeLinkFilter, 
+        Gitlab::Markdown::RedactorFilter
       ]
+    }
+
+    CONTEXT_TRANSFORMERS = {
+      gfm: {
+        only_path: true,
+
+        # EmojiFilter
+        asset_host: Gitlab::Application.config.asset_host,
+        asset_root: Gitlab.config.gitlab.base_url
+      },
+      full: :gfm,
+
+      atom: [
+        :full, 
+        { 
+          only_path: false, 
+          xhtml: true 
+        }
+      ],
+      email: [
+        :full,
+        { 
+          only_path: false 
+        }
+      ],
+      description: [
+        :full,
+        { 
+          # SanitizationFilter
+          inline_sanitization: true
+        }
+      ],
+      single_line: :gfm,
+
+      post_process: {
+        post_process: true
+      }
+    }
+
+    def self.html_pipelines
+      @html_pipelines ||= Hash.new do |hash, pipeline|
+        filters = get_filters(pipeline)
+        HTML::Pipeline.new(filters)
+      end
+    end
+
+    def self.get_filters(pipelines)
+      Array.wrap(pipelines).flat_map do |pipeline|
+        case pipeline
+        when Class
+          pipeline
+        when Symbol
+          get_filters(FILTERS[pipeline])
+        when Array
+          get_filters(pipeline)
+        end
+      end.compact
+    end
+
+    def self.get_context_transformers(pipelines)
+      Array.wrap(pipelines).flat_map do |pipeline|
+        case pipeline
+        when Hash
+          ->(context) { context.merge(pipeline) }
+        when Proc
+          pipeline
+        when Symbol
+          get_context_transformers(CONTEXT_TRANSFORMERS[pipeline])
+        when Array
+          get_context_transformers(pipeline)
+        end
+      end.compact
     end
   end
 end
diff --git a/lib/gitlab/markdown/markdown_filter.rb b/lib/gitlab/markdown/markdown_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..921e2a0794e62c75945084628e43ba98371c7fe4
--- /dev/null
+++ b/lib/gitlab/markdown/markdown_filter.rb
@@ -0,0 +1,39 @@
+module Gitlab
+  module Markdown
+    class MarkdownFilter < HTML::Pipeline::TextFilter
+      def initialize(text, context = nil, result = nil)
+        super text, context, result
+        @text = @text.gsub "\r", ''
+      end
+
+      def call
+        html = self.class.renderer.render(@text)
+        html.rstrip!
+        html
+      end
+  
+      private      
+
+      def self.redcarpet_options
+        # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
+        @redcarpet_options ||= {
+          fenced_code_blocks:  true,
+          footnotes:           true,
+          lax_spacing:         true,
+          no_intra_emphasis:   true,
+          space_after_headers: true,
+          strikethrough:       true,
+          superscript:         true,
+          tables:              true
+        }.freeze
+      end
+
+      def self.renderer
+        @renderer ||= begin
+          renderer = Redcarpet::Render::HTML.new
+          Redcarpet::Markdown.new(renderer, redcarpet_options)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
index 6ee3d1ce03903465b9bc694ab26cedb5271c630a..3e9909d2f3322dfd9488fe2dddc640817f5be2ce 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -16,7 +16,7 @@ module Gitlab
       def call
         return doc unless linkable_files?
 
-        doc.search('a').each do |el|
+        doc.search('a:not(.gfm)').each do |el|
           process_link_attr el.attribute('href')
         end
 
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index e368de7d8484866e6bc6a5d7d9ee0f7565ba81ac..550dfafca852104127916438725edd00b1dce76a 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -11,7 +11,7 @@ module Gitlab
       def whitelist
         # Descriptions are more heavily sanitized, allowing only a few elements.
         # See http://git.io/vkuAN
-        if pipeline == :description
+        if context[:inline_sanitization]
           whitelist = LIMITED
           whitelist[:elements] -= %w(pre code img ol ul li)
         else
@@ -25,10 +25,6 @@ module Gitlab
 
       private
 
-      def pipeline
-        context[:pipeline] || :default
-      end
-
       def customized?(transformers)
         transformers.last.source_location[0] == __FILE__
       end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 66016ecc877ecaf5005a0993b35f9a0ca78766bb..633c988d0255d3e2a38319db4be0b8693115841e 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -13,7 +13,8 @@ module Gitlab
 
     def analyze(text)
       references.clear
-      @text = Gitlab::Markdown.render_without_gfm(text)
+
+      @document = Gitlab::Markdown.render(text, project: project)
     end
 
     %i(user label issue merge_request snippet commit commit_range).each do |type|
@@ -44,20 +45,13 @@ module Gitlab
       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,
-
-        # ReferenceGathererFilter
+        project:              project,
+        current_user:         current_user,
         load_lazy_references: false,
         reference_filter:     filter
       }
 
-      pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
-      result = pipeline.call(@text)
+      result = self.class.pipeline.call(@document, context)
 
       values = result[:references][filter_type].uniq
 
@@ -67,5 +61,9 @@ module Gitlab
 
       values
     end
+
+    def self.pipeline
+      @pipeline ||= HTML::Pipeline.new([Gitlab::Markdown::ReferenceGathererFilter])
+    end
   end
 end