diff --git a/.rubocop.yml b/.rubocop.yml
index 03b78d6884016d3cca875d16414e521914703a73..0cc729d3b08eff154b191a719e982b6ed3ccd2bc 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -954,7 +954,7 @@ Lint/Void:
 
 Rails/ActionFilter:
   Description: 'Enforces consistent use of action filter methods.'
-  Enabled: false
+  Enabled: true
 
 Rails/DefaultScope:
   Description: 'Checks if the argument passed to default_scope is a block.'
diff --git a/CHANGELOG b/CHANGELOG
index b4affd5217a24a70e7e9a04ae6a8339598b41138..5bb6842f50057fa195a315f375a7d93e3468ffce 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,14 +3,16 @@ Please view this file on the master branch, on stable branches it's out of date.
 v 7.11.0 (unreleased)
   - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
   - Ignore invalid lines in .gitmodules
+  - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
   -
-  -
+  - Add "Reply quoting selected text" shortcut key (`r`)
   -
   -
   -
   -
   -
   - Improve new project command options (Ben Bodenmiller)
+  - Prevent sending empty messages to HipChat (Chulki Lee)
 
 v 7.10.0 (unreleased)
   - Ignore submodules that are defined in .gitmodules but are checked in as directories.
@@ -38,7 +40,6 @@ v 7.10.0 (unreleased)
   - Allow HTML tags in Markdown input
   - Fix code unfold not working on Compare commits page (Stan Hu)
   - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
-  - Include missing events and fix save functionality in admin service template settings form (Stan Hu)
   - Fix "Import projects from" button to show the correct instructions (Stan Hu)
   - Fix dots in Wiki slugs causing errors (Stan Hu)
   - Make maximum attachment size configurable via Application Settings (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 1c2a900220495e8bfad2e12e44ee84ae700b4302..c7a59a82d1a9eb7bb6ac7754ce3eef58d51f01d0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -42,7 +42,7 @@ gem "browser"
 gem "gitlab_git", '~> 7.1.10'
 
 # Ruby/Rack Git Smart-HTTP Server Handler
-gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack'
+gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
 
 # LDAP Auth
 gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
@@ -88,7 +88,7 @@ gem "six"
 gem "seed-fu"
 
 # Markup pipeline for GitLab
-gem 'html-pipeline-gitlab', '~> 0.1'
+gem 'html-pipeline', '~> 1.11.0'
 
 # Markdown to HTML
 gem "github-markup"
@@ -143,7 +143,7 @@ gem "redis-rails"
 gem 'tinder', '~> 1.9.2'
 
 # HipChat integration
-gem "hipchat", "~> 1.4.0"
+gem 'hipchat', '~> 1.5.0'
 
 # Flowdock integration
 gem "gitlab-flowdock-git-hook", "~> 0.4.2"
@@ -251,12 +251,13 @@ group :development, :test do
   # PhantomJS driver for Capybara
   gem 'poltergeist', '~> 1.5.1'
 
-  gem 'jasmine', '2.0.2'
+  gem 'jasmine', '~> 2.2.0'
+  gem 'jasmine-rails'
 
   gem "spring", '~> 1.3.1'
   gem "spring-commands-rspec", '1.0.4'
   gem "spring-commands-spinach", '1.0.0'
-  
+
   gem "byebug"
 end
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 360b1abcf56dd24914d93d1d59101387a4005a8f..d905ac927fc68e0900f86812edbb32fe9b6a9439 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -199,7 +199,7 @@ GEM
     gitlab-flowdock-git-hook (0.4.2.2)
       gitlab-grit (>= 2.4.1)
       multi_json
-    gitlab-grack (2.0.0)
+    gitlab-grack (2.0.2)
       rack (~> 1.5.1)
     gitlab-grit (2.7.2)
       charlock_holmes (~> 0.6)
@@ -271,20 +271,15 @@ GEM
     hashie (2.1.2)
     highline (1.6.21)
     hike (1.2.3)
-    hipchat (1.4.0)
+    hipchat (1.5.0)
       httparty
+      mimemagic
     hitimes (1.2.2)
     html-pipeline (1.11.0)
       activesupport (>= 2)
       nokogiri (~> 1.4)
-    html-pipeline-gitlab (0.2.0)
-      actionpack (~> 4)
-      gitlab_emoji (~> 0.1)
-      html-pipeline (~> 1.11.0)
-      mime-types
-      sanitize (~> 2.1)
     http_parser.rb (0.5.3)
-    httparty (0.13.0)
+    httparty (0.13.3)
       json (~> 1.8)
       multi_xml (>= 0.5.2)
     httpauth (0.2.1)
@@ -292,12 +287,17 @@ GEM
     i18n (0.7.0)
     ice_cube (0.11.1)
     ice_nine (0.10.0)
-    jasmine (2.0.2)
-      jasmine-core (~> 2.0.0)
+    jasmine (2.2.0)
+      jasmine-core (~> 2.2)
       phantomjs
       rack (>= 1.2.1)
       rake
-    jasmine-core (2.0.0)
+    jasmine-core (2.2.0)
+    jasmine-rails (0.10.8)
+      jasmine-core (>= 1.3, < 3.0)
+      phantomjs (>= 1.9)
+      railties (>= 3.2.0)
+      sprockets-rails
     jquery-atwho-rails (0.3.3)
     jquery-rails (3.1.0)
       railties (>= 3.0, < 5.0)
@@ -329,6 +329,7 @@ GEM
       mime-types (>= 1.16, < 3)
     method_source (0.8.2)
     mime-types (1.25.1)
+    mimemagic (0.3.0)
     mini_portile (0.6.1)
     minitest (5.3.5)
     mousetrap-rails (1.4.6)
@@ -391,7 +392,7 @@ GEM
     parser (2.2.0.2)
       ast (>= 1.1, < 3.0)
     pg (0.15.1)
-    phantomjs (1.9.2.0)
+    phantomjs (1.9.8.0)
     poltergeist (1.5.1)
       capybara (~> 2.1)
       cliver (~> 0.3.1)
@@ -700,7 +701,7 @@ DEPENDENCIES
   gemnasium-gitlab-service (~> 0.2)
   github-markup
   gitlab-flowdock-git-hook (~> 0.4.2)
-  gitlab-grack (~> 2.0.0.rc2)
+  gitlab-grack (~> 2.0.2)
   gitlab-linguist (~> 3.0.1)
   gitlab_emoji (~> 0.1)
   gitlab_git (~> 7.1.10)
@@ -714,10 +715,11 @@ DEPENDENCIES
   guard-rspec
   guard-spinach
   haml-rails
-  hipchat (~> 1.4.0)
-  html-pipeline-gitlab (~> 0.1)
+  hipchat (~> 1.5.0)
+  html-pipeline (~> 1.11.0)
   httparty
-  jasmine (= 2.0.2)
+  jasmine (~> 2.2.0)
+  jasmine-rails
   jquery-atwho-rails (~> 0.3.3)
   jquery-rails
   jquery-scrollto-rails
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index bd52d3d4d70c07b54916dcb5bcfe1f2544e1bdbd..020c103dbc525414c56c05a793ec7b0ff4df5e64 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -38,7 +38,7 @@
 #= require shortcuts
 #= require shortcuts_navigation
 #= require shortcuts_dashboard_navigation
-#= require shortcuts_issueable
+#= require shortcuts_issuable
 #= require shortcuts_network
 #= require cal-heatmap
 #= require_tree .
@@ -173,6 +173,7 @@ $ ->
     $(@).closest(".diff-file").find(".notes_holder").toggle()
     e.preventDefault()
 
+  $(document).off "click", '.js-confirm-danger'
   $(document).on "click", '.js-confirm-danger', (e) ->
     e.preventDefault()
     btn = $(e.target)
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
index bb99edbd09eb5e21fba32774401da7d739902a01..66e34dd4a088e783dbe69966d56ac5582d37c2fa 100644
--- a/app/assets/javascripts/confirm_danger_modal.js.coffee
+++ b/app/assets/javascripts/confirm_danger_modal.js.coffee
@@ -8,11 +8,13 @@ class @ConfirmDangerModal
     submit = $('.js-confirm-danger-submit')
     submit.disable()
 
+    $('.js-confirm-danger-input').off 'input'
     $('.js-confirm-danger-input').on 'input', ->
       if rstrip($(@).val()) is project_path
         submit.enable()
       else
         submit.disable()
 
+    $('.js-confirm-danger-submit').off 'click'
     $('.js-confirm-danger-submit').on 'click', =>
       @form.submit()
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 330ebac6f75af495e413d841f4cde66fd0b294ae..9aee3b281f3f78653653d631902c20292607c766 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -22,7 +22,7 @@ class Dispatcher
         shortcut_handler = new ShortcutsNavigation()
       when 'projects:issues:show'
         new Issue()
-        shortcut_handler = new ShortcutsIssueable()
+        shortcut_handler = new ShortcutsIssuable()
         new ZenMode()
       when 'projects:milestones:show'
         new Milestone()
@@ -47,7 +47,7 @@ class Dispatcher
           new IssuableForm($('.merge-request-form'))
       when 'projects:merge_requests:show'
         new Diff()
-        shortcut_handler = new ShortcutsIssueable()
+        shortcut_handler = new ShortcutsIssuable()
         new ZenMode()
       when "projects:merge_requests:diffs"
         new Diff()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..6b534f29218aba9b98ecc05ee64fd2a1879301af
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -0,0 +1,48 @@
+#= require jquery
+#= require mousetrap
+
+#= require shortcuts_navigation
+
+class @ShortcutsIssuable extends ShortcutsNavigation
+  constructor: (isMergeRequest) ->
+    super()
+    Mousetrap.bind('a', ->
+      $('.js-assignee').select2('open')
+      return false
+    )
+    Mousetrap.bind('m', ->
+      $('.js-milestone').select2('open')
+      return false
+    )
+    Mousetrap.bind('r', =>
+      @replyWithSelectedText()
+      return false
+    )
+
+    if isMergeRequest
+      @enabledHelp.push('.hidden-shortcut.merge_requests')
+    else
+      @enabledHelp.push('.hidden-shortcut.issues')
+
+  replyWithSelectedText: ->
+    if window.getSelection
+      selected = window.getSelection().toString()
+      replyField = $('.js-main-target-form #note_note')
+
+      return if selected.trim() == ""
+
+      # Put a '>' character before each non-empty line in the selection
+      quote = _.map selected.split("\n"), (val) ->
+        "> #{val}\n" if val.trim() != ''
+
+      # If replyField already has some content, add a newline before our quote
+      separator = replyField.val().trim() != "" and "\n" or ''
+
+      replyField.val (_, current) ->
+        current + separator + quote.join('') + "\n"
+
+      # Trigger autosave for the added text
+      replyField.trigger('input')
+
+      # Focus the input field
+      replyField.focus()
diff --git a/app/assets/javascripts/shortcuts_issueable.coffee b/app/assets/javascripts/shortcuts_issueable.coffee
deleted file mode 100644
index b8dae71e037310266cbea613e05361bc03e3d2d5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_issueable.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsIssueable extends ShortcutsNavigation
-  constructor: (isMergeRequest) ->
-    super()
-    Mousetrap.bind('a', ->
-      $('.js-assignee').select2('open')
-      return false
-    )
-    Mousetrap.bind('m', ->
-      $('.js-milestone').select2('open')
-      return false
-    )
-    
-    if isMergeRequest
-      @enabledHelp.push('.hidden-shortcut.merge_reuests')
-    else
-      @enabledHelp.push('.hidden-shortcut.issues')
-
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index 27f0fd31d5025491e46ea9bcb3104c9dc4e12043..ed12bdcef22aadebd24b2c7cb3f77a33bbf76c66 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,3 +1,7 @@
+#= require d3
+#= require jquery
+#= require stat_graph_contributors_util
+
 class @ContributorsStatGraph
   init: (log) ->
     @parsed_log = ContributorsStatGraphUtil.parse_log(log)
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 8b82d20c6c25f2f9151d158ae448617bd778482b..0e6fbdef3bc1cd382c30ce15ecaa80a1b2cb579d 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -1,3 +1,7 @@
+#= require d3
+#= require jquery
+#= require underscore
+
 class @ContributorsGraph
   MARGIN:
     top: 20
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 6a8f20f60471fe3722bf630077b07ec5b6cf02e8..fe5456f820caec82e5060fb4e841dade4860e957 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -3,7 +3,7 @@
 # Automatically sets the layout and ensures an administrator is logged in
 class Admin::ApplicationController < ApplicationController
   layout 'admin'
-  before_filter :authenticate_admin!
+  before_action :authenticate_admin!
 
   def authenticate_admin!
     return render_404 unless current_user.is_admin?
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b5fda196bf0e89ea4d2e5a1f5faf1273cc9bac5b..e97576769086742d6548e0f76f1243d2b0940a01 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -1,5 +1,5 @@
 class Admin::ApplicationSettingsController < Admin::ApplicationController
-  before_filter :set_application_setting
+  before_action :set_application_setting
 
   def show
   end
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index e1643bb34bf7ce3ab8a3e5f3041ce68d2b9009cb..0808024fc39a46b39f49c298f7983a0080f39e6c 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -1,5 +1,5 @@
 class Admin::BroadcastMessagesController < Admin::ApplicationController
-  before_filter :broadcast_messages
+  before_action :broadcast_messages
 
   def index
     @broadcast_message = BroadcastMessage.new
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index e93603bef362f304dda135f3021f725aa54e55f0..c301e61d1c7ab0cf2d7cc4d53e91857c64c05b85 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -1,13 +1,13 @@
 class Admin::DeployKeysController < Admin::ApplicationController
-  before_filter :deploy_keys, only: [:index]
-  before_filter :deploy_key, only: [:show, :destroy]
+  before_action :deploy_keys, only: [:index]
+  before_action :deploy_key, only: [:show, :destroy]
 
   def index
 
   end
 
   def show
-    
+
   end
 
   def new
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 22d045fc388a07332a29c155da38a6e5b401f881..2dfae13ac5c0e10f980fa78668b6aa5143df2ad1 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,5 +1,5 @@
 class Admin::GroupsController < Admin::ApplicationController
-  before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
+  before_action :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
 
   def index
     @groups = Group.all
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index 21111bb44f5aa9cb5c3f3e0f8b7516a7e78272a7..cb33fdd97634158bb037ba10f6c360c73c2c9ade 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -1,5 +1,5 @@
 class Admin::KeysController < Admin::ApplicationController
-  before_filter :user, only: [:show, :destroy]
+  before_action :user, only: [:show, :destroy]
 
   def show
     @key = user.keys.find(params[:id])
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 5176a8399ae63b74ead7232cc94b6dca7d6df449..ee449badf59a2a51084d7421e1919f6cb4159ff4 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,7 +1,7 @@
 class Admin::ProjectsController < Admin::ApplicationController
-  before_filter :project, only: [:show, :transfer]
-  before_filter :group, only: [:show, :transfer]
-  before_filter :repository, only: [:show, :transfer]
+  before_action :project, only: [:show, :transfer]
+  before_action :group, only: [:show, :transfer]
+  before_action :repository, only: [:show, :transfer]
 
   def index
     @projects = Project.all
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 76a938c5fe4fd8587c4a75659946dad255edfe2b..c1fdcd7fab66ef698154f668b731792276e7cbda 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -1,5 +1,5 @@
 class Admin::ServicesController < Admin::ApplicationController
-  before_filter :service, only: [:edit, :update]
+  before_action :service, only: [:edit, :update]
 
   def index
     @services = services_templates
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index b4c011f213c1bd3507f5940ed2f146301322f104..adb83996f8bf6513e980d6d9e588ff3987c92b44 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,5 +1,5 @@
 class Admin::UsersController < Admin::ApplicationController
-  before_filter :user, only: [:show, :edit, :update, :destroy]
+  before_action :user, only: [:show, :edit, :update, :destroy]
 
   def index
     @users = User.order_name_asc.filter(params[:filter])
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 920a981e7c9bc3501f241fc6cdc262128378737e..8ddb424dcfbc9e40fa0c840d5013c4b0010d506b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,15 +6,15 @@ class ApplicationController < ActionController::Base
 
   PER_PAGE = 20
 
-  before_filter :authenticate_user_from_token!
-  before_filter :authenticate_user!
-  before_filter :reject_blocked!
-  before_filter :check_password_expiration
-  before_filter :ldap_security_check
-  before_filter :default_headers
-  before_filter :add_gon_variables
-  before_filter :configure_permitted_parameters, if: :devise_controller?
-  before_filter :require_email, unless: :devise_controller?
+  before_action :authenticate_user_from_token!
+  before_action :authenticate_user!
+  before_action :reject_blocked!
+  before_action :check_password_expiration
+  before_action :ldap_security_check
+  before_action :default_headers
+  before_action :add_gon_variables
+  before_action :configure_permitted_parameters, if: :devise_controller?
+  before_action :require_email, unless: :devise_controller?
 
   protect_from_forgery with: :exception
 
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index cb51792df16f223fce050bcaf3ff170a62520cb6..33227e7f1d8bc6ec1e8e3d352a5310b685ce4814 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -1,5 +1,5 @@
 class Dashboard::MilestonesController < ApplicationController
-  before_filter :load_projects
+  before_action :load_projects
 
   def index
     project_milestones = case params[:state]
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 56e6fcc41ca1445c54150b4f9653968d5091c166..426bc6154153053a82ffa0e54a59e1a22c34c3ac 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,5 +1,5 @@
 class Dashboard::ProjectsController < ApplicationController
-  before_filter :event_filter
+  before_action :event_filter
 
   def starred
     @projects = current_user.starred_projects
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 9bd853ed5c70ef751c6cf40b6144ed3c77937e59..40b5de1295a0447005f585e5bc19c9d4bbc37f75 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,8 +1,8 @@
 class DashboardController < ApplicationController
   respond_to :html
 
-  before_filter :load_projects, except: [:projects]
-  before_filter :event_filter, only: :show
+  before_action :load_projects, except: [:projects]
+  before_action :event_filter, only: :show
 
   def show
     @projects = @projects.includes(:namespace)
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index c51a4a211a6210688350602240c8f6f5c5a744d1..a7250b799f352141d412f47efe062dd0d2a4fb6a 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,5 +1,5 @@
 class Explore::GroupsController < ApplicationController
-  skip_before_filter :authenticate_user!,
+  skip_before_action :authenticate_user!,
                      :reject_blocked, :set_current_user_for_observers
 
   layout "explore"
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index b295f295bb10fbf0e1798a17af79500c4279f7ec..b1b0a2514dc0858ff30ffcb74dd10ec1e2e6232f 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,5 +1,5 @@
 class Explore::ProjectsController < ApplicationController
-  skip_before_filter :authenticate_user!,
+  skip_before_action :authenticate_user!,
                      :reject_blocked
 
   layout 'explore'
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 265cf4f0f4a13544fa5abbec2f6398472bab068a..5648a652e8e8a5da4e4f0c8891695c0e48e1077a 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -1,10 +1,10 @@
 class Groups::GroupMembersController < Groups::ApplicationController
-  skip_before_filter :authenticate_user!, only: [:index]
-  before_filter :group
+  skip_before_action :authenticate_user!, only: [:index]
+  before_action :group
 
   # Authorize
-  before_filter :authorize_read_group!
-  before_filter :authorize_admin_group!, except: [:index, :leave]
+  before_action :authorize_read_group!
+  before_action :authorize_admin_group!, except: [:index, :leave]
 
   layout :determine_layout
 
@@ -49,7 +49,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
 
   def resend_invite
     redirect_path = group_group_members_path(@group)
-    
+
     @group_member = @group.group_members.find(params[:id])
 
     if @group_member.invite?
@@ -63,7 +63,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
 
   def leave
     @group_member = @group.group_members.where(user_id: current_user.id).first
-    
+
     if can?(current_user, :destroy_group_member, @group_member)
       @group_member.destroy
       redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 546ff2cc71fd26007473436b242e203b57858e09..41564b04a92482bd297e042d4e9029daf8aab23a 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -1,7 +1,7 @@
 class Groups::MilestonesController < ApplicationController
   layout 'group'
 
-  before_filter :authorize_group_milestone!, only: :update
+  before_action :authorize_group_milestone!, only: :update
 
   def index
     project_milestones = case params[:state]
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 7af3c077182228637d1691653336fcc6ea4f5caf..294af0b170450ef6a198a0b11e00bf4bd4813aed 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,17 +1,17 @@
 class GroupsController < Groups::ApplicationController
-  skip_before_filter :authenticate_user!, only: [:show, :issues, :merge_requests]
+  skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests]
   respond_to :html
-  before_filter :group, except: [:new, :create]
+  before_action :group, except: [:new, :create]
 
   # Authorize
-  before_filter :authorize_read_group!, except: [:new, :create]
-  before_filter :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
-  before_filter :authorize_create_group!, only: [:new, :create]
+  before_action :authorize_read_group!, except: [:new, :create]
+  before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
+  before_action :authorize_create_group!, only: [:new, :create]
 
   # Load group projects
-  before_filter :load_projects, except: [:new, :create, :projects, :edit, :update]
-  before_filter :event_filter, only: :show
-  before_filter :set_title, only: [:new, :create]
+  before_action :load_projects, except: [:new, :create, :projects, :edit, :update]
+  before_action :event_filter, only: :show
+  before_action :set_title, only: [:new, :create]
 
   layout :determine_layout
 
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index bb8d7e0235c69c253220845d9a45f1f3ae1f05bc..ca78a4aaa8e5e4705b12994c31304b8c6439e165 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -1,15 +1,15 @@
 class Import::BitbucketController < Import::BaseController
-  before_filter :verify_bitbucket_import_enabled
-  before_filter :bitbucket_auth, except: :callback
+  before_action :verify_bitbucket_import_enabled
+  before_action :bitbucket_auth, except: :callback
 
   rescue_from OAuth::Error, with: :bitbucket_unauthorized
 
   def callback
-    request_token = session.delete(:oauth_request_token) 
+    request_token = session.delete(:oauth_request_token)
     raise "Session expired!" if request_token.nil?
 
     request_token.symbolize_keys!
-    
+
     access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
 
     current_user.bitbucket_access_token = access_token.token
@@ -21,7 +21,7 @@ class Import::BitbucketController < Import::BaseController
 
   def status
     @repos = client.projects
-    
+
     @already_added_projects = current_user.created_projects.where(import_type: "bitbucket")
     already_added_projects_names = @already_added_projects.pluck(:import_source)
 
@@ -41,7 +41,7 @@ class Import::BitbucketController < Import::BaseController
     repo_owner = repo["owner"]
     repo_owner = current_user.username if repo_owner == client.user["user"]["username"]
     @target_namespace = params[:new_namespace].presence || repo_owner
-    
+
     namespace = get_or_create_namespace || (render and return)
 
     unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 87b41454c778e965b60ff3c7ddb399db0f33b9d2..b9f99c1b88ad75f46f74772bd1dc7bcf0e0effb5 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -1,6 +1,6 @@
 class Import::GithubController < Import::BaseController
-  before_filter :verify_github_import_enabled
-  before_filter :github_auth, except: :callback
+  before_action :verify_github_import_enabled
+  before_action :github_auth, except: :callback
 
   rescue_from Octokit::Unauthorized, with: :github_unauthorized
 
@@ -36,7 +36,7 @@ class Import::GithubController < Import::BaseController
     repo_owner = repo.owner.login
     repo_owner = current_user.username if repo_owner == client.user.login
     @target_namespace = params[:new_namespace].presence || repo_owner
-    
+
     namespace = get_or_create_namespace || (render and return)
 
     @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index bddbfded81246e8cc6bfbb02b4764b81ccead695..1b8962d89244b223b87a7de46e7e34e0a8f4d774 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -1,6 +1,6 @@
 class Import::GitlabController < Import::BaseController
-  before_filter :verify_gitlab_import_enabled
-  before_filter :gitlab_auth, except: :callback
+  before_action :verify_gitlab_import_enabled
+  before_action :gitlab_auth, except: :callback
 
   rescue_from OAuth2::Error, with: :gitlab_unauthorized
 
@@ -13,7 +13,7 @@ class Import::GitlabController < Import::BaseController
 
   def status
     @repos = client.projects
-    
+
     @already_added_projects = current_user.created_projects.where(import_type: "gitlab")
     already_added_projects_names = @already_added_projects.pluck(:import_source)
 
@@ -33,7 +33,7 @@ class Import::GitlabController < Import::BaseController
     repo_owner = repo["namespace"]["path"]
     repo_owner = current_user.username if repo_owner == client.user["username"]
     @target_namespace = params[:new_namespace].presence || repo_owner
-    
+
     namespace = get_or_create_namespace || (render and return)
 
     @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 73c912e285bf86193e982c9cb23a9351322d7daf..5adf6ed7853d655e471a590c5a7f394b28d45c30 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -1,8 +1,8 @@
 class Import::GoogleCodeController < Import::BaseController
-  before_filter :user_map, only: [:new_user_map, :create_user_map]
+  before_action :user_map, only: [:new_user_map, :create_user_map]
 
   def new
-    
+
   end
 
   def callback
@@ -68,7 +68,7 @@ class Import::GoogleCodeController < Import::BaseController
 
   def status
     unless client.valid?
-      return redirect_to new_import_google_path 
+      return redirect_to new_import_google_code_path
     end
 
     @repos = client.repos
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 1f97ff16c5519a33086fe82120af3a298843c80b..a29c03395f42ca43a94cf61d7a473efc32500c87 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -1,6 +1,6 @@
 class InvitesController < ApplicationController
-  before_filter :member
-  skip_before_filter :authenticate_user!, only: :decline
+  before_action :member
+  skip_before_action :authenticate_user!, only: :decline
 
   respond_to :html
 
@@ -24,7 +24,7 @@ class InvitesController < ApplicationController
     if member.decline_invite!
       label, _ = source_info(member.source)
 
-      path = 
+      path =
         if current_user
           dashboard_path
         else
@@ -41,7 +41,7 @@ class InvitesController < ApplicationController
 
   def member
     return @member if defined?(@member)
-    
+
     @token = params[:id]
     @member = Member.find_by_invite_token(@token)
 
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 386d103ee5a629a046d5127c34751dfff00b388b..83eec1bf4a2645ea9aa5987ca9379681b52e6b4b 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -1,5 +1,5 @@
 class NamespacesController < ApplicationController
-  skip_before_filter :authenticate_user!
+  skip_before_action :authenticate_user!
 
   def show
     namespace = Namespace.find_by(path: params[:id])
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index efa291d9397559f5e9c1eb1e3f0f388a51b4daa3..ea256de2c3e53ce6f59b4745fb29a906ef27d97f 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,5 +1,5 @@
 class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
-  before_filter :authenticate_user!
+  before_action :authenticate_user!
   layout "profile"
 
   def index
@@ -10,7 +10,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
     @application = Doorkeeper::Application.new(application_params)
 
     @application.owner = current_user
-    
+
     if @application.save
       flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
       redirect_to oauth_application_url(@application)
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index a57b4a60c24824950f14f8b2f74fe4d479c3b549..6d3c1a320db1d0ca1ee30e86ada197c4c8c89ff9 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -1,5 +1,5 @@
 class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
-  before_filter :authenticate_resource_owner!
+  before_action :authenticate_resource_owner!
   layout "profile"
 
   def new
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 4e2bd0a9b4b92c9730f6ec9522a8e2f1e6f992b3..b0a5a631c63e3282a4aed088b7ad2749a7ad7581 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -1,6 +1,6 @@
 class Profiles::KeysController < ApplicationController
   layout "profile"
-  skip_before_filter :authenticate_user!, only: [:get_keys]
+  skip_before_action :authenticate_user!, only: [:get_keys]
 
   def index
     @keys = current_user.keys
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 0c614969a3fe8626cad43d97bece80f869dd2fb1..b719a7fe9a9ca495c522542243ce653fb2056637 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -1,11 +1,11 @@
 class Profiles::PasswordsController < ApplicationController
   layout :determine_layout
 
-  skip_before_filter :check_password_expiration, only: [:new, :create]
+  skip_before_action :check_password_expiration, only: [:new, :create]
 
-  before_filter :set_user
-  before_filter :set_title
-  before_filter :authorize_change_password!
+  before_action :set_user
+  before_action :set_title
+  before_action :authorize_change_password!
 
   def new
   end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 7f76906066d2015b096f2190ed8b35c920dc419d..eb001e8d73955ed5b8028e06e34cc3c5b74b06e5 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -1,9 +1,9 @@
 class ProfilesController < ApplicationController
   include ActionView::Helpers::SanitizeHelper
 
-  before_filter :user
-  before_filter :authorize_change_username!, only: :update_username
-  skip_before_filter :require_email, only: [:show, :update]
+  before_action :user
+  before_action :authorize_change_username!, only: :update_username
+  skip_before_action :require_email, only: [:show, :update]
 
   layout 'profile'
 
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 4719933394fca0ef749385e01a1a2bff8c435831..f7a28e920d190d139444d713509e04396ff4af85 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,6 +1,6 @@
 class Projects::ApplicationController < ApplicationController
-  before_filter :project
-  before_filter :repository
+  before_action :project
+  before_action :repository
   layout :determine_layout
 
   def authenticate_user!
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index a482b90880da9fdf0e935a4d34d657777551732a..22a12c4b9ae025e4d4d2f6ebd495e3648095c0f3 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -1,7 +1,7 @@
 class Projects::AvatarsController < Projects::ApplicationController
   layout 'project'
 
-  before_filter :project
+  before_action :project
 
   def show
     @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index a87b8270a22eb37a365f285662897d6cb8885b84..3362264dccee2f940f3a72c37580d1b0b68e4d4e 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -2,9 +2,9 @@
 class Projects::BlameController < Projects::ApplicationController
   include ExtractsPath
 
-  before_filter :require_non_empty_project
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def show
     @blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 4b7eb4df298ee50c84d315a279dbd20a1c5b0c9b..b762518d377fb5dc2ab6670b53f8d3513b4a6e56 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -6,15 +6,15 @@ class Projects::BlobController < Projects::ApplicationController
   # Raised when given an invalid file path
   class InvalidPathError < StandardError; end
 
-  before_filter :require_non_empty_project, except: [:new, :create]
-  before_filter :authorize_download_code!
-  before_filter :authorize_push_code!, only: [:destroy]
-  before_filter :assign_blob_vars
-  before_filter :commit, except: [:new, :create]
-  before_filter :blob, except: [:new, :create]
-  before_filter :from_merge_request, only: [:edit, :update]
-  before_filter :after_edit_path, only: [:edit, :update]
-  before_filter :require_branch_head, only: [:edit, :update]
+  before_action :require_non_empty_project, except: [:new, :create]
+  before_action :authorize_download_code!
+  before_action :authorize_push_code!, only: [:destroy]
+  before_action :assign_blob_vars
+  before_action :commit, except: [:new, :create]
+  before_action :blob, except: [:new, :create]
+  before_action :from_merge_request, only: [:edit, :update]
+  before_action :after_edit_path, only: [:edit, :update]
+  before_action :require_branch_head, only: [:edit, :update]
 
   def new
     commit unless @repository.empty?
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index f049e96e61df4fcee5ed91daf7f24c60aeb8a798..696011b94b9bb23d7d563ac85736f20d15f62480 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,9 +1,9 @@
 class Projects::BranchesController < Projects::ApplicationController
   include ActionView::Helpers::SanitizeHelper
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
-  before_filter :authorize_push_code!, only: [:create, :destroy]
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
+  before_action :authorize_push_code!, only: [:create, :destroy]
 
   def index
     @sort = params[:sort] || 'name'
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 894cf93b9ae7c624aa5cb2c6ca05f81bc80ddee2..8a1b7899be3d0172aeb088de81f8b7f71ef21e45 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,9 +3,9 @@
 # Not to be confused with CommitsController, plural.
 class Projects::CommitController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
-  before_filter :commit
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
+  before_action :commit
 
   def show
     return git_not_found! unless @commit
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 4b6ab43747625eb2dc91501b37bc7c51bd98e7e7..d1c15174aea8ead1d12e15302fc17f04bbae3693 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -3,9 +3,9 @@ require "base64"
 class Projects::CommitsController < Projects::ApplicationController
   include ExtractsPath
 
-  before_filter :require_non_empty_project
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def show
     @repo = @project.repository
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 146808fa5620c93b9bbb6c85949393ccbbfc51b0..03e6c381275b4d4fefa6f09786325103718a18f9 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,7 +1,7 @@
 class Projects::CompareController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
 
   def index
   end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 6fba3ce299bf119ab808331105181e3e12e2fb74..8c1bbf76917f17b3e11ad11cbf1697f50d0ff650 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -2,7 +2,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
   respond_to :html
 
   # Authorize
-  before_filter :authorize_admin_project!
+  before_action :authorize_admin_project!
 
   layout "project_settings"
 
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 21a151a426e57fe3295f40061123ad63ee04f6a1..01a079d2e6593d9a3bdb6e05d5d9bd3778d655e7 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -1,7 +1,7 @@
 class Projects::ForksController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
 
   def new
     @namespaces = current_user.manageable_namespaces
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 6e54af356e0c96c8e1d60b9e62b0060baf6e4efc..a060ea6f9989d448fd8e48d05d05cf5a69a2f8e2 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,7 +1,7 @@
 class Projects::GraphsController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
 
   def show
     respond_to do |format|
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index ba95bb13e1fbae73408a512da0948705f61c073a..57fc48ac7da5ab5523b8a80b775cd56387e6a5c1 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -1,6 +1,6 @@
 class Projects::HooksController < Projects::ApplicationController
   # Authorize
-  before_filter :authorize_admin_project!
+  before_action :authorize_admin_project!
 
   respond_to :html
 
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index b64491b4666b9a18cd30a24aaea42d99ed8c2893..066b66014f8725ce6c7dd50db8d472d4208e1957 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -1,8 +1,8 @@
 class Projects::ImportsController < Projects::ApplicationController
   # Authorize
-  before_filter :authorize_admin_project!
-  before_filter :require_no_repo
-  before_filter :redirect_if_progress, except: :show
+  before_action :authorize_admin_project!
+  before_action :require_no_repo
+  before_action :redirect_if_progress, except: :show
 
   def new
   end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 88302276b5ee59974886c60a40ea47d74cdbe770..c524e1a0ea306477469d88ccecdc8f0e270fcb49 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,18 +1,18 @@
 class Projects::IssuesController < Projects::ApplicationController
-  before_filter :module_enabled
-  before_filter :issue, only: [:edit, :update, :show, :toggle_subscription]
+  before_action :module_enabled
+  before_action :issue, only: [:edit, :update, :show, :toggle_subscription]
 
   # Allow read any issue
-  before_filter :authorize_read_issue!
+  before_action :authorize_read_issue!
 
   # Allow write(create) issue
-  before_filter :authorize_write_issue!, only: [:new, :create]
+  before_action :authorize_write_issue!, only: [:new, :create]
 
   # Allow modify issue
-  before_filter :authorize_modify_issue!, only: [:edit, :update]
+  before_action :authorize_modify_issue!, only: [:edit, :update]
 
   # Allow issues bulk update
-  before_filter :authorize_admin_issues!, only: [:bulk_update]
+  before_action :authorize_admin_issues!, only: [:bulk_update]
 
   respond_to :html
 
@@ -99,7 +99,7 @@ class Projects::IssuesController < Projects::ApplicationController
 
   def toggle_subscription
     @issue.toggle_subscription(current_user)
-    
+
     render nothing: true
   end
 
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 207a01ed3b0ef6c82669fc3055397af307d69394..2f8cb203cf9e77259c9cec78d5ab4ea50e6cb026 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -1,8 +1,8 @@
 class Projects::LabelsController < Projects::ApplicationController
-  before_filter :module_enabled
-  before_filter :label, only: [:edit, :update, :destroy]
-  before_filter :authorize_labels!
-  before_filter :authorize_admin_labels!, except: [:index]
+  before_action :module_enabled
+  before_action :label, only: [:edit, :update, :destroy]
+  before_action :authorize_labels!
+  before_action :authorize_admin_labels!, except: [:index]
 
   respond_to :js, :html
 
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 47ce8467358b4c72804fe8ca71b79c7c28d84fac..ab75f2ddb7351f5dd22007de9e6787134d24dcd2 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,20 +1,20 @@
 require 'gitlab/satellite/satellite'
 
 class Projects::MergeRequestsController < Projects::ApplicationController
-  before_filter :module_enabled
-  before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
-  before_filter :closes_issues, only: [:edit, :update, :show, :diffs]
-  before_filter :validates_merge_request, only: [:show, :diffs]
-  before_filter :define_show_vars, only: [:show, :diffs]
+  before_action :module_enabled
+  before_action :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
+  before_action :closes_issues, only: [:edit, :update, :show, :diffs]
+  before_action :validates_merge_request, only: [:show, :diffs]
+  before_action :define_show_vars, only: [:show, :diffs]
 
   # Allow read any merge_request
-  before_filter :authorize_read_merge_request!
+  before_action :authorize_read_merge_request!
 
   # Allow write(create) merge_request
-  before_filter :authorize_write_merge_request!, only: [:new, :create]
+  before_action :authorize_write_merge_request!, only: [:new, :create]
 
   # Allow modify merge_request
-  before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
+  before_action :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
 
   def index
     terms = params['issue_search']
@@ -176,7 +176,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def toggle_subscription
     @merge_request.toggle_subscription(current_user)
-    
+
     render nothing: true
   end
 
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index b49b549547ab149278935b942996f79eeacebaca..61689488d13b24961453086462a414c13b16c0bf 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -1,12 +1,12 @@
 class Projects::MilestonesController < Projects::ApplicationController
-  before_filter :module_enabled
-  before_filter :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests]
+  before_action :module_enabled
+  before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests]
 
   # Allow read any milestone
-  before_filter :authorize_read_milestone!
+  before_action :authorize_read_milestone!
 
   # Allow admin milestone
-  before_filter :authorize_admin_milestone!, except: [:index, :show]
+  before_action :authorize_admin_milestone!, except: [:index, :show]
 
   respond_to :html
 
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 83d1c1dacae6d5c4afa83ef10bdf520c95c76d21..06aef91cadd6bcb497204a6295330ef13dfdc358 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -2,9 +2,9 @@ class Projects::NetworkController < Projects::ApplicationController
   include ExtractsPath
   include ApplicationHelper
 
-  before_filter :require_non_empty_project
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def show
     respond_to do |format|
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 868629a0bc4aee1f33e7e55cc154dda8efe4217c..496b85cb46de02f26bc192cf1ce4d7dadd8b2b2c 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,9 +1,9 @@
 class Projects::NotesController < Projects::ApplicationController
   # Authorize
-  before_filter :authorize_read_note!
-  before_filter :authorize_write_note!, only: [:create]
-  before_filter :authorize_admin_note!, only: [:update, :destroy]
-  before_filter :find_current_user_notes, except: [:destroy, :delete_attachment]
+  before_action :authorize_read_note!
+  before_action :authorize_write_note!, only: [:create]
+  before_action :authorize_admin_note!, only: [:update, :destroy]
+  before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
 
   def index
     current_fetched_at = Time.now.to_i
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 72967a26ff12c6fe40f8451d1cb55ddc8e026f4c..d7fbc979067790c7265c4a092efcd3bed7128f03 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,6 +1,6 @@
 class Projects::ProjectMembersController < Projects::ApplicationController
   # Authorize
-  before_filter :authorize_admin_project!, except: :leave
+  before_action :authorize_admin_project!, except: :leave
 
   layout "project_settings"
 
@@ -24,7 +24,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
         users = @group.users.search(params[:search]).to_a
         @group_members = @group_members.where(user_id: users)
       end
-      
+
       @group_members = @group_members.order('access_level DESC').limit(20)
     end
 
@@ -62,7 +62,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
     redirect_path = namespace_project_project_members_path(@project.namespace, @project)
 
     @project_member = @project.project_members.find(params[:id])
-    
+
     if @project_member.invite?
       @project_member.resend_invite
 
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index ac36ac6fcd3902470c175f6310132f53856f25dd..6b52eccebf73a6f49406052ad28994b82324a39c 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -1,7 +1,7 @@
 class Projects::ProtectedBranchesController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_admin_project!
+  before_action :require_non_empty_project
+  before_action :authorize_admin_project!
 
   layout "project_settings"
 
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index b1a029ce69618c432c75ee6585d38c5242eb13d0..647c145407827101daa20d52a3c5bd8fa3346d94 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -2,9 +2,9 @@
 class Projects::RawController < Projects::ApplicationController
   include ExtractsPath
 
-  before_filter :require_non_empty_project
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def show
     @blob = @repository.blob_at(@commit.id, @path)
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index ec3b2b8d75a56d8d125555d5ed7fb373a8beb348..01ca1537c0e66316229fa9df52f7bed145d19b20 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -1,9 +1,9 @@
 class Projects::RefsController < Projects::ApplicationController
   include ExtractsPath
 
-  before_filter :require_non_empty_project
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def switch
     respond_to do |format|
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 96defb0c721c52837a77a45a434e849ace335b06..c4a5e2d63598f624c6a7990cb64913cd65cc769e 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,8 +1,8 @@
 class Projects::RepositoriesController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project, except: :create
-  before_filter :authorize_download_code!
-  before_filter :authorize_admin_project!, only: :create
+  before_action :require_non_empty_project, except: :create
+  before_action :authorize_download_code!
+  before_action :authorize_admin_project!, only: :create
 
   def create
     @project.create_repository
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 9a484c109baf9d449f5cbdf88ea9ec3240927eb0..ae8146dca590b94b883c1c0bc5d39b46d8acd8a2 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -1,7 +1,7 @@
 class Projects::ServicesController < Projects::ApplicationController
   # Authorize
-  before_filter :authorize_admin_project!
-  before_filter :service, only: [:edit, :update, :test]
+  before_action :authorize_admin_project!
+  before_action :service, only: [:edit, :update, :test]
 
   respond_to :html
 
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index ed26840037360227877ddaa92fc275520f00ede8..3d75abcc29d5171912058796b0d93c0e66561288 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,18 +1,18 @@
 class Projects::SnippetsController < Projects::ApplicationController
-  before_filter :module_enabled
-  before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
+  before_action :module_enabled
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read any snippet
-  before_filter :authorize_read_project_snippet!
+  before_action :authorize_read_project_snippet!
 
   # Allow write(create) snippet
-  before_filter :authorize_write_project_snippet!, only: [:new, :create]
+  before_action :authorize_write_project_snippet!, only: [:new, :create]
 
   # Allow modify snippet
-  before_filter :authorize_modify_project_snippet!, only: [:edit, :update]
+  before_action :authorize_modify_project_snippet!, only: [:edit, :update]
 
   # Allow destroy snippet
-  before_filter :authorize_admin_project_snippet!, only: [:destroy]
+  before_action :authorize_admin_project_snippet!, only: [:destroy]
 
   respond_to :html
 
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 83f4937bce36838f944b786684e014cfff440f3e..f565fbbbbc3fd9a2dcf9c52479cebbcd504c9944 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,9 +1,9 @@
 class Projects::TagsController < Projects::ApplicationController
   # Authorize
-  before_filter :require_non_empty_project
-  before_filter :authorize_download_code!
-  before_filter :authorize_push_code!, only: [:create]
-  before_filter :authorize_admin_project!, only: [:destroy]
+  before_action :require_non_empty_project
+  before_action :authorize_download_code!
+  before_action :authorize_push_code!, only: [:create]
+  before_action :authorize_admin_project!, only: [:destroy]
 
   def index
     sorted = VersionSorter.rsort(@repository.tag_names)
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index b23010bf59546dd664002c89c74789b466d10f3b..b659e15f2429f9fdb03cdeb448b171e9ec3415b0 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -2,9 +2,9 @@
 class Projects::TreeController < Projects::ApplicationController
   include ExtractsPath
 
-  before_filter :require_non_empty_project, except: [:new, :create]
-  before_filter :assign_ref_vars
-  before_filter :authorize_download_code!
+  before_action :require_non_empty_project, except: [:new, :create]
+  before_action :assign_ref_vars
+  before_action :authorize_download_code!
 
   def show
     if tree.entries.empty?
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 276dced865678e1f85bf72f2307bc39a2cb3e16a..6153ca2dc1bbe9cbb50fdf9f060533094a3015c2 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,11 +1,11 @@
 class Projects::UploadsController < Projects::ApplicationController
   layout 'project'
 
-  # We want to skip these filters for only the `show` action if `image?` is true, 
+  # We want to skip these filters for only the `show` action if `image?` is true,
   # but `skip_before_filter` doesn't work with both `only` and `if`, so we accomplish the same like this.
   skipped_filters = [:authenticate_user!, :reject_blocked!, :project, :repository]
-  skip_before_filter  *skipped_filters, only: [:show]
-  before_filter       *skipped_filters, only: [:show], unless: :image?
+  skip_before_action  *skipped_filters, only: [:show]
+  before_action       *skipped_filters, only: [:show], unless: :image?
 
   def create
     link_to_file = ::Projects::UploadService.new(project, params[:file]).
@@ -40,7 +40,7 @@ class Projects::UploadsController < Projects::ApplicationController
     file_project = Project.find_with_namespace("#{namespace}/#{id}")
 
     if file_project.nil?
-      @uploader = nil 
+      @uploader = nil
       return
     end
 
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index aeb7f0699f577d74e4c67848608f71bb5db35672..36ef86e190973374af9fab4cfcf1675e4c067e2a 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -1,10 +1,10 @@
 require 'project_wiki'
 
 class Projects::WikisController < Projects::ApplicationController
-  before_filter :authorize_read_wiki!
-  before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
-  before_filter :authorize_admin_wiki!, only: :destroy
-  before_filter :load_project_wiki
+  before_action :authorize_read_wiki!
+  before_action :authorize_write_wiki!, only: [:edit, :create, :history]
+  before_action :authorize_admin_wiki!, only: :destroy
+  before_action :load_project_wiki
   include WikiHelper
 
   def pages
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 0f28794b736e66a6c10b6f1f706beffeacb6bd9e..1422f2b8a4c40df9289d1a1f1be534c8199271e9 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,13 +1,13 @@
 class ProjectsController < ApplicationController
   prepend_before_filter :render_go_import, only: [:show]
-  skip_before_filter :authenticate_user!, only: [:show]
-  before_filter :project, except: [:new, :create]
-  before_filter :repository, except: [:new, :create]
+  skip_before_action :authenticate_user!, only: [:show]
+  before_action :project, except: [:new, :create]
+  before_action :repository, except: [:new, :create]
 
   # Authorize
-  before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
-  before_filter :set_title, only: [:new, :create]
-  before_filter :event_filter, only: :show
+  before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+  before_action :set_title, only: [:new, :create]
+  before_action :event_filter, only: :show
 
   layout 'navless', only: [:new, :create, :fork]
 
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 38d116a4ee3595bdedee46370726c7300dfa37d9..830751a989f25042cd994fa4bb3892776ab8a894 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,5 +1,5 @@
 class RegistrationsController < Devise::RegistrationsController
-  before_filter :signup_enabled?
+  before_action :signup_enabled?
 
   def new
     redirect_to(new_user_session_path)
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 6c84dbbb9984679a7cc55ad2c64288641af37614..a5259466cb8d2b6853b07c27375cbb9e8235235c 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,11 +1,15 @@
 class SnippetsController < ApplicationController
-  before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
-  before_filter :authorize_modify_snippet!, only: [:edit, :update]
-  before_filter :authorize_admin_snippet!, only: [:destroy]
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
-  before_filter :set_title
+  # Allow modify snippet
+  before_action :authorize_modify_snippet!, only: [:edit, :update]
 
-  skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw]
+  # Allow destroy snippet
+  before_action :authorize_admin_snippet!, only: [:destroy]
+
+  before_action :set_title
+
+  skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw]
 
   respond_to :html
 
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index c5f3da54ea23b63338585b4242fdac5f32463cd0..17edff68be27cf075750818b250261ee2f57cdee 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -1,6 +1,6 @@
 class UploadsController < ApplicationController
-  skip_before_filter :authenticate_user!
-  before_filter :find_model, :authorize_access!
+  skip_before_action :authenticate_user!
+  before_action :find_model, :authorize_access!
 
   def show
     uploader = @model.send(upload_mount)
@@ -28,7 +28,7 @@ class UploadsController < ApplicationController
   end
 
   def authorize_access!
-    authorized = 
+    authorized =
       case @model
       when Project
         can?(current_user, :read_project, @model)
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 679d6897ce97a38db06b7578387b431f6731c522..460cc868b35cee1b7aef1942d4dda40408fc9779 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,6 +1,6 @@
 class UsersController < ApplicationController
-  skip_before_filter :authenticate_user!
-  before_filter :set_user
+  skip_before_action :authenticate_user!
+  before_action :set_user
   layout :determine_layout
 
   def show
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 5c77af729d0c25067efc255eb7749a8fa4689d5e..a6844b2a47b84221d4ad5d724a798ecef05e3cd2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -255,11 +255,15 @@ module ApplicationHelper
   #
   # Returns `html_options`, adding `rel: nofollow` for external links
   def add_nofollow(link, html_options = {})
-    uri = URI(link)
+    begin
+      uri = URI(link)
 
-    if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host
-      rel = html_options.fetch(:rel, '')
-      html_options[:rel] = (rel + ' nofollow').strip
+      if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host
+        rel = html_options.fetch(:rel, '')
+        html_options[:rel] = (rel + ' nofollow').strip
+      end
+    rescue URI::Error
+      # noop
     end
 
     html_options
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index aa1de2f50efb0731540988b2382d971e3869921c..aff7011edd022bf5c17d96d144e870d66c2f9075 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -74,6 +74,7 @@ module GitlabMarkdownHelper
     end
   end
 
+  # TODO (rspeicher): This should be its own filter
   def create_relative_links(text)
     paths = extract_paths(text)
 
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index ad4a7612724719dc36150946ec49958037ab723d..c3b4731dff303616d9689ca84e3d0686d0d5e425 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -108,4 +108,7 @@ module IssuesHelper
       xml.summary issue.title
     end
   end
+
+  # Required for Gitlab::Markdown::IssueReferenceFilter
+  module_function :url_for_issue, :title_for_issue
 end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 32ef2e7ca8416c7ea3cacb1961370d0bc4092c29..8272c177d59352b907715da6dfd1a20190a039c2 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,4 +1,6 @@
 module LabelsHelper
+  include ActionView::Helpers::TagHelper
+
   def project_label_names
     @project.labels.pluck(:title)
   end
@@ -7,9 +9,13 @@ module LabelsHelper
     label_color = label.color || Label::DEFAULT_COLOR
     text_color = text_color_for_bg(label_color)
 
-    content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do
-      label.name
-    end
+    # Intentionally not using content_tag here so that this method can be called
+    # by LabelReferenceFilter
+    span = %(<span class="label color-label") +
+      %( style="background-color: #{label_color}; color: #{text_color}">) +
+      escape_once(label.name) + '</span>'
+
+    span.html_safe
   end
 
   def suggested_colors
@@ -42,13 +48,16 @@ module LabelsHelper
     r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
 
     if (r + g + b) > 500
-      "#333"
+      '#333333'
     else
-      "#FFF"
+      '#FFFFFF'
     end
   end
 
   def project_labels_options(project)
     options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
   end
+
+  # Required for Gitlab::Markdown::LabelReferenceFilter
+  module_function :render_colored_label, :text_color_for_bg, :escape_once
 end
diff --git a/app/models/label.rb b/app/models/label.rb
index 1f22ed23d42d88792c265f1fec20dae4676f7ade..eee28acefc11cbe6317e68eccb66305318605fb8 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -27,7 +27,7 @@ class Label < ActiveRecord::Base
   # Don't allow '?', '&', and ',' for label titles
   validates :title,
             presence: true,
-            format: { with: /\A[^&\?,&]+\z/ },
+            format: { with: /\A[^&\?,]+\z/ },
             uniqueness: { scope: :project_id }
 
   default_scope { order(title: :asc) }
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index d264a56ebdf496582c6861139b1eb8a789195a1f..07520eab5d13164793824b186ecb693adb4314cd 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -50,8 +50,9 @@ class HipchatService < Service
 
   def execute(data)
     return unless supported_events.include?(data[:object_kind])
-
-    gate[room].send('GitLab', create_message(data))
+    message = create_message(data)
+    return unless message.present?
+    gate[room].send('GitLab', message)
   end
 
   private
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index eb7a099bfe266dd46051dbdc7a44d36a9a29dc42..34ca7776c9edb970a84d1aad1ab2a21029c225a3 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -3,7 +3,7 @@
 
 %p #{@service.description} template
 
-= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f|
+= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |form|
   - if @service.errors.any?
     #error_explanation
       .alert.alert-danger
@@ -15,80 +15,68 @@
         = markdown @service.help
 
   .form-group
-    = f.label :active, "Active", class: "control-label"
+    = form.label :active, "Active", class: "control-label"
     .col-sm-10
-      = f.check_box :active
+      = form.check_box :active
 
   - if @service.supported_events.length > 1
     .form-group
-      = f.label :url, "Trigger", class: 'control-label'
+      = form.label :url, "Trigger", class: 'control-label'
       .col-sm-10
         - if @service.supported_events.include?("push")
           %div
-            = f.check_box :push_events, class: 'pull-left'
+            = form.check_box :push_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :push_events, class: 'list-label' do
+              = form.label :push_events, class: 'list-label' do
                 %strong Push events
               %p.light
                 This url will be triggered by a push to the repository
         - if @service.supported_events.include?("tag_push")
           %div
-            = f.check_box :tag_push_events, class: 'pull-left'
+            = form.check_box :tag_push_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :tag_push_events, class: 'list-label' do
+              = form.label :tag_push_events, class: 'list-label' do
                 %strong Tag push events
               %p.light
                 This url will be triggered when a new tag is pushed to the repository
         - if @service.supported_events.include?("note")
           %div
-            = f.check_box :note_events, class: 'pull-left'
+            = form.check_box :note_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :note_events, class: 'list-label' do
+              = form.label :note_events, class: 'list-label' do
                 %strong Comments
               %p.light
                 This url will be triggered when someone adds a comment
         - if @service.supported_events.include?("issue")
           %div
-            = f.check_box :issues_events, class: 'pull-left'
+            = form.check_box :issues_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :issues_events, class: 'list-label' do
+              = form.label :issues_events, class: 'list-label' do
                 %strong Issues events
               %p.light
                 This url will be triggered when an issue is created
         - if @service.supported_events.include?("merge_request")
           %div
-            = f.check_box :merge_requests_events, class: 'pull-left'
+            = form.check_box :merge_requests_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :merge_requests_events, class: 'list-label' do
+              = form.label :merge_requests_events, class: 'list-label' do
                 %strong Merge Request events
               %p.light
                 This url will be triggered when a merge request is created
 
   - @service.fields.each do |field|
-    - name = field[:name]
-    - title = field[:title] || name.humanize
-    - value = @service.send(name) unless field[:type] == 'password'
     - type = field[:type]
-    - placeholder = field[:placeholder]
-    - choices = field[:choices]
-    - default_choice = field[:default_choice]
-    - help = field[:help]
 
-    .form-group
-      = f.label name, title, class: "control-label"
-      .col-sm-10
-        - if type == 'text'
-          = f.text_field name, class: "form-control", placeholder: placeholder
-        - elsif type == 'textarea'
-          = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
-        - elsif type == 'checkbox'
-          = f.check_box name
-        - elsif type == 'select'
-          = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
-        - elsif type == 'password'
-          = f.password_field name, class: 'form-control'
-        - if help
-          %span.help-block= help
+    - if type == 'fieldset'
+      - fields = field[:fields]
+      - legend = field[:legend]
+
+      %fieldset
+        %legend= legend
+        - fields.each do |subfield|
+          = render 'shared/field', form: form, field: subfield
+    - else
+      = render 'shared/field', form: form, field: field
 
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
+    = form.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 7b21ca30d8c4d7e7dde99773eda4b27c77ff6c0d..ae072bacfb17888e6c2cd5f941b8e63bca184e04 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -187,7 +187,11 @@
                 %td.shortcut
                   .key m
                 %td Change milestone
-            %tbody{ class: 'hidden-shortcut merge_reuests', style: 'display:none' }
+              %tr
+                %td.shortcut
+                  .key r
+                %td Reply (quoting selected text)
+            %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
               %tr
                 %th
                 %th Merge Requests
@@ -199,6 +203,10 @@
                 %td.shortcut
                   .key m
                 %td Change milestone
+              %tr
+                %td.shortcut
+                  .key r
+                %td Reply (quoting selected text)
 
 
 :javascript
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
index d55fcfc97a83f4ed2f90f45b523bcce849244c06..9c6824ecad7e49de7ff039f5093eacbb12041e3b 100644
--- a/app/views/import/google_code/new_user_map.html.haml
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -8,9 +8,31 @@
     Customize how Google Code email addresses and usernames are imported into GitLab.
     In the next step, you'll be able to select the projects you want to import.
   %p 
-    The user map is a JSON document mapping Google Code users (as keys) to the way they will be imported into GitLab (as values). By default the username is masked to ensure users' privacy.
-  %p
-    To map a Google Code user to a full name or GitLab user, simply replace the value, e.g. <code>"johnsmith@gmail.com": "John Smith"</code> or <code>"johnsmith@gmail.com": "@johnsmith"</code>. Be sure to preserve the surrounding double quotes and other punctuation.
+    The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
+  %ul
+    %li
+      %strong Default: Directly import the Google Code email address or username
+      %p
+        <code>"johnsmith@example.com": "johnsm...@example.com"</code> 
+        will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. 
+        The email address or username is masked to ensure the user's privacy.
+    %li
+      %strong Map a Google Code user to a GitLab user
+      %p
+        <code>"johnsmith@example.com": "@johnsmith"</code> 
+        will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, 
+        and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
+    %li
+      %strong Map a Google Code user to a full name
+      %p
+        <code>"johnsmith@example.com": "John Smith"</code> 
+        will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
+    %li
+      %strong Map a Google Code user to a full email address
+      %p
+        <code>"johnsmith@example.com": "johnsmith@example.com"</code> 
+        will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. 
+        By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
 
   .form-group
     .col-sm-12
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index f356a25dbfa23738996debf3fca8d7c94acd4511..b869fd6e12a0f7838919ca86cc498bdd6ca10cc3 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -9,5 +9,5 @@
 %div
   .md-write-holder
     = yield
-  .md-preview-holder.hide
+  .md.md-preview-holder.hide
     .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index d986ce67c0c6b2588e92bebd783a360bc4772445..4e72458932c45631c0e356a8740cc9b791e45aef 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -82,12 +82,12 @@
 
 .mr-compare.merge-request
   %ul.nav.nav-tabs.merge-request-tabs
-    %li.commits-tab{data: {action: 'commits'}}
+    %li.commits-tab{data: {action: 'commits', toggle: 'tab'}}
       = link_to url_for(params) do
         %i.fa.fa-history
         Commits
         %span.badge= @commits.size
-    %li.diffs-tab{data: {action: 'diffs'}}
+    %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}}
       = link_to url_for(params) do
         %i.fa.fa-list-alt
         Changes
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index ce6b7a0737a8c0fa344687710d8f2d2f0cae0666..1b465e3addd99eece9d3bd649ee937ba54956a25 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -10,7 +10,7 @@
 
 %hr
 
-= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
   - if @service.errors.any?
     .alert.alert-danger
       %ul
@@ -23,83 +23,71 @@
         = markdown @service.help
 
   .form-group
-    = f.label :active, "Active", class: "control-label"
+    = form.label :active, "Active", class: "control-label"
     .col-sm-10
-      = f.check_box :active
+      = form.check_box :active
 
   - if @service.supported_events.length > 1
     .form-group
-      = f.label :url, "Trigger", class: 'control-label'
+      = form.label :url, "Trigger", class: 'control-label'
       .col-sm-10
         - if @service.supported_events.include?("push")
           %div
-            = f.check_box :push_events, class: 'pull-left'
+            = form.check_box :push_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :push_events, class: 'list-label' do
+              = form.label :push_events, class: 'list-label' do
                 %strong Push events
               %p.light
                 This url will be triggered by a push to the repository
         - if @service.supported_events.include?("tag_push")
           %div
-            = f.check_box :tag_push_events, class: 'pull-left'
+            = form.check_box :tag_push_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :tag_push_events, class: 'list-label' do
+              = form.label :tag_push_events, class: 'list-label' do
                 %strong Tag push events
               %p.light
                 This url will be triggered when a new tag is pushed to the repository
         - if @service.supported_events.include?("note")
           %div
-            = f.check_box :note_events, class: 'pull-left'
+            = form.check_box :note_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :note_events, class: 'list-label' do
+              = form.label :note_events, class: 'list-label' do
                 %strong Comments
               %p.light
                 This url will be triggered when someone adds a comment
         - if @service.supported_events.include?("issue")
           %div
-            = f.check_box :issues_events, class: 'pull-left'
+            = form.check_box :issues_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :issues_events, class: 'list-label' do
+              = form.label :issues_events, class: 'list-label' do
                 %strong Issues events
               %p.light
                 This url will be triggered when an issue is created
         - if @service.supported_events.include?("merge_request")
           %div
-            = f.check_box :merge_requests_events, class: 'pull-left'
+            = form.check_box :merge_requests_events, class: 'pull-left'
             .prepend-left-20
-              = f.label :merge_requests_events, class: 'list-label' do
+              = form.label :merge_requests_events, class: 'list-label' do
                 %strong Merge Request events
               %p.light
                 This url will be triggered when a merge request is created
 
   - @service.fields.each do |field|
-    - name = field[:name]
-    - title = field[:title] || name.humanize
-    - value = service_field_value(field[:type], @service.send(name))
     - type = field[:type]
-    - placeholder = field[:placeholder]
-    - choices = field[:choices]
-    - default_choice = field[:default_choice]
-    - help = field[:help]
 
-    .form-group
-      = f.label name, title, class: "control-label"
-      .col-sm-10
-        - if type == 'text'
-          = f.text_field name, class: "form-control", placeholder: placeholder
-        - elsif type == 'textarea'
-          = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
-        - elsif type == 'checkbox'
-          = f.check_box name
-        - elsif type == 'select'
-          = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
-        - elsif type == 'password'
-          = f.password_field name, placeholder: value, class: 'form-control'
-        - if help
-          %span.help-block= help
+    - if type == 'fieldset'
+      - fields = field[:fields]
+      - legend = field[:legend]
+
+      %fieldset
+        %legend= legend
+        - fields.each do |subfield|
+          = render 'shared/field', form: form, field: subfield
+    - else
+      = render 'shared/field', form: form, field: field
 
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
+    = form.submit 'Save', class: 'btn btn-save'
     &nbsp;
     - if @service.valid? && @service.activated?
       - disabled = @service.can_test? ? '':'disabled'
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..30d37dceb308e8779b1623d853c929ba9c9bafde
--- /dev/null
+++ b/app/views/shared/_field.html.haml
@@ -0,0 +1,24 @@
+- name = field[:name]
+- title = field[:title] || name.humanize
+- value = service_field_value(field[:type], @service.send(name))
+- type = field[:type]
+- placeholder = field[:placeholder]
+- choices = field[:choices]
+- default_choice = field[:default_choice]
+- help = field[:help]
+
+.form-group
+  = form.label name, title, class: "control-label"
+  .col-sm-10
+    - if type == 'text'
+      = form.text_field name, class: "form-control", placeholder: placeholder
+    - elsif type == 'textarea'
+      = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder
+    - elsif type == 'checkbox'
+      = form.check_box name
+    - elsif type == 'select'
+      = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+    - elsif type == 'password'
+      = form.password_field name, placeholder: value, class: 'form-control'
+    - if help
+      %span.help-block= help
diff --git a/config/routes.rb b/config/routes.rb
index f95507017cbfc650bda44be0b3262aa9d1c4e521..86939a1d7eaf121d885f181fa2b268efe07e1b21 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,6 +2,7 @@ require 'sidekiq/web'
 require 'api/api'
 
 Gitlab::Application.routes.draw do
+  mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
   use_doorkeeper do
     controllers applications: 'oauth/applications',
                 authorized_applications: 'oauth/authorized_applications',
diff --git a/db/schema.rb b/db/schema.rb
index 1aee37b2e6184df956560e339fc6caee78911641..2b7e27e3a3165e243d5065a3be799d83914e560a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -470,7 +470,6 @@ ActiveRecord::Schema.define(version: 20150417122318) do
     t.integer  "notification_level",            default: 1,     null: false
     t.datetime "password_expires_at"
     t.integer  "created_by_id"
-    t.datetime "last_credential_check_at"
     t.string   "avatar"
     t.string   "confirmation_token"
     t.datetime "confirmed_at"
@@ -478,6 +477,7 @@ ActiveRecord::Schema.define(version: 20150417122318) do
     t.string   "unconfirmed_email"
     t.boolean  "hide_no_ssh_key",               default: false
     t.string   "website_url",                   default: "",    null: false
+    t.datetime "last_credential_check_at"
     t.string   "github_access_token"
     t.string   "gitlab_access_token"
     t.string   "notification_email"
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 2fd22c513ad4b84d1420baa2326611e067c3af29..84f1d74c058a158bd7ea9f2a770fc2b36601fe5a 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -16,7 +16,7 @@ To enable Slack integration you must create an Incoming WebHooks integration on
 
 1.  Choose the channel name you want to send notifications to
 
-1.  Click **Add Incoming WebHooks Integration**Add Integrations.
+1.  Click **Add Incoming WebHooks Integration**
     - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
 
 1. Copy the **Webhook URL**, we'll need this later for GitLab.
@@ -32,10 +32,15 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
 
 1.  Navigate to Settings -> Services -> Slack
 
-1.  Fill in your Slack details
+1. Pick the triggers you want to activate
 
+1.  Fill in your Slack details
+    - Webhook: Paste the Webhook URL from the step above
+    - Username: Fill this in if you want to change the username of the bot
+    - Channel: Fill this in if you want to change the channel where the messages will be posted
     - Mark it as active
-    - Paste in the webhook URL you got from Slack
+    
+1. Save your settings
 
 Have fun :)
 
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 1d5fd4c8b0dc0f3fcb1919124903530e0a6517de..8ec5a20035f9671f17736bcbf7824953b84f4509 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -163,7 +163,7 @@ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported
 
 ## Special GitLab References
 
-GFM recognized special references.
+GFM recognizes special references.
 
 You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
 
@@ -171,19 +171,30 @@ GFM will turn that reference into a link so you can navigate between them easily
 
 GFM will recognize the following:
 
-- @foo : for specific team members or groups
-- @all : for the whole team
-- #123 : for issues
-- !123 : for merge requests
-- $123 : for snippets
-- 1234567 : for commits
-- \[file\](path/to/file) : for file references
-
-GFM also recognizes references to commits, issues, and merge requests in other projects:
-
-- namespace/project#123 : for issues
-- namespace/project!123 : for merge requests
-- namespace/project@1234567 : for commits
+| input                  | references                 |
+|-----------------------:|:---------------------------|
+| `@user_name`           | specific user              |
+| `@group_name`          | specific group             |
+| `@all`                 | entire team                |
+| `#123`                 | issue                      |
+| `!123`                 | merge request              |
+| `$123`                 | snippet                    |
+| `~123`                 | label by ID                |
+| `~bug`                 | one-word label by name     |
+| `~"feature request"`   | multi-word label by name   |
+| `9ba12248`             | specific commit            |
+| `9ba12248...b19a04f5`  | commit range comparison    |
+| `[README](doc/README)` | repository file references |
+
+GFM also recognizes certain cross-project references:
+
+| input                                   | references              |
+|----------------------------------------:|:------------------------|
+| `namespace/project#123`                 | issue                   |
+| `namespace/project!123`                 | merge request           |
+| `namespace/project$123`                 | snippet                 |
+| `namespace/project@9ba12248`            | specific commit         |
+| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
 
 ## Task Lists
 
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 2e41fad89e78429fa79fddf2c80bc732d0e4426d..bfef975024f2792179970c0a8bbaa67b22bea302 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -162,6 +162,7 @@ Options:
 
 ```
 BACKUP=timestamp_of_backup (required if more than one backup exists)
+force=yes (do not ask if the authorized_keys file should get regenerated)
 ```
 
 Example output:
diff --git a/doc/update/6.x-or-7.x-to-7.10.md b/doc/update/6.x-or-7.x-to-7.10.md
index 2ee9a07cee3c906c9b71b6e3e340e2ac94a618be..39e12f32d0e2028ec85015a6a42a3722b653e3e7 100644
--- a/doc/update/6.x-or-7.x-to-7.10.md
+++ b/doc/update/6.x-or-7.x-to-7.10.md
@@ -126,7 +126,7 @@ sudo apt-get install nodejs
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.0
+sudo -u git -H git checkout v2.6.2
 ```
 
 ## 7. Install libs, migrations, etc.
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
index 28fd433e1c8272246a06fdd800194d9e67852b69..6ffa21c61411685e4cb15351d705dfff09ea59ba 100644
--- a/doc/update/7.8-to-7.9.md
+++ b/doc/update/7.8-to-7.9.md
@@ -42,6 +42,8 @@ sudo -u git -H git checkout v2.6.0
 
 ### 4. Install libs, migrations, etc.
 
+Please refer to the [Node.js setup documentation](https://github.com/joyent/node/wiki/installing-node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions) if you aren't running default GitLab server setup.
+
 ```bash
 sudo apt-get install nodejs
 
diff --git a/doc/update/7.9-to-7.10.md b/doc/update/7.9-to-7.10.md
new file mode 100644
index 0000000000000000000000000000000000000000..0512e7cde7c4382d1063c48129009598e1fb7650
--- /dev/null
+++ b/doc/update/7.9-to-7.10.md
@@ -0,0 +1,118 @@
+# From 7.9 to 7.10
+
+### 0. Stop server
+
+    sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-10-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-10-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.2
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+    sudo service gitlab start
+    sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+    sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.9)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.8 to 7.8](7.8-to-7.9.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
\ No newline at end of file
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index f62a53d3340c22a670b07e37b55fae2489190d64..e32984c4e68f2fbcf1893740e505b7f3638d112a 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -26,12 +26,11 @@ If you have local changes to your GitLab repository the script will stash them a
 
 Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
 
-    # Starting with GitLab version 7.0 upgrader script has been moved to bin directory
     cd /home/git/gitlab
-    if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb; else sudo -u git -H ruby script/upgrade.rb; fi
+    sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute'
 
     # to perform a non-interactive install (no user input required) you can add -y
-    # if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi
+    # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y
 
 ## 3. Start application
 
@@ -66,11 +65,12 @@ Here is a one line command with step 1 to 5 for the next time you upgrade:
 cd /home/git/gitlab; \
   sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
   sudo service gitlab stop; \
-  if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
+  sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \
   cd /home/git/gitlab-shell; \
   sudo -u git -H git fetch; \
   sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
   cd /home/git/gitlab; \
   sudo service gitlab start; \
-  sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+  sudo service nginx restart; \
+  sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
 ```
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index b5e82563ff1f6d4bae0e50128b9d24c75805254f..70bfe0597760c7141b2ff81ee3647216d0da7b20 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -30,7 +30,10 @@ module Gitlab
 
       def user_map
         @user_map ||= begin
-          user_map = Hash.new { |hash, user| Client.mask_email(user) }
+          user_map = Hash.new do |hash, user| 
+            # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked.
+            Client.mask_email(user).sub("...", "\\.\\.\\.")
+          end
 
           import_data = project.import_data.try(:data)
           stored_user_map = import_data["user_map"] if import_data
@@ -203,25 +206,25 @@ module Gitlab
       end
 
       def linkify_issues(s)
-        s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
+        s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
+        s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2')
+        s
       end
 
       def escape_for_markdown(s)
-        s = s.gsub("*", "\\*")
-        s = s.gsub("#", "\\#")
+        # No headings and lists
+        s = s.gsub(/^#/, "\\#")
+        s = s.gsub(/^-/, "\\-")
+
+        # No inline code
         s = s.gsub("`", "\\`")
-        s = s.gsub(":", "\\:")
-        s = s.gsub("-", "\\-")
-        s = s.gsub("+", "\\+")
-        s = s.gsub("_", "\\_")
-        s = s.gsub("(", "\\(")
-        s = s.gsub(")", "\\)")
-        s = s.gsub("[", "\\[")
-        s = s.gsub("]", "\\]")
-        s = s.gsub("<", "\\<")
-        s = s.gsub(">", "\\>")
+
+        # Carriage returns make me sad
         s = s.gsub("\r", "")
+
+        # Markdown ignores single newlines, but we need them as <br />.
         s = s.gsub("\n", "  \n")
+
         s
       end
 
@@ -276,11 +279,18 @@ module Gitlab
         if raw_updates.has_key?("blockedOn")
           blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on|
             name, id = raw_blocked_on.split(":", 2)
-            if name == project.import_source
-              "##{id}"
-            else
-              "#{project.namespace.path}/#{name}##{id}"
-            end
+
+            deleted = name.start_with?("-") 
+            name = name[1..-1] if deleted
+
+            text =
+              if name == project.import_source
+                "##{id}"
+              else
+                "#{project.namespace.path}/#{name}##{id}"
+              end
+            text = "~~#{text}~~" if deleted
+            text
           end
           updates << "*Blocked on: #{blocked_ons.join(", ")}*"
         end
@@ -288,11 +298,18 @@ module Gitlab
         if raw_updates.has_key?("blocking")
           blockings = raw_updates["blocking"].map do |raw_blocked_on|
             name, id = raw_blocked_on.split(":", 2)
-            if name == project.import_source
-              "##{id}"
-            else
-              "#{project.namespace.path}/#{name}##{id}"
-            end
+            
+            deleted = name.start_with?("-") 
+            name = name[1..-1] if deleted
+
+            text =
+              if name == project.import_source
+                "##{id}"
+              else
+                "#{project.namespace.path}/#{name}##{id}"
+              end
+            text = "~~#{text}~~" if deleted
+            text
           end
           updates << "*Blocking: #{blockings.join(", ")}*"
         end
@@ -340,7 +357,7 @@ module Gitlab
 
       def format_issue_body(author, date, content, attachments)
         body = []
-        body << "*By #{author} on #{date}*"
+        body << "*By #{author} on #{date} (imported from Google Code)*"
         body << "---"
 
         if content.blank?
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 47c456d8dc747224a81aa8831c8514c2ffda9b91..37b250d353edf247e8781ea97a2fa6dca3463862 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,5 +1,4 @@
 require 'html/pipeline'
-require 'html/pipeline/gitlab'
 
 module Gitlab
   # Custom parser for GitLab-flavored Markdown
@@ -10,11 +9,11 @@ module Gitlab
   # Supported reference formats are:
   #   * @foo for team members
   #   * #123 for issues
-  #   * #JIRA-123 for Jira issues
+  #   * JIRA-123 for Jira issues
   #   * !123 for merge requests
   #   * $123 for snippets
-  #   * 123456 for commits
-  #   * 123456...7890123 for commit ranges (comparisons)
+  #   * 1c002d for specific commit
+  #   * 1c002d...35cfb2 for commit ranges (comparisons)
   #
   # It also parses Emoji codes to insert images. See
   # http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
@@ -30,10 +29,6 @@ module Gitlab
   #   >> gfm(":trollface:")
   #   => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
   module Markdown
-    include IssuesHelper
-
-    attr_reader :options, :html_options
-
     # Public: Parse the provided text with GitLab-Flavored Markdown
     #
     # text         - the source text
@@ -65,47 +60,24 @@ module Gitlab
         reference_only_path:  true
       )
 
-      @options      = options
-      @html_options = html_options
-
-      # TODO: add popups with additional information
+      pipeline = HTML::Pipeline.new(filters)
 
-      # Used markdown pipelines in GitLab:
-      # GitlabEmojiFilter - performs emoji replacement.
-      # SanitizationFilter - remove unsafe HTML tags and attributes
-      #
-      # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
-      filters = [
-        HTML::Pipeline::Gitlab::GitlabEmojiFilter,
-        HTML::Pipeline::SanitizationFilter
-      ]
+      context = {
+        # SanitizationFilter
+        whitelist:       sanitization_whitelist,
 
-      whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
-      whitelist[:attributes][:all].push('class', 'id')
-      whitelist[:elements].push('span')
-
-      # Remove the rel attribute that the sanitize gem adds, and remove the
-      # href attribute if it contains inline javascript
-      fix_anchors = lambda do |env|
-        name, node = env[:node_name], env[:node]
-        if name == 'a'
-          node.remove_attribute('rel')
-          if node['href'] && node['href'].match('javascript:')
-            node.remove_attribute('href')
-          end
-        end
-      end
-      whitelist[:transformers].push(fix_anchors)
+        # EmojiFilter
+        asset_root:      Gitlab.config.gitlab.url,
+        asset_host:      Gitlab::Application.config.asset_host,
 
-      markdown_context = {
-              asset_root: Gitlab.config.gitlab.url,
-              asset_host: Gitlab::Application.config.asset_host,
-              whitelist: whitelist
+        # ReferenceFilter
+        current_user:    current_user,
+        only_path:       options[:reference_only_path],
+        project:         project,
+        reference_class: html_options[:class]
       }
 
-      markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
-
-      result = markdown_pipeline.call(text, markdown_context)
+      result = pipeline.call(text, context)
 
       save_options = 0
       if options[:xhtml]
@@ -114,21 +86,6 @@ module Gitlab
 
       text = result[:output].to_html(save_with: save_options)
 
-      # Extract pre blocks so they are not altered
-      # from http://github.github.com/github-flavored-markdown/
-      text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
-      # Extract links with probably parsable hrefs
-      text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
-      # Extract images with probably parsable src
-      text.gsub!(%r{<img.*?>}m) { |match| extract_piece(match) }
-
-      text = parse(text, project)
-
-      # Insert pre block extractions
-      text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
-        insert_piece($1)
-      end
-
       if options[:parse_tasks]
         text = parse_tasks(text)
       end
@@ -138,242 +95,53 @@ module Gitlab
 
     private
 
-    def extract_piece(text)
-      @extractions ||= {}
-
-      md5 = Digest::MD5.hexdigest(text)
-      @extractions[md5] = text
-      "{gfm-extraction-#{md5}}"
-    end
-
-    def insert_piece(id)
-      @extractions[id]
-    end
-
-    # Private: Parses text for references
+    # Filters used in our pipeline
     #
-    # text - Text to parse
+    # SanitizationFilter should come first so that all generated reference HTML
+    # goes through untouched.
     #
-    # Returns parsed text
-    def parse(text, project = @project)
-      parse_references(text, project) if project
-
-      text
-    end
-
-    NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR
-    PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
-
-    REFERENCE_PATTERN = %r{
-      (?<prefix>\W)?                         # Prefix
-      (                                      # Reference
-         @(?<user>#{NAME_STR})               # User name
-        |~(?<label>\d+)                      # Label ID
-        |(?<issue>([A-Z\-]+-)\d+)            # JIRA Issue ID
-        |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
-        |#{PROJ_STR}?!(?<merge_request>\d+)  # MR ID
-        |\$(?<snippet>\d+)                   # Snippet ID
-        |(#{PROJ_STR}@)?(?<commit_range>[\h]{6,40}\.{2,3}[\h]{6,40}) # Commit range
-        |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID
-        |(?<skip>gfm-extraction-[\h]{6,40})  # Skip gfm extractions. Otherwise will be parsed as commit
-      )
-      (?<suffix>\W)?                         # Suffix
-    }x.freeze
-
-    TYPES = [:user, :issue, :label, :merge_request, :snippet, :commit, :commit_range].freeze
-
-    def parse_references(text, project = @project)
-      # parse reference links
-      text.gsub!(REFERENCE_PATTERN) do |match|
-        type       = TYPES.select{|t| !$~[t].nil?}.first
-
-        actual_project = project
-        project_prefix = nil
-        project_path = $LAST_MATCH_INFO[:project]
-        if project_path
-          actual_project = ::Project.find_with_namespace(project_path)
-          actual_project = nil unless can?(current_user, :read_project, actual_project)
-          project_prefix = project_path
-        end
-
-        parse_result($LAST_MATCH_INFO, type,
-                     actual_project, project_prefix) || match
-      end
-    end
-
-    # Called from #parse_references.  Attempts to build a gitlab reference
-    # link.  Returns nil if +type+ is nil, if the match string is an HTML
-    # entity, if the reference is invalid, or if the matched text includes an
-    # invalid project path.
-    def parse_result(match_info, type, project, project_prefix)
-      prefix = match_info[:prefix]
-      suffix = match_info[:suffix]
-
-      return nil if html_entity?(prefix, suffix) || type.nil?
-      return nil if project.nil? && !project_prefix.nil?
-
-      identifier = match_info[type]
-      ref_link = reference_link(type, identifier, project, project_prefix)
-
-      if ref_link
-        "#{prefix}#{ref_link}#{suffix}"
-      else
-        nil
-      end
-    end
-
-    # Return true if the +prefix+ and +suffix+ indicate that the matched string
-    # is an HTML entity like &amp;
-    def html_entity?(prefix, suffix)
-      prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+    # See https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
+    def filters
+      [
+        HTML::Pipeline::SanitizationFilter,
+
+        Gitlab::Markdown::EmojiFilter,
+
+        Gitlab::Markdown::UserReferenceFilter,
+        Gitlab::Markdown::IssueReferenceFilter,
+        Gitlab::Markdown::ExternalIssueReferenceFilter,
+        Gitlab::Markdown::MergeRequestReferenceFilter,
+        Gitlab::Markdown::SnippetReferenceFilter,
+        Gitlab::Markdown::CommitRangeReferenceFilter,
+        Gitlab::Markdown::CommitReferenceFilter,
+        Gitlab::Markdown::LabelReferenceFilter,
+      ]
     end
 
-    # Private: Dispatches to a dedicated processing method based on reference
-    #
-    # reference  - Object reference ("@1234", "!567", etc.)
-    # identifier - Object identifier (Issue ID, SHA hash, etc.)
+    # Customize the SanitizationFilter whitelist
     #
-    # Returns string rendered by the processing method
-    def reference_link(type, identifier, project = @project, prefix_text = nil)
-      send("reference_#{type}", identifier, project, prefix_text)
-    end
-
-    def reference_user(identifier, project = @project, _ = nil)
-      link_options = html_options.merge(
-          class: "gfm gfm-project_member #{html_options[:class]}"
-        )
+    # - Allow `class` and `id` attributes on all elements
+    # - Allow `span` elements
+    # - Remove `rel` attributes from `a` elements
+    # - Remove `a` nodes with `javascript:` in the `href` attribute
+    def sanitization_whitelist
+      whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
+      whitelist[:attributes][:all].push('class', 'id')
+      whitelist[:elements].push('span')
 
-      if identifier == "all"
-        link_to(
-          "@all",
-          namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]),
-          link_options
-        )
-      elsif namespace = Namespace.find_by(path: identifier)
-        url =
-          if namespace.is_a?(Group)
-            return nil unless can?(current_user, :read_group, namespace)
-            group_url(identifier, only_path: options[:reference_only_path])
-          else
-            user_url(identifier, only_path: options[:reference_only_path])
+      fix_anchors = lambda do |env|
+        name, node = env[:node_name], env[:node]
+        if name == 'a'
+          node.remove_attribute('rel')
+          if node['href'] && node['href'].match('javascript:')
+            node.remove_attribute('href')
           end
-
-        link_to("@#{identifier}", url, link_options)
-      end
-    end
-
-    def reference_label(identifier, project = @project, _ = nil)
-      if label = project.labels.find_by(id: identifier)
-        link_options = html_options.merge(
-          class: "gfm gfm-label #{html_options[:class]}"
-        )
-        link_to(
-          render_colored_label(label),
-          namespace_project_issues_path(project.namespace, project, label_name: label.name),
-          link_options
-        )
-      end
-    end
-
-    def reference_issue(identifier, project = @project, prefix_text = nil)
-      if project.default_issues_tracker?
-        if project.issue_exists? identifier
-          url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
-          title = title_for_issue(identifier, project)
-          link_options = html_options.merge(
-            title: "Issue: #{title}",
-            class: "gfm gfm-issue #{html_options[:class]}"
-          )
-
-          link_to("#{prefix_text}##{identifier}", url, link_options)
-        end
-      else
-        if project.external_issue_tracker.present?
-          reference_external_issue(identifier, project,
-                                   prefix_text)
         end
       end
-    end
-
-    def reference_merge_request(identifier, project = @project, prefix_text = nil)
-      if merge_request = project.merge_requests.find_by(iid: identifier)
-        link_options = html_options.merge(
-          title: "Merge Request: #{merge_request.title}",
-          class: "gfm gfm-merge_request #{html_options[:class]}"
-        )
-        url = namespace_project_merge_request_url(project.namespace, project,
-                                                  merge_request,
-                                                  only_path: options[:reference_only_path])
-        link_to("#{prefix_text}!#{identifier}", url, link_options)
-      end
-    end
-
-    def reference_snippet(identifier, project = @project, _ = nil)
-      if snippet = project.snippets.find_by(id: identifier)
-        link_options = html_options.merge(
-          title: "Snippet: #{snippet.title}",
-          class: "gfm gfm-snippet #{html_options[:class]}"
-        )
-        link_to(
-          "$#{identifier}",
-          namespace_project_snippet_url(project.namespace, project, snippet,
-                                        only_path: options[:reference_only_path]),
-          link_options
-        )
-      end
-    end
-
-    def reference_commit(identifier, project = @project, prefix_text = nil)
-      if project.valid_repo? && commit = project.repository.commit(identifier)
-        link_options = html_options.merge(
-          title: commit.link_title,
-          class: "gfm gfm-commit #{html_options[:class]}"
-        )
-        prefix_text = "#{prefix_text}@" if prefix_text
-        link_to(
-          "#{prefix_text}#{identifier}",
-          namespace_project_commit_url( project.namespace, project, commit,
-                                        only_path: options[:reference_only_path]),
-          link_options
-        )
-      end
-    end
-
-    def reference_commit_range(identifier, project = @project, prefix_text = nil)
-      from_id, to_id = identifier.split(/\.{2,3}/, 2)
-
-      inclusive = identifier !~ /\.{3}/
-      from_id << "^" if inclusive
-
-      if project.valid_repo? &&
-          from = project.repository.commit(from_id) &&
-          to = project.repository.commit(to_id)
-
-        link_options = html_options.merge(
-          title: "Commits #{from_id} through #{to_id}",
-          class: "gfm gfm-commit_range #{html_options[:class]}"
-        )
-        prefix_text = "#{prefix_text}@" if prefix_text
 
-        link_to(
-          "#{prefix_text}#{identifier}",
-          namespace_project_compare_url(project.namespace, project,
-                                        from: from_id, to: to_id,
-                                        only_path: options[:reference_only_path]),
-          link_options
-        )
-      end
-    end
-
-    def reference_external_issue(identifier, project = @project, prefix_text = nil)
-      url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
-      title = project.external_issue_tracker.title
+      whitelist[:transformers].push(fix_anchors)
 
-      link_options = html_options.merge(
-        title: "Issue in #{title}",
-        class: "gfm gfm-issue #{html_options[:class]}"
-      )
-      link_to("#{prefix_text}##{identifier}", url, link_options)
+      whitelist
     end
 
     # Turn list items that start with "[ ]" into HTML checkbox inputs.
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1128c1bed7a3eecf47590966ee7e0156f04218a3
--- /dev/null
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -0,0 +1,105 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces commit range references with links.
+    #
+    # This filter supports cross-project references.
+    class CommitRangeReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      # 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(COMMIT_RANGE_PATTERN) do |match|
+          yield match, $~[:commit_range], $~[:project]
+        end
+      end
+
+      def initialize(*args)
+        super
+
+        @commit_map = {}
+      end
+
+      # Pattern used to extract commit range references from text
+      #
+      # The beginning and ending SHA1 sums can be between 6 and 40 hex
+      # characters, and the range selection can be double- or triple-dot.
+      #
+      # This pattern supports cross-project references.
+      COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>\h{6,40}\.{2,3}\h{6,40})/
+
+      def call
+        replace_text_nodes_matching(COMMIT_RANGE_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, commit_range, project_ref|
+          project = self.project_from_ref(project_ref)
+
+          from_id, to_id = split_commit_range(commit_range)
+
+          if valid_range?(project, from_id, to_id)
+            url = url_for_commit_range(project, from_id, to_id)
+
+            title = "Commits #{from_id} through #{to_id}"
+            klass = reference_class(:commit_range)
+
+            project_ref += '@' if project_ref
+
+            %(<a href="#{url}"
+                 title="#{title}"
+                 class="#{klass}">#{project_ref}#{commit_range}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def split_commit_range(range)
+        from_id, to_id = range.split(/\.{2,3}/, 2)
+        from_id << "^" if range !~ /\.{3}/
+
+        [from_id, to_id]
+      end
+
+      def commit(id)
+        unless @commit_map[id]
+          @commit_map[id] = project.repository.commit(id)
+        end
+
+        @commit_map[id]
+      end
+
+      def valid_range?(project, from_id, to_id)
+        project && project.valid_repo? && commit(from_id) && commit(to_id)
+      end
+
+      def url_for_commit_range(project, from_id, to_id)
+        h = Rails.application.routes.url_helpers
+        h.namespace_project_compare_url(project.namespace, project,
+                                        from: from_id, to: to_id,
+                                        only_path: context[:only_path])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..745de6402cf1ac77c8f3ee5a5375e7ff01012cab
--- /dev/null
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -0,0 +1,80 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces commit references with links.
+    #
+    # This filter supports cross-project references.
+    class CommitReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      # 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_PATTERN) do |match|
+          yield match, $~[:commit], $~[:project]
+        end
+      end
+
+      # Pattern used to extract commit references from text
+      #
+      # The SHA1 sum can be between 6 and 40 hex characters.
+      #
+      # This pattern supports cross-project references.
+      COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit>\h{6,40})/
+
+      def call
+        replace_text_nodes_matching(COMMIT_PATTERN) do |content|
+          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, commit_ref, project_ref|
+          project = self.project_from_ref(project_ref)
+
+          if commit = commit_from_ref(project, commit_ref)
+            url = url_for_commit(project, commit)
+
+            title = escape_once(commit.link_title)
+            klass = reference_class(:commit)
+
+            project_ref += '@' if project_ref
+
+            %(<a href="#{url}"
+                 title="#{title}"
+                 class="#{klass}">#{project_ref}#{commit_ref}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def commit_from_ref(project, commit_ref)
+        if project && project.valid_repo?
+          project.repository.commit(commit_ref)
+        end
+      end
+
+      def url_for_commit(project, commit)
+        h = Rails.application.routes.url_helpers
+        h.namespace_project_commit_url(project.namespace, project, commit,
+                                        only_path: context[:only_path])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
new file mode 100644
index 0000000000000000000000000000000000000000..887c205cdc96c9487afb8ac297baf6fd5ea76e37
--- /dev/null
+++ b/lib/gitlab/markdown/cross_project_reference.rb
@@ -0,0 +1,32 @@
+module Gitlab
+  module Markdown
+    # Common methods for ReferenceFilters that support an optional cross-project
+    # reference.
+    module CrossProjectReference
+      NAMING_PATTERN  = Gitlab::Regex::NAMESPACE_REGEX_STR
+      PROJECT_PATTERN = "(?<project>#{NAMING_PATTERN}/#{NAMING_PATTERN})"
+
+      # Given a cross-project reference string, get the Project record
+      #
+      # Defaults to value of `context[:project]` if:
+      # * No reference is given OR
+      # * Reference given doesn't exist
+      #
+      # ref - String reference.
+      #
+      # Returns a Project, or nil if the reference can't be accessed
+      def project_from_ref(ref)
+        return context[:project] unless ref
+
+        other = Project.find_with_namespace(ref)
+        return nil unless other && user_can_reference_project?(other)
+        
+        other
+      end
+
+      def user_can_reference_project?(project, user = context[:current_user])
+        Ability.abilities.allowed?(user, :read_project, project)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/gitlab/markdown/emoji_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e239f76684411c8f91d6060ea16ee691315b1ff8
--- /dev/null
+++ b/lib/gitlab/markdown/emoji_filter.rb
@@ -0,0 +1,79 @@
+require 'gitlab_emoji'
+require 'html/pipeline/filter'
+require 'action_controller'
+
+module Gitlab
+  module Markdown
+    # HTML filter that replaces :emoji: with images.
+    #
+    # Based on HTML::Pipeline::EmojiFilter
+    #
+    # Context options:
+    #   :asset_root
+    #   :asset_host
+    class EmojiFilter < HTML::Pipeline::Filter
+      IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
+
+      def call
+        doc.search('text()').each do |node|
+          content = node.to_html
+          next unless content.include?(':')
+          next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
+
+          html = emoji_image_filter(content)
+
+          next if html == content
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
+      # Replace :emoji: with corresponding images.
+      #
+      # text - String text to replace :emoji: in.
+      #
+      # Returns a String with :emoji: replaced with images.
+      def emoji_image_filter(text)
+        text.gsub(emoji_pattern) do |match|
+          name = $1
+          "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{emoji_url(name)}' height='20' width='20' align='absmiddle' />"
+        end
+      end
+
+      private
+
+      def emoji_url(name)
+        emoji_path = "emoji/#{emoji_filename(name)}"
+        if context[:asset_host]
+          # Asset host is specified.
+          url_to_image(emoji_path)
+        elsif context[:asset_root]
+          # Gitlab url is specified
+          File.join(context[:asset_root], url_to_image(emoji_path))
+        else
+          # All other cases
+          url_to_image(emoji_path)
+        end
+      end
+
+      def url_to_image(image)
+        ActionController::Base.helpers.url_to_image(image)
+      end
+
+      # Build a regexp that matches all valid :emoji: names.
+      def self.emoji_pattern
+        @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+      end
+
+      def emoji_pattern
+        self.class.emoji_pattern
+      end
+
+      def emoji_filename(name)
+        "#{Emoji.emoji_filename(name)}.png"
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0fc3f4cca06915bf835548bf6b1789d53ff39b1f
--- /dev/null
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -0,0 +1,63 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces external issue tracker references with links.
+    # References are ignored if the project doesn't use an external issue
+    # tracker.
+    class ExternalIssueReferenceFilter < ReferenceFilter
+      # Public: Find `JIRA-123` issue references in text
+      #
+      #   ExternalIssueReferenceFilter.references_in(text) do |match, issue|
+      #     "<a href=...>##{issue}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match and the String issue reference.
+      #
+      # Returns a String replaced with the return of the block.
+      def self.references_in(text)
+        text.gsub(ISSUE_PATTERN) do |match|
+          yield match, $~[:issue]
+        end
+      end
+
+      # Pattern used to extract `JIRA-123` issue references from text
+      ISSUE_PATTERN = /(?<issue>([A-Z\-]+-)\d+)/
+
+      def call
+        # Early return if the project isn't using an external tracker
+        return doc if project.nil? || project.default_issues_tracker?
+
+        replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+          issue_link_filter(content)
+        end
+      end
+
+      # Replace `JIRA-123` issue references in text with links to the referenced
+      # issue's details page.
+      #
+      # text - String text to replace references in.
+      #
+      # 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)
+        project = context[:project]
+
+        self.class.references_in(text) do |match, issue|
+          url = url_for_issue(issue, project, only_path: context[:only_path])
+
+          title = escape_once("Issue in #{project.external_issue_tracker.title}")
+          klass = reference_class(:issue)
+
+          %(<a href="#{url}"
+               title="#{title}"
+               class="#{klass}">#{issue}</a>)
+        end
+      end
+
+      def url_for_issue(*args)
+        IssuesHelper.url_for_issue(*args)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c9267cc3e9d90c46bd41df347d5fa3f8967d70f4
--- /dev/null
+++ b/lib/gitlab/markdown/issue_reference_filter.rb
@@ -0,0 +1,74 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces issue references with links. References to
+    # issues that do not exist are ignored.
+    #
+    # This filter supports cross-project references.
+    class IssueReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      # Public: Find `#123` issue references in text
+      #
+      #   IssueReferenceFilter.references_in(text) do |match, issue, project_ref|
+      #     "<a href=...>##{issue}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, the Integer issue ID, 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(ISSUE_PATTERN) do |match|
+          yield match, $~[:issue].to_i, $~[:project]
+        end
+      end
+
+      # Pattern used to extract `#123` issue references from text
+      #
+      # This pattern supports cross-project references.
+      ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?<issue>([a-zA-Z\-]+-)?\d+)/
+
+      def call
+        replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+          issue_link_filter(content)
+        end
+      end
+
+      # Replace `#123` issue references in text with links to the referenced
+      # issue's details page.
+      #
+      # text - String text to replace references in.
+      #
+      # Returns a String with `#123` references replaced with links. All links
+      # have `gfm` and `gfm-issue` class names attached for styling.
+      def issue_link_filter(text)
+        self.class.references_in(text) do |match, issue, project_ref|
+          project = self.project_from_ref(project_ref)
+
+          if project && project.issue_exists?(issue)
+            url = url_for_issue(issue, project, only_path: context[:only_path])
+
+            title = escape_once("Issue: #{title_for_issue(issue, project)}")
+            klass = reference_class(:issue)
+
+            %(<a href="#{url}"
+                 title="#{title}"
+                 class="#{klass}">#{project_ref}##{issue}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def url_for_issue(*args)
+        IssuesHelper.url_for_issue(*args)
+      end
+
+      def title_for_issue(*args)
+        IssuesHelper.title_for_issue(*args)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4c21192c0d3cb4ba2cacd4228fc9084e10f97606
--- /dev/null
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -0,0 +1,94 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces label references with links.
+    class LabelReferenceFilter < ReferenceFilter
+      # Public: Find label references in text
+      #
+      #   LabelReferenceFilter.references_in(text) do |match, id, name|
+      #     "<a href=...>#{Label.find(id)}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, an optional Integer label ID, and an optional
+      # String label name.
+      #
+      # Returns a String replaced with the return of the block.
+      def self.references_in(text)
+        text.gsub(LABEL_PATTERN) do |match|
+          yield match, $~[:label_id].to_i, $~[:label_name]
+        end
+      end
+
+      # Pattern used to extract label references from text
+      #
+      # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad).
+      LABEL_PATTERN = %r{
+        ~(
+          (?<label_id>\d+)   | # Integer-based label ID, or
+          (?<label_name>
+            [A-Za-z0-9_-]+   | # String-based single-word label title
+            ['"][^&\?,]+['"]   # String-based multi-word label surrounded in quotes
+          )
+        )
+      }x
+
+      def call
+        replace_text_nodes_matching(LABEL_PATTERN) do |content|
+          label_link_filter(content)
+        end
+      end
+
+      # Replace label references in text with links to the label specified.
+      #
+      # text - String text to replace references in.
+      #
+      # 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)
+        project = context[:project]
+
+        self.class.references_in(text) do |match, id, name|
+          params = label_params(id, name)
+
+          if label = project.labels.find_by(params)
+            url = url_for_label(project, label)
+
+            klass = reference_class(:label)
+
+            %(<a href="#{url}" class="#{klass}">#{render_colored_label(label)}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def url_for_label(project, label)
+        h = Rails.application.routes.url_helpers
+        h.namespace_project_issues_path(project.namespace, project,
+                                        label_name: label.name,
+                                        only_path: context[:only_path])
+      end
+
+      def render_colored_label(label)
+        LabelsHelper.render_colored_label(label)
+      end
+
+      # Parameters to pass to `Label.find_by` based on the given arguments
+      #
+      # id   - Integer ID to pass. If present, returns {id: id}
+      # name - String name to pass. If `id` is absent, finds by name without
+      #        surrounding quotes.
+      #
+      # Returns a Hash.
+      def label_params(id, name)
+        if id > 0
+          { id: id }
+        else
+          # TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding.
+          { name: name.tr('\'"', '') }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40239523cdaa33188a4c436b36b84a7ab0d0b2f3
--- /dev/null
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -0,0 +1,73 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces merge request references with links. References
+    # to merge requests that do not exist are ignored.
+    #
+    # This filter supports cross-project references.
+    class MergeRequestReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      # Public: Find `!123` merge request references in text
+      #
+      #   MergeRequestReferenceFilter.references_in(text) do |match, merge_request, project_ref|
+      #     "<a href=...>##{merge_request}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, the Integer merge request ID, 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(MERGE_REQUEST_PATTERN) do |match|
+          yield match, $~[:merge_request].to_i, $~[:project]
+        end
+      end
+
+      # Pattern used to extract `!123` merge request references from text
+      #
+      # This pattern supports cross-project references.
+      MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?<merge_request>\d+)/
+
+      def call
+        replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content|
+          merge_request_link_filter(content)
+        end
+      end
+
+      # Replace `!123` merge request references in text with links to the
+      # referenced merge request's details page.
+      #
+      # text - String text to replace references in.
+      #
+      # Returns a String with `!123` references replaced with links. All links
+      # have `gfm` and `gfm-merge_request` class names attached for styling.
+      def merge_request_link_filter(text)
+        self.class.references_in(text) do |match, id, project_ref|
+          project = self.project_from_ref(project_ref)
+
+          if project && merge_request = project.merge_requests.find_by(iid: id)
+            title = escape_once("Merge Request: #{merge_request.title}")
+            klass = reference_class(:merge_request)
+
+            url = url_for_merge_request(merge_request, project)
+
+            %(<a href="#{url}"
+                 title="#{title}"
+                 class="#{klass}">#{project_ref}!#{id}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      # TODO (rspeicher): Cleanup
+      def url_for_merge_request(mr, project)
+        h = Rails.application.routes.url_helpers
+        h.namespace_project_merge_request_url(project.namespace, project, mr,
+                                            only_path: context[:only_path])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26663c8d990d41c3e53e48e43be44e0452c3b2ac
--- /dev/null
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -0,0 +1,76 @@
+require 'active_support/core_ext/string/output_safety'
+require 'html/pipeline'
+
+module Gitlab
+  module Markdown
+    # Base class for GitLab Flavored Markdown reference filters.
+    #
+    # References within <pre>, <code>, <a>, and <style> elements are ignored.
+    #
+    # Context options:
+    #   :project (required) - Current project, ignored if reference is cross-project.
+    #   :reference_class    - Custom CSS class added to reference links.
+    #   :only_path          - Generate path-only links.
+    #
+    class ReferenceFilter < HTML::Pipeline::Filter
+      def escape_once(html)
+        ERB::Util.html_escape_once(html)
+      end
+
+      # Don't look for references in text nodes that are children of these
+      # elements.
+      IGNORE_PARENTS = %w(pre code a style).to_set
+
+      def ignored_ancestry?(node)
+        has_ancestor?(node, IGNORE_PARENTS)
+      end
+
+      def project
+        context[:project]
+      end
+
+      def reference_class(type)
+        "gfm gfm-#{type} #{context[:reference_class]}".strip
+      end
+
+      # Iterate through the document's text nodes, yielding the current node's
+      # content if:
+      #
+      # * The `project` context value is present AND
+      # * The node's content matches `pattern` AND
+      # * The node is not an ancestor of an ignored node type
+      #
+      # 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's existing content and update the current document.
+      #
+      # Returns the updated Nokogiri::XML::Document object.
+      def replace_text_nodes_matching(pattern)
+        return doc if project.nil?
+
+        doc.search('text()').each do |node|
+          content = node.to_html
+
+          next unless content.match(pattern)
+          next if ignored_ancestry?(node)
+
+          html = yield content
+
+          next if html == content
+
+          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!
+      def validate
+        needs :project
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ada67de992b575a6c543f497ffcf3bd53b7ac56b
--- /dev/null
+++ b/lib/gitlab/markdown/snippet_reference_filter.rb
@@ -0,0 +1,72 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces snippet references with links. References to
+    # snippets that do not exist are ignored.
+    #
+    # This filter supports cross-project references.
+    class SnippetReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      # Public: Find `$123` snippet references in text
+      #
+      #   SnippetReferenceFilter.references_in(text) do |match, snippet|
+      #     "<a href=...>$#{snippet}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, the Integer snippet ID, 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(SNIPPET_PATTERN) do |match|
+          yield match, $~[:snippet].to_i, $~[:project]
+        end
+      end
+
+      # Pattern used to extract `$123` snippet references from text
+      #
+      # This pattern supports cross-project references.
+      SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?<snippet>\d+)/
+
+      def call
+        replace_text_nodes_matching(SNIPPET_PATTERN) do |content|
+          snippet_link_filter(content)
+        end
+      end
+
+      # Replace `$123` snippet references in text with links to the referenced
+      # snippets's details page.
+      #
+      # text - String text to replace references in.
+      #
+      # Returns a String with `$123` references replaced with links. All links
+      # have `gfm` and `gfm-snippet` class names attached for styling.
+      def snippet_link_filter(text)
+        self.class.references_in(text) do |match, id, project_ref|
+          project = self.project_from_ref(project_ref)
+
+          if project && snippet = project.snippets.find_by(id: id)
+            title = escape_once("Snippet: #{snippet.title}")
+            klass = reference_class(:snippet)
+
+            url = url_for_snippet(snippet, project)
+
+            %(<a href="#{url}"
+                 title="#{title}"
+                 class="#{klass}">#{project_ref}$#{id}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def url_for_snippet(snippet, project)
+        h = Rails.application.routes.url_helpers
+        h.namespace_project_snippet_url(project.namespace, project, snippet,
+                                        only_path: context[:only_path])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5fc8ed55fe26f114459f52e1ce95f3746b9a1190
--- /dev/null
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -0,0 +1,92 @@
+module Gitlab
+  module Markdown
+    # HTML filter that replaces user or group references with links.
+    #
+    # A special `@all` reference is also supported.
+    class UserReferenceFilter < ReferenceFilter
+      # Public: Find `@user` user references in text
+      #
+      #   UserReferenceFilter.references_in(text) do |match, username|
+      #     "<a href=...>@#{user}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, and the String user name.
+      #
+      # Returns a String replaced with the return of the block.
+      def self.references_in(text)
+        text.gsub(USER_PATTERN) do |match|
+          yield match, $~[:user]
+        end
+      end
+
+      # Pattern used to extract `@user` user references from text
+      USER_PATTERN = /@(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})/
+
+      def call
+        replace_text_nodes_matching(USER_PATTERN) do |content|
+          user_link_filter(content)
+        end
+      end
+
+      # Replace `@user` user references in text with links to the referenced
+      # user's profile page.
+      #
+      # text - String text to replace references in.
+      #
+      # 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)
+        project = context[:project]
+
+        self.class.references_in(text) do |match, user|
+          klass = reference_class(:project_member)
+
+          if user == 'all'
+            url = link_to_all(project)
+
+            %(<a href="#{url}" class="#{klass}">@#{user}</a>)
+          elsif namespace = Namespace.find_by(path: user)
+            if namespace.is_a?(Group)
+              if user_can_reference_group?(namespace)
+                url = group_url(user, only_path: context[:only_path])
+                %(<a href="#{url}" class="#{klass}">@#{user}</a>)
+              else
+                match
+              end
+            else
+              url = user_url(user, only_path: context[:only_path])
+              %(<a href="#{url}" class="#{klass}">@#{user}</a>)
+            end
+          else
+            match
+          end
+        end
+      end
+
+      private
+
+      def urls
+        Rails.application.routes.url_helpers
+      end
+
+      def group_url(*args)
+        urls.group_url(*args)
+      end
+
+      def user_url(*args)
+        urls.user_url(*args)
+      end
+
+      def link_to_all(project)
+        urls.namespace_project_url(project.namespace, project,
+                                   only_path: context[:only_path])
+      end
+
+      def user_can_reference_group?(group)
+        Ability.abilities.allowed?(context[:current_user], :read_group, group)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index a502a8fe9cd0ec0ffa751c1224843cc581e80ded..34aae3843551bb106f269f677ab523ca4fefbde8 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -3,8 +3,6 @@ module Gitlab
   class ReferenceExtractor
     attr_accessor :project, :current_user, :references
 
-    include ::Gitlab::Markdown
-
     def initialize(project, current_user = nil)
       @project = project
       @current_user = current_user
@@ -34,7 +32,7 @@ module Gitlab
           project.team.members.flatten
         elsif namespace = Namespace.find_by(path: identifier)
           if namespace.is_a?(Group)
-            namespace.users
+            namespace.users if can?(current_user, :read_group, namespace)
           else
             namespace.owner
           end
@@ -87,6 +85,72 @@ module Gitlab
 
     private
 
+    NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR
+    PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
+
+    REFERENCE_PATTERN = %r{
+      (?<prefix>\W)?                         # Prefix
+      (                                      # Reference
+         @(?<user>#{NAME_STR})               # User name
+        |~(?<label>\d+)                      # Label ID
+        |(?<issue>([A-Z\-]+-)\d+)            # JIRA Issue ID
+        |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
+        |#{PROJ_STR}?!(?<merge_request>\d+)  # MR ID
+        |\$(?<snippet>\d+)                   # Snippet ID
+        |(#{PROJ_STR}@)?(?<commit_range>[\h]{6,40}\.{2,3}[\h]{6,40}) # Commit range
+        |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID
+      )
+      (?<suffix>\W)?                         # Suffix
+    }x.freeze
+
+    TYPES = %i(user issue label merge_request snippet commit commit_range).freeze
+
+    def parse_references(text, project = @project)
+      # parse reference links
+      text.gsub!(REFERENCE_PATTERN) do |match|
+        type = TYPES.detect { |t| $~[t].present? }
+
+        actual_project = project
+        project_prefix = nil
+        project_path = $LAST_MATCH_INFO[:project]
+        if project_path
+          actual_project = ::Project.find_with_namespace(project_path)
+          actual_project = nil unless can?(current_user, :read_project, actual_project)
+          project_prefix = project_path
+        end
+
+        parse_result($LAST_MATCH_INFO, type,
+                     actual_project, project_prefix) || match
+      end
+    end
+
+    # Called from #parse_references.  Attempts to build a gitlab reference
+    # link.  Returns nil if +type+ is nil, if the match string is an HTML
+    # entity, if the reference is invalid, or if the matched text includes an
+    # invalid project path.
+    def parse_result(match_info, type, project, project_prefix)
+      prefix = match_info[:prefix]
+      suffix = match_info[:suffix]
+
+      return nil if html_entity?(prefix, suffix) || type.nil?
+      return nil if project.nil? && !project_prefix.nil?
+
+      identifier = match_info[type]
+      ref_link = reference_link(type, identifier, project, project_prefix)
+
+      if ref_link
+        "#{prefix}#{ref_link}#{suffix}"
+      else
+        nil
+      end
+    end
+
+    # Return true if the +prefix+ and +suffix+ indicate that the matched string
+    # is an HTML entity like &amp;
+    def html_entity?(prefix, suffix)
+      prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+    end
+
     def reference_link(type, identifier, project, _)
       references[type] << [project, identifier]
     end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 015a66f7fa02a46fc39d09137e2de0a2fc67d3cf..d4cf65400807b8d8d1fc73711e99fd35e9e9846e 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -249,6 +249,16 @@ describe ApplicationHelper do
       expect(link_to('Example', 'http://example.foo/bar')).
         to eq '<a href="http://example.foo/bar">Example</a>'
     end
+
+    it 'should not raise an error when given a bad URI' do
+      expect { link_to('default', 'if real=1 RANDOM; if real>1 IDLHS; if real>500 LHS') }.
+        not_to raise_error
+    end
+
+    it 'should not raise an error when given a bad mailto URL' do
+      expect { link_to('email', 'mailto://foo.bar@example.es?subject=Subject%20Line') }.
+        not_to raise_error
+    end
   end
 
   describe 'markup_render' do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 944e743675c7d91cb67007ddb7ed1ba521c53c16..64f130e4ae48b26d65c68552a345031f45194248 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -2,449 +2,27 @@ require 'spec_helper'
 
 describe GitlabMarkdownHelper do
   include ApplicationHelper
-  include IssuesHelper
-
-  # TODO: Properly test this
-  def can?(*)
-    true
-  end
 
   let!(:project) { create(:project) }
-  let(:empty_project) { create(:empty_project) }
 
   let(:user)          { create(:user, username: 'gfm') }
   let(:commit)        { project.repository.commit }
-  let(:earlier_commit){ project.repository.commit("HEAD~2") }
   let(:issue)         { create(:issue, project: project) }
   let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
   let(:snippet)       { create(:project_snippet, project: project) }
-  let(:member)        { project.project_members.where(user_id: user).first }
 
   # Helper expects a current_user method.
   let(:current_user) { user }
 
-  def url_helper(image_name)
-    File.join(root_url, 'assets', image_name)
-  end
-
   before do
     # Helper expects a @project instance variable
     @project = project
-    @ref = 'markdown'
-    @repository = project.repository
-    @request.host = Gitlab.config.gitlab.host
   end
 
   describe "#gfm" do
-    it "should return unaltered text if project is nil" do
-      actual = "Testing references: ##{issue.iid}"
-
-      expect(gfm(actual)).not_to eq(actual)
-
-      @project = nil
-      expect(gfm(actual)).to eq(actual)
-    end
-
-    it "should not alter non-references" do
-      actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
-      expect(gfm(actual)).to eq(expected)
-    end
-
-    it "should not touch HTML entities" do
-      allow(@project.issues).to receive(:where).
-        with(id: '39').and_return([issue])
-      actual = 'We&#39;ll accept good pull requests.'
-      expect(gfm(actual)).to eq("We'll accept good pull requests.")
-    end
-
     it "should forward HTML options to links" do
       expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')).
-          to have_selector('a.gfm.foo')
-    end
-
-    describe "referencing a commit range" do
-      let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) }
-
-      it "should link using a full id" do
-        actual = "What happened in #{earlier_commit.id}...#{commit.id}"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link using a short id" do
-        actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}"
-        expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id)
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link inclusively" do
-        actual = "What happened in #{earlier_commit.id}..#{commit.id}"
-        expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id)
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        actual = "(see #{earlier_commit.id}...#{commit.id})"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Changes #{earlier_commit.id}...#{commit.id} dramatically"
-        expected = /Changes <a.+>#{earlier_commit.id}...#{commit.id}<\/a> dramatically/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}"
-        expect(gfm(actual)).to eq(expected)
-      end
-
-      it "should include a title attribute" do
-        actual = "What happened in #{earlier_commit.id}...#{commit.id}"
-        expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/)
-      end
-
-      it "should include standard gfm classes" do
-        actual = "What happened in #{earlier_commit.id}...#{commit.id}"
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/)
-      end
-    end
-
-    describe "referencing a commit" do
-      let(:expected) { namespace_project_commit_path(project.namespace, project, commit) }
-
-      it "should link using a full id" do
-        actual = "Reverts #{commit.id}"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link using a short id" do
-        actual = "Backported from #{commit.short_id}"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        actual = "Reverted (see #{commit.id})"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Changes #{commit.id} dramatically"
-        expected = /Changes <a.+>#{commit.id}<\/a> dramatically/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        actual = expected = "What happened in #{commit.id.reverse}"
-        expect(gfm(actual)).to eq(expected)
-      end
-
-      it "should include a title attribute" do
-        actual = "Reverts #{commit.id}"
-        expect(gfm(actual)).to match(/title="#{commit.link_title}"/)
-      end
-
-      it "should include standard gfm classes" do
-        actual = "Reverts #{commit.id}"
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-commit\s?"/)
-      end
-    end
-
-    describe "referencing a team member" do
-      let(:actual)   { "@#{user.username} you are right." }
-      let(:expected) { user_path(user) }
-
-      before do
-        project.team << [user, :master]
-      end
-
-      it "should link using a simple name" do
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link using a name with dots" do
-        user.update_attributes(name: "alphA.Beta")
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link using name with underscores" do
-        user.update_attributes(name: "ping_pong_king")
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        actual = "Mail the admin (@#{user.username})"
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Yes, @#{user.username} is right."
-        expected = /Yes, <a.+>@#{user.username}<\/a> is right/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        actual = expected = "@#{user.username.reverse} you are right."
-        expect(gfm(actual)).to eq(expected)
-      end
-
-      it "should include standard gfm classes" do
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/)
-      end
-    end
-
-    # Shared examples for referencing an object
-    #
-    # Expects the following attributes to be available in the example group:
-    #
-    # - object    - The object itself
-    # - reference - The object reference string (e.g., #1234, $1234, !1234)
-    #
-    # Currently limited to Snippets, Issues and MergeRequests
-    shared_examples 'referenced object' do
-      let(:actual)   { "Reference to #{reference}" }
-      let(:expected) { polymorphic_path([project.namespace, project, object]) }
-
-      it "should link using a valid id" do
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        # Wrap the reference in parenthesis
-        expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
-        # Append some text to the end of the reference
-        expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
-          to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Referenced #{reference} already."
-        expected = /Referenced <a.+>[^\s]+<\/a> already/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        # Modify the reference string so it's still parsed, but is invalid
-        reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
-        expect(gfm(actual)).to eq(actual)
-      end
-
-      it "should include a title attribute" do
-        title = "#{object.class.to_s.titlecase}: #{object.title}"
-        expect(gfm(actual)).to match(/title="#{title}"/)
-      end
-
-      it "should include standard gfm classes" do
-        css = object.class.to_s.underscore
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
-      end
-    end
-
-    # Shared examples for referencing an object in a different project
-    #
-    # Expects the following attributes to be available in the example group:
-    #
-    # - object    - The object itself
-    # - reference - The object reference string (e.g., #1234, $1234, !1234)
-    # - other_project - The project that owns the target object
-    #
-    # Currently limited to Snippets, Issues and MergeRequests
-    shared_examples 'cross-project referenced object' do
-      let(:project_path) { @other_project.path_with_namespace }
-      let(:full_reference) { "#{project_path}#{reference}" }
-      let(:actual)   { "Reference to #{full_reference}" }
-      let(:expected) do
-        if object.is_a?(Commit)
-          namespace_project_commit_path(@other_project.namespace, @other_project, object)
-        else
-          polymorphic_path([@other_project.namespace, @other_project, object])
-        end
-      end
-
-      it 'should link using a valid id' do
-        expect(gfm(actual)).to match(
-          /#{expected}.*#{Regexp.escape(full_reference)}/
-        )
-      end
-
-      it 'should link with adjacent text' do
-        # Wrap the reference in parenthesis
-        expect(gfm(actual.gsub(full_reference, "(#{full_reference})"))).to(
-          match(expected)
-        )
-
-        # Append some text to the end of the reference
-        expect(gfm(actual.gsub(full_reference, "#{full_reference}, right?"))).
-          to(match(expected))
-      end
-
-      it 'should keep whitespace intact' do
-        actual   = "Referenced #{full_reference} already."
-        expected = /Referenced <a.+>[^\s]+<\/a> already/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it 'should not link with an invalid id' do
-        # Modify the reference string so it's still parsed, but is invalid
-        if object.is_a?(Commit)
-          reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
-        else
-          reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
-        end
-        expect(gfm(actual)).to eq(actual)
-      end
-
-      it 'should include a title attribute' do
-        if object.is_a?(Commit)
-          title = object.link_title
-        else
-          title = "#{object.class.to_s.titlecase}: #{object.title}"
-        end
-        expect(gfm(actual)).to match(/title="#{title}"/)
-      end
-
-      it 'should include standard gfm classes' do
-        css = object.class.to_s.underscore
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
-      end
-    end
-
-    describe "referencing an issue" do
-      let(:object)    { issue }
-      let(:reference) { "##{issue.iid}" }
-
-      include_examples 'referenced object'
-    end
-
-    context 'cross-repo references' do
-      before(:all) do
-        @other_project = create(:project, :public)
-        @commit2 = @other_project.repository.commit
-        @issue2 = create(:issue, project: @other_project)
-        @merge_request2 = create(:merge_request,
-                                 source_project: @other_project,
-                                 target_project: @other_project)
-      end
-
-      describe 'referencing an issue in another project' do
-        let(:object)    { @issue2 }
-        let(:reference) { "##{@issue2.iid}" }
-
-        include_examples 'cross-project referenced object'
-      end
-
-      describe 'referencing an merge request in another project' do
-        let(:object)    { @merge_request2 }
-        let(:reference) { "!#{@merge_request2.iid}" }
-
-        include_examples 'cross-project referenced object'
-      end
-
-      describe 'referencing a commit in another project' do
-        let(:object)    { @commit2 }
-        let(:reference) { "@#{@commit2.id}" }
-
-        include_examples 'cross-project referenced object'
-      end
-    end
-
-    describe "referencing a Jira issue" do
-      let(:actual)   { "Reference to JIRA-#{issue.iid}" }
-      let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
-      let(:reference) { "JIRA-#{issue.iid}" }
-
-      before do
-        jira = @project.create_jira_service if @project.jira_service.nil?
-        properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"}
-        jira.update_attributes(properties: properties, active: true)
-      end
-
-      after do
-        @project.jira_service.destroy! unless @project.jira_service.nil?
-      end
-
-      it "should link using a valid id" do
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        # Wrap the reference in parenthesis
-        expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
-        # Append some text to the end of the reference
-        expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
-          to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Referenced #{reference} already."
-        expected = /Referenced <a.+>[^\s]+<\/a> already/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        # Modify the reference string so it's still parsed, but is invalid
-        invalid_reference = actual.gsub(/(\d+)$/, "r45")
-        expect(gfm(invalid_reference)).to eq(invalid_reference)
-      end
-
-      it "should include a title attribute" do
-        title = "Issue in JIRA tracker"
-        expect(gfm(actual)).to match(/title="#{title}"/)
-      end
-
-      it "should include standard gfm classes" do
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-issue\s?"/)
-      end
-    end
-
-    describe "referencing a merge request" do
-      let(:object)    { merge_request }
-      let(:reference) { "!#{merge_request.iid}" }
-
-      include_examples 'referenced object'
-    end
-
-    describe "referencing a snippet" do
-      let(:object)    { snippet }
-      let(:reference) { "$#{snippet.id}" }
-      let(:actual)   { "Reference to #{reference}" }
-      let(:expected) { namespace_project_snippet_path(project.namespace, project, object) }
-
-      it "should link using a valid id" do
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should link with adjacent text" do
-        # Wrap the reference in parenthesis
-        expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
-        # Append some text to the end of the reference
-        expect(gfm(actual.gsub(reference, "#{reference}, right?"))).to match(expected)
-      end
-
-      it "should keep whitespace intact" do
-        actual   = "Referenced #{reference} already."
-        expected = /Referenced <a.+>[^\s]+<\/a> already/
-        expect(gfm(actual)).to match(expected)
-      end
-
-      it "should not link with an invalid id" do
-        # Modify the reference string so it's still parsed, but is invalid
-        reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
-        expect(gfm(actual)).to eq(actual)
-      end
-
-      it "should include a title attribute" do
-        title = "Snippet: #{object.title}"
-        expect(gfm(actual)).to match(/title="#{title}"/)
-      end
-
-      it "should include standard gfm classes" do
-        css = object.class.to_s.underscore
-        expect(gfm(actual)).to match(/class="\s?gfm gfm-snippet\s?"/)
-      end
-
+        to have_selector('a.gfm.foo')
     end
 
     describe "referencing multiple objects" do
@@ -466,90 +44,159 @@ describe GitlabMarkdownHelper do
       end
     end
 
-    describe "emoji" do
-      it "matches at the start of a string" do
-        expect(gfm(":+1:")).to match(/<img/)
+    context 'parse_tasks: true' do
+      before(:all) do
+        @source_text_asterisk = <<-EOT.strip_heredoc
+          * [ ] valid unchecked task
+          * [x] valid lowercase checked task
+          * [X] valid uppercase checked task
+              * [ ] valid unchecked nested task
+              * [x] valid checked nested task
+
+          [ ] not an unchecked task - no list item
+          [x] not a checked task - no list item
+
+          * [  ] not an unchecked task - too many spaces
+          * [x ] not a checked task - too many spaces
+          * [] not an unchecked task - no spaces
+          * Not a task [ ] - not at beginning
+        EOT
+
+        @source_text_dash = <<-EOT.strip_heredoc
+          - [ ] valid unchecked task
+          - [x] valid lowercase checked task
+          - [X] valid uppercase checked task
+              - [ ] valid unchecked nested task
+              - [x] valid checked nested task
+        EOT
+      end
+
+      it 'should render checkboxes at beginning of asterisk list items' do
+        rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+        expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid lowercase checked task/
+        )
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid uppercase checked task/
+        )
       end
 
-      it "matches at the end of a string" do
-        expect(gfm("This gets a :-1:")).to match(/<img/)
-      end
+      it 'should render checkboxes at beginning of dash list items' do
+        rendered_text = markdown(@source_text_dash, parse_tasks: true)
 
-      it "matches with adjacent text" do
-        expect(gfm("+1 (:+1:)")).to match(/<img/)
+        expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid lowercase checked task/
+        )
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid uppercase checked task/
+        )
       end
 
-      it "has a title attribute" do
-        expect(gfm(":-1:")).to match(/title=":-1:"/)
-      end
+      it 'should render checkboxes for nested tasks' do
+        rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
 
-      it "has an alt attribute" do
-        expect(gfm(":-1:")).to match(/alt=":-1:"/)
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid unchecked nested task/
+        )
+        expect(rendered_text).to match(
+          /<input.*checkbox.*valid checked nested task/
+        )
       end
 
-      it "has an emoji class" do
-        expect(gfm(":+1:")).to match('class="emoji"')
-      end
+      it 'should not be confused by whitespace before bullets' do
+        rendered_text_asterisk = markdown(@source_text_asterisk,
+                                          parse_tasks: true)
+        rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
 
-      it "sets height and width" do
-        actual = gfm(":+1:")
-        expect(actual).to match(/width="20"/)
-        expect(actual).to match(/height="20"/)
+        expect(rendered_text_asterisk).to match(
+          /<input.*checkbox.*valid unchecked nested task/
+        )
+        expect(rendered_text_asterisk).to match(
+          /<input.*checkbox.*valid checked nested task/
+        )
+        expect(rendered_text_dash).to match(
+          /<input.*checkbox.*valid unchecked nested task/
+        )
+        expect(rendered_text_dash).to match(
+          /<input.*checkbox.*valid checked nested task/
+        )
       end
 
-      it "keeps whitespace intact" do
-        expect(gfm('This deserves a :+1: big time.')).
-          to match(/deserves a <img.+> big time/)
-      end
+      it 'should not render checkboxes outside of list items' do
+        rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
 
-      it "ignores invalid emoji" do
-        expect(gfm(":invalid-emoji:")).not_to match(/<img/)
+        expect(rendered_text).not_to match(
+          /<input.*checkbox.*not an unchecked task - no list item/
+        )
+        expect(rendered_text).not_to match(
+          /<input.*checkbox.*not a checked task - no list item/
+        )
       end
 
-      it "should work independent of reference links (i.e. without @project being set)" do
-        @project = nil
-        expect(gfm(":+1:")).to match(/<img/)
+      it 'should not render checkboxes with invalid formatting' do
+        rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+        expect(rendered_text).not_to match(
+          /<input.*checkbox.*not an unchecked task - too many spaces/
+        )
+        expect(rendered_text).not_to match(
+          /<input.*checkbox.*not a checked task - too many spaces/
+        )
+        expect(rendered_text).not_to match(
+          /<input.*checkbox.*not an unchecked task - no spaces/
+        )
+        expect(rendered_text).not_to match(
+          /Not a task.*<input.*checkbox.*not at beginning/
+        )
       end
     end
   end
 
-  describe "#link_to_gfm" do
+  describe '#link_to_gfm' do
     let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) }
     let(:issues)      { create_list(:issue, 2, project: project) }
 
-    it "should handle references nested in links with all the text" do
+    it 'should handle references nested in links with all the text' do
       actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
+      doc = Nokogiri::HTML.parse(actual)
 
-      # Break the result into groups of links with their content, without
-      # closing tags
-      groups = actual.split("</a>")
+      # Make sure we didn't create invalid markup
+      expect(doc.errors).to be_empty
 
       # Leading commit link
-      expect(groups[0]).to match(/href="#{commit_path}"/)
-      expect(groups[0]).to match(/This should finally fix $/)
+      expect(doc.css('a')[0].attr('href')).to eq commit_path
+      expect(doc.css('a')[0].text).to eq 'This should finally fix '
 
       # First issue link
-      expect(groups[1]).
-        to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[0])}"/)
-      expect(groups[1]).to match(/##{issues[0].iid}$/)
+      expect(doc.css('a')[1].attr('href')).
+        to eq namespace_project_issue_path(project.namespace, project, issues[0])
+      expect(doc.css('a')[1].text).to eq "##{issues[0].iid}"
 
       # Internal commit link
-      expect(groups[2]).to match(/href="#{commit_path}"/)
-      expect(groups[2]).to match(/ and /)
+      expect(doc.css('a')[2].attr('href')).to eq commit_path
+      expect(doc.css('a')[2].text).to eq ' and '
 
       # Second issue link
-      expect(groups[3]).
-        to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[1])}"/)
-      expect(groups[3]).to match(/##{issues[1].iid}$/)
+      expect(doc.css('a')[3].attr('href')).
+        to eq namespace_project_issue_path(project.namespace, project, issues[1])
+      expect(doc.css('a')[3].text).to eq "##{issues[1].iid}"
 
       # Trailing commit link
-      expect(groups[4]).to match(/href="#{commit_path}"/)
-      expect(groups[4]).to match(/ for real$/)
+      expect(doc.css('a')[4].attr('href')).to eq commit_path
+      expect(doc.css('a')[4].text).to eq ' for real'
     end
 
-    it "should forward HTML options" do
+    it 'should forward HTML options' do
       actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
-      expect(actual).to have_selector 'a.gfm.gfm-commit.foo'
+      doc = Nokogiri::HTML.parse(actual)
+
+      expect(doc.css('a')).to satisfy do |v|
+        # 'foo' gets added to all links
+        v.all? { |a| a.attr('class').match(/foo$/) }
+      end
     end
 
     it "escapes HTML passed in as the body" do
@@ -560,20 +207,7 @@ describe GitlabMarkdownHelper do
   end
 
   describe "#markdown" do
-    it "should handle references in paragraphs" do
-      actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n"
-      expected = namespace_project_commit_path(project.namespace, project, commit)
-      expect(markdown(actual)).to match(expected)
-    end
-
-    it "should handle references in headers" do
-      actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
-
-      expect(markdown(actual, no_header_anchors: true)).
-        to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
-      expect(markdown(actual, no_header_anchors: true)).
-        to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
-    end
+    # TODO (rspeicher) - This block tests multiple different contexts. Break this up!
 
     it "should add ids and links to headers" do
       # Test every rule except nested tags.
@@ -590,35 +224,15 @@ describe GitlabMarkdownHelper do
       )
     end
 
-    it "should handle references in lists" do
-      project.team << [user, :master]
-
-      actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}"
-
-      expect(markdown(actual)).
-        to match(%r{<li>dark: <a.+>##{issue.iid}</a></li>})
-      expect(markdown(actual)).
-        to match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
-    end
-
-    it "should not link the apostrophe to issue 39" do
-      project.team << [user, :master]
-      allow(project.issues).
-        to receive(:where).with(iid: '39').and_return([issue])
-
-      actual   = "Yes, it is @#{member.user.username}'s task."
-      expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
-      expect(markdown(actual)).to match(expected)
-    end
+    # REFERENCES (PART TWO: THE REVENGE) ---------------------------------------
 
-    it "should not link the apostrophe to issue 39 in code blocks" do
-      project.team << [user, :master]
-      allow(project.issues).
-        to receive(:where).with(iid: '39').and_return([issue])
+    it "should handle references in headers" do
+      actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
 
-      actual   = "Yes, `it is @#{member.user.username}'s task.`"
-      expected = /Yes, <code>it is @gfm\'s task.<\/code>/
-      expect(markdown(actual)).to match(expected)
+      expect(markdown(actual, no_header_anchors: true)).
+        to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
+      expect(markdown(actual, no_header_anchors: true)).
+        to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
     end
 
     it "should handle references in <em>" do
@@ -628,118 +242,120 @@ describe GitlabMarkdownHelper do
         to match(%r{Apply <em><a.+>!#{merge_request.iid}</a></em>})
     end
 
-    it "should handle tables" do
-      actual = %Q{| header 1 | header 2 |
-| -------- | -------- |
-| cell 1   | cell 2   |
-| cell 3   | cell 4   |}
-
-      expect(markdown(actual)).to match(/\A<table/)
-    end
+    # CODE BLOCKS -------------------------------------------------------------
 
     it "should leave code blocks untouched" do
+      allow(helper).to receive(:current_user).and_return(user)
       allow(helper).to receive(:user_color_scheme_class).and_return(:white)
 
       target_html = "<pre class=\"code highlight white plaintext\"><code>some code from $#{snippet.id}\nhere too\n</code></pre>\n"
 
-      expect(helper.markdown("\n    some code from $#{snippet.id}\n    here too\n")).
+      expect(markdown("\n    some code from $#{snippet.id}\n    here too\n")).
         to eq(target_html)
-      expect(helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
+      expect(markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
         to eq(target_html)
     end
 
     it "should leave inline code untouched" do
-      expect(markdown("\nDon't use `$#{snippet.id}` here.\n")).to eq(
-        "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
-      )
+      expect(markdown("Don't use `$#{snippet.id}` here.")).
+        to eq "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
     end
 
+    # REF-LIKE AUTOLINKS? -----------------------------------------------------
+    # Basically: Don't parse references inside `<a>` tags.
+
     it "should leave ref-like autolinks untouched" do
       expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("<p>look at <a href=\"http://example.tld/#!#{merge_request.iid}\">http://example.tld/#!#{merge_request.iid}</a></p>\n")
     end
 
     it "should leave ref-like href of 'manual' links untouched" do
-      expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
+      expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\" class=\"gfm gfm-merge_request\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
     end
 
     it "should leave ref-like src of images untouched" do
       expect(markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})")).to eq("<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.iid}\" alt=\"some image\"></p>\n")
     end
 
-    it "should generate absolute urls for refs" do
-      expect(markdown("##{issue.iid}")).to include(namespace_project_issue_path(project.namespace, project, issue))
-    end
+    # RELATIVE URLS -----------------------------------------------------------
+    # TODO (rspeicher): These belong in a relative link filter spec
 
-    it "should generate absolute urls for emoji" do
-      expect(markdown(':smile:')).to(
-        include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png))
-      )
-    end
+    context 'relative links' do
+      context 'with a valid repository' do
+        before do
+          @repository = project.repository
+          @ref = 'markdown'
+        end
 
-    it "should generate absolute urls for emoji if relative url is present" do
-      allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root')
-      expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
-    end
+        it "should handle relative urls for a file in master" do
+          actual = "[GitLab API doc](doc/api/README.md)\n"
+          expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should generate absolute urls for emoji if asset_host is present" do
-      allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com")
-      ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
-      expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
-    end
+        it "should handle relative urls for a file in master with an anchor" do
+          actual = "[GitLab API doc](doc/api/README.md#section)\n"
+          expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md#section\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
+        it "should not handle relative urls for the current file with an anchor" do
+          actual = "[GitLab API doc](#section)\n"
+          expected = "<p><a href=\"#section\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should handle relative urls for a file in master" do
-      actual = "[GitLab API doc](doc/api/README.md)\n"
-      expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it "should handle relative urls for a directory in master" do
+          actual = "[GitLab API doc](doc/api)\n"
+          expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should handle relative urls for a file in master with an anchor" do
-      actual = "[GitLab API doc](doc/api/README.md#section)\n"
-      expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md#section\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it "should handle absolute urls" do
+          actual = "[GitLab](https://www.gitlab.com)\n"
+          expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should not handle relative urls for the current file with an anchor" do
-      actual = "[GitLab API doc](#section)\n"
-      expected = "<p><a href=\"#section\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it "should handle relative urls in reference links for a file in master" do
+          actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
+          expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should handle relative urls for a directory in master" do
-      actual = "[GitLab API doc](doc/api)\n"
-      expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it "should handle relative urls in reference links for a directory in master" do
+          actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
+          expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc directory</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should handle absolute urls" do
-      actual = "[GitLab](https://www.gitlab.com)\n"
-      expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it "should not handle malformed relative urls in reference links for a file in master" do
+          actual = "[GitLab readme]: doc/api/README.md\n"
+          expected = ""
+          expect(markdown(actual)).to match(expected)
+        end
 
-    it "should handle relative urls in reference links for a file in master" do
-      actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
-      expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+        it 'should allow whitelisted HTML tags from the user' do
+          actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>'
+          expect(markdown(actual)).to match(actual)
+        end
+      end
 
-    it "should handle relative urls in reference links for a directory in master" do
-      actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
-      expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc directory</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
+      context 'with an empty repository' do
+        before do
+          @project = create(:empty_project)
+          @repository = @project.repository
+        end
 
-     it "should not handle malformed relative urls in reference links for a file in master" do
-      actual = "[GitLab readme]: doc/api/README.md\n"
-      expected = ""
-      expect(markdown(actual)).to match(expected)
+        it "should not touch relative urls" do
+          actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
+          expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
+          expect(markdown(actual)).to match(expected)
+        end
+      end
     end
 
-    it 'should allow whitelisted HTML tags from the user' do
-      actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>'
-      expect(markdown(actual)).to match(actual)
-    end
+    # SANITIZATION ------------------------------------------------------------
+    # TODO (rspeicher): These are testing SanitizationFilter, not `markdown`
 
     it 'should sanitize tags that are not whitelisted' do
       actual = '<textarea>no inputs allowed</textarea> <blink>no blinks</blink>'
@@ -767,20 +383,7 @@ describe GitlabMarkdownHelper do
     end
   end
 
-  describe 'markdown for empty repository' do
-    before do
-      @project = empty_project
-      @repository = empty_project.repository
-    end
-
-    it "should not touch relative urls" do
-      actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
-      expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
-      expect(markdown(actual)).to match(expected)
-    end
-  end
-
-  describe "#render_wiki_content" do
+  describe '#render_wiki_content' do
     before do
       @wiki = double('WikiPage')
       allow(@wiki).to receive(:content).and_return('wiki content')
@@ -803,114 +406,4 @@ describe GitlabMarkdownHelper do
       helper.render_wiki_content(@wiki)
     end
   end
-
-  describe '#gfm_with_tasks' do
-    before(:all) do
-      @source_text_asterisk = <<EOT.gsub(/^\s{8}/, '')
-        * [ ] valid unchecked task
-        * [x] valid lowercase checked task
-        * [X] valid uppercase checked task
-            * [ ] valid unchecked nested task
-            * [x] valid checked nested task
-
-        [ ] not an unchecked task - no list item
-        [x] not a checked task - no list item
-
-        * [  ] not an unchecked task - too many spaces
-        * [x ] not a checked task - too many spaces
-        * [] not an unchecked task - no spaces
-        * Not a task [ ] - not at beginning
-EOT
-
-      @source_text_dash = <<EOT.gsub(/^\s{8}/, '')
-        - [ ] valid unchecked task
-        - [x] valid lowercase checked task
-        - [X] valid uppercase checked task
-            - [ ] valid unchecked nested task
-            - [x] valid checked nested task
-EOT
-    end
-
-    it 'should render checkboxes at beginning of asterisk list items' do
-      rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
-      expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid lowercase checked task/
-      )
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid uppercase checked task/
-      )
-    end
-
-    it 'should render checkboxes at beginning of dash list items' do
-      rendered_text = markdown(@source_text_dash, parse_tasks: true)
-
-      expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid lowercase checked task/
-      )
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid uppercase checked task/
-      )
-    end
-
-    it 'should render checkboxes for nested tasks' do
-      rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid unchecked nested task/
-      )
-      expect(rendered_text).to match(
-        /<input.*checkbox.*valid checked nested task/
-      )
-    end
-
-    it 'should not be confused by whitespace before bullets' do
-      rendered_text_asterisk = markdown(@source_text_asterisk,
-                                        parse_tasks: true)
-      rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
-
-      expect(rendered_text_asterisk).to match(
-        /<input.*checkbox.*valid unchecked nested task/
-      )
-      expect(rendered_text_asterisk).to match(
-        /<input.*checkbox.*valid checked nested task/
-      )
-      expect(rendered_text_dash).to match(
-        /<input.*checkbox.*valid unchecked nested task/
-      )
-      expect(rendered_text_dash).to match(
-        /<input.*checkbox.*valid checked nested task/
-      )
-    end
-
-    it 'should not render checkboxes outside of list items' do
-      rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
-      expect(rendered_text).not_to match(
-        /<input.*checkbox.*not an unchecked task - no list item/
-      )
-      expect(rendered_text).not_to match(
-        /<input.*checkbox.*not a checked task - no list item/
-      )
-    end
-
-    it 'should not render checkboxes with invalid formatting' do
-      rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
-      expect(rendered_text).not_to match(
-        /<input.*checkbox.*not an unchecked task - too many spaces/
-      )
-      expect(rendered_text).not_to match(
-        /<input.*checkbox.*not a checked task - too many spaces/
-      )
-      expect(rendered_text).not_to match(
-        /<input.*checkbox.*not an unchecked task - no spaces/
-      )
-      expect(rendered_text).not_to match(
-        /Not a task.*<input.*checkbox.*not at beginning/
-      )
-    end
-  end
 end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 1e64a201942d128e377ad4a20cfdcd7f1faf1b7d..0b7e3b1d11f3809940b6083bba6c33550870f009 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
 describe LabelsHelper do
-  it { expect(text_color_for_bg('#EEEEEE')).to eq('#333') }
-  it { expect(text_color_for_bg('#222E2E')).to eq('#FFF') }
+  it { expect(text_color_for_bg('#EEEEEE')).to eq('#333333') }
+  it { expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') }
 end
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..57dcc2161d38554b0f51cc553446c7a895e4999b
--- /dev/null
+++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee
@@ -0,0 +1,83 @@
+#= require jquery
+#= require jasmine-fixture
+
+#= require shortcuts_issuable
+
+describe 'ShortcutsIssuable', ->
+  beforeEach ->
+    @shortcut = new ShortcutsIssuable()
+
+  describe '#replyWithSelectedText', ->
+    # Stub window.getSelection to return the provided String.
+    stubSelection = (text) ->
+      window.getSelection = -> text
+
+    beforeEach ->
+      @selector = 'form.js-main-target-form textarea#note_note'
+      affix(@selector)
+
+    describe 'with empty selection', ->
+      it 'does nothing', ->
+        stubSelection('')
+        @shortcut.replyWithSelectedText()
+        expect($(@selector).val()).toBe('')
+
+    describe 'with any selection', ->
+      beforeEach ->
+        stubSelection('Selected text.')
+
+      it 'leaves existing input intact', ->
+        $(@selector).val('This text was already here.')
+        expect($(@selector).val()).toBe('This text was already here.')
+
+        @shortcut.replyWithSelectedText()
+        expect($(@selector).val()).
+          toBe("This text was already here.\n> Selected text.\n\n")
+
+      it 'triggers `input`', ->
+        triggered = false
+        $(@selector).on 'input', -> triggered = true
+        @shortcut.replyWithSelectedText()
+
+        expect(triggered).toBe(true)
+
+      it 'triggers `focus`', ->
+        focused = false
+        $(@selector).on 'focus', -> focused = true
+        @shortcut.replyWithSelectedText()
+
+        expect(focused).toBe(true)
+
+    describe 'with a one-line selection', ->
+      it 'quotes the selection', ->
+        stubSelection('This text has been selected.')
+
+        @shortcut.replyWithSelectedText()
+
+        expect($(@selector).val()).
+          toBe("> This text has been selected.\n\n")
+
+    describe 'with a multi-line selection', ->
+      it 'quotes the selected lines as a group', ->
+        stubSelection(
+          """
+          Selected line one.
+
+          Selected line two.
+          Selected line three.
+
+          """
+        )
+
+        @shortcut.replyWithSelectedText()
+
+        expect($(@selector).val()).
+          toBe(
+            """
+            > Selected line one.
+            > Selected line two.
+            > Selected line three.
+
+
+            """
+          )
diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js
index 1090cb7f620724bc757387a0a58cff5283526e3d..78d39f1b4280d6480ccff83cf6e2e9961897f84a 100644
--- a/spec/javascripts/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/stat_graph_contributors_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_graph
+
 describe("ContributorsGraph", function () {
   describe("#set_x_domain", function () {
     it("set the x_domain", function () {
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
index 9c1b588861d98861651533be5b6bf99fd400ea69..ee90892eb48bcbda4d61af2949da6c6cbf81320f 100644
--- a/spec/javascripts/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/stat_graph_contributors_util_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_util
+
 describe("ContributorsStatGraphUtil", function () {
 
   describe("#parse_log", function () {
diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/stat_graph_spec.js
index b589af34610bb8daacbeafecdfe0bf7d57a57187..4c652910cd6f43a5722c89f1e3bfd21076284fa5 100644
--- a/spec/javascripts/stat_graph_spec.js
+++ b/spec/javascripts/stat_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph
+
 describe("StatGraph", function () {
 
   describe("#get_log", function () {
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index 9bfa261a356ac0707d3bc959ba99e99b381bf68e..f4b01c9f27a86587c8d10006a9e94dc7be3b013c 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -1,76 +1,20 @@
-# src_files
+# path to parent directory of spec_files
+# relative path from Rails.root
 #
-# Return an array of filepaths relative to src_dir to include before jasmine specs.
-# Default: []
+# Alternatively accept an array of directory to include external spec files
+# spec_dir:
+#   - spec/javascripts
+#   - ../engine/spec/javascripts
 #
-# EXAMPLE:
-#
-# src_files:
-#   - lib/source1.js
-#   - lib/source2.js
-#   - dist/**/*.js
-#
-src_files:
-  - assets/application.js
-
-# stylesheets
-#
-# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
-# Default: []
-#
-# EXAMPLE:
-#
-# stylesheets:
-#   - css/style.css
-#   - stylesheets/*.css
-#
-stylesheets:
-  - stylesheets/**/*.css
+# defaults to spec/javascripts
+spec_dir: spec/javascripts
 
-# helpers
-#
-# Return an array of filepaths relative to spec_dir to include before jasmine specs.
-# Default: ["helpers/**/*.js"]
-#
-# EXAMPLE:
-#
-# helpers:
-#   - helpers/**/*.js
-#
+# list of file expressions to include as helpers into spec runner
+# relative path from spec_dir
 helpers:
-  - helpers/**/*.js
+  - "helpers/**/*.{js.coffee,js,coffee}"
 
-# spec_files
-#
-# Return an array of filepaths relative to spec_dir to include.
-# Default: ["**/*[sS]pec.js"]
-#
-# EXAMPLE:
-#
-# spec_files:
-#   - **/*[sS]pec.js
-#
+# list of file expressions to include as specs into spec runner
+# relative path from spec_dir
 spec_files:
-  - '**/*[sS]pec.js'
-
-# src_dir
-#
-# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
-# Default: project root
-#
-# EXAMPLE:
-#
-# src_dir: public
-#
-src_dir:
-
-# spec_dir
-#
-# Spec directory path. Your spec_files must be returned relative to this path.
-# Default: spec/javascripts
-#
-# EXAMPLE:
-#
-# spec_dir: spec/javascripts
-#
-spec_dir: spec/javascripts
+  - "**/*[Ss]pec.{js.coffee,js,coffee}"
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
index b4919802afe405cd727abf936f9b074792bf3d97..4d73aec5a311968fcb8a76a4ce113f22f0e6d5de 100644
--- a/spec/javascripts/support/jasmine_helper.rb
+++ b/spec/javascripts/support/jasmine_helper.rb
@@ -8,4 +8,8 @@
 #   config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
 #end
 #
-
+#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
+#Jasmine.configure do |config|
+#   config.prevent_phantom_js_auto_install = true
+#end
+#
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 1c4503ae0ef31c50f9496e342a1971e274f494b1..67378328336b7720a4a24c0404156016c2c9c9bd 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -57,10 +57,11 @@ describe Gitlab::GoogleCodeImport::Importer do
       expect(issue.label_names).to include("Type: Enhancement")
       expect(issue.title).to eq("Scrolling through tasks")
       expect(issue.state).to eq("closed")
-      expect(issue.description).to include("schattenpr...")
+      expect(issue.description).to include("schattenpr\\.\\.\\.")
       expect(issue.description).to include("November 18, 2009 00:20")
-      expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel \(like in fluxbox\).')
-      expect(issue.description).to include('Patch is attached that adds two new mouse\-actions \(next\_taskprev\_task\)')
+      expect(issue.description).to include("Google Code")
+      expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel (like in fluxbox).')
+      expect(issue.description).to include('Patch is attached that adds two new mouse-actions (next_task+prev_task)')
       expect(issue.description).to include('that can be used for exactly that purpose.')
       expect(issue.description).to include('all the best!')
       expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ebdc8926e203b9c2698046a160a89426bff6550
--- /dev/null
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe CommitRangeReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project) { create(:project) }
+    let(:commit1) { project.repository.commit }
+    let(:commit2) { project.repository.commit("HEAD~2") }
+
+    it 'requires project context' do
+      expect { described_class.call('Commit Range 1c002d..d200c1', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %w(pre code a style).each do |elem|
+      it "ignores valid references contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'internal reference' do
+      let(:reference) { "#{commit1.id}...#{commit2.id}" }
+      let(:reference2) { "#{commit1.id}..#{commit2.id}" }
+
+      it 'links to a valid two-dot reference' do
+        doc = filter("See #{reference2}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id)
+      end
+
+      it 'links to a valid three-dot reference' do
+        doc = filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).
+          to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id)
+      end
+
+      it 'links to a valid short ID' do
+        reference = "#{commit1.short_id}...#{commit2.id}"
+        reference2 = "#{commit1.id}...#{commit2.short_id}"
+
+        expect(filter("See #{reference}").css('a').first.text).to eq reference
+        expect(filter("See #{reference2}").css('a').first.text).to eq reference2
+      end
+
+      it 'links with adjacent text' do
+        doc = filter("See (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid commit IDs' do
+        exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
+
+        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
+      end
+
+      it 'includes a title attribute' do
+        doc = filter("See #{reference}")
+        expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("See #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("See #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path option' do
+        doc = filter("See #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).not_to match %r(https?://)
+        expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+      end
+    end
+
+    context 'cross-project reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, namespace: namespace) }
+      let(:commit1)   { project.repository.commit }
+      let(:commit2)   { project.repository.commit("HEAD~2") }
+      let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
+
+      context 'when user can access reference' do
+        before { allow_cross_reference! }
+
+        it 'links to a valid reference' do
+          doc = filter("See #{reference}")
+
+          expect(doc.css('a').first.attr('href')).
+            to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id)
+        end
+
+        it 'links with adjacent text' do
+          doc = filter("Fixed (#{reference}.)")
+          expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+        end
+
+        it 'ignores invalid commit IDs on the referenced project' do
+          exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}"
+          expect(filter(act).to_html).to eq exp
+
+          exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}"
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'when user cannot access reference' do
+        before { disallow_cross_reference! }
+
+        it 'ignores valid references' do
+          exp = act = "See #{reference}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..71fd2db2c5899e8722120f26ae2cef74b6a4ceee
--- /dev/null
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -0,0 +1,118 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe CommitReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project) { create(:project) }
+    let(:commit)  { project.repository.commit }
+
+    it 'requires project context' do
+      expect { described_class.call('Commit 1c002d', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %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
+      end
+    end
+
+    context 'internal reference' do
+      let(:reference) { commit.id }
+
+      # 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]}")
+
+          expect(doc.css('a').first.text).to eq reference[0...size]
+          expect(doc.css('a').first.attr('href')).
+            to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+        end
+      end
+
+      it 'links with adjacent text' do
+        doc = filter("See (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid commit IDs' do
+        exp = act = "See #{reference.reverse}"
+
+        expect(project).to receive(:valid_repo?).and_return(true)
+        expect(project.repository).to receive(:commit).with(reference.reverse)
+        expect(filter(act).to_html).to eq exp
+      end
+
+      it 'includes a title attribute' do
+        doc = 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}")
+        expect(doc.text).to eq "See #{commit.id}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("See #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("See #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path context' do
+        doc = filter("See #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).not_to match %r(https?://)
+        expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+      end
+    end
+
+    context 'cross-project reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, namespace: namespace) }
+      let(:commit)    { project.repository.commit }
+      let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" }
+
+      context 'when user can access reference' do
+        before { allow_cross_reference! }
+
+        it 'links to a valid reference' do
+          doc = 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}.)")
+          expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+        end
+
+        it 'ignores invalid commit IDs on the referenced project' do
+          exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}"
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'when user cannot access reference' do
+        before { disallow_cross_reference! }
+
+        it 'ignores valid references' do
+          exp = act = "See #{reference}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4698d6138c20cc67e5261af8f09adc808524165b
--- /dev/null
+++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe CrossProjectReference do
+    # context in the html-pipeline sense, not in the rspec sense
+    let(:context) do
+      {
+        current_user: double('user'),
+        project: double('project')
+      }
+    end
+
+    include described_class
+
+    describe '#project_from_ref' do
+      context 'when no project was referenced' do
+        it 'returns the project from context' do
+          expect(project_from_ref(nil)).to eq context[:project]
+        end
+      end
+
+      context 'when referenced project does not exist' do
+        it 'returns nil' do
+          expect(project_from_ref('invalid/reference')).to be_nil
+        end
+      end
+
+      context 'when referenced project exists' do
+        let(:project2) { double('referenced project') }
+
+        before do
+          expect(Project).to receive(:find_with_namespace).
+            with('cross/reference').and_return(project2)
+        end
+
+        context 'and the user has permission to read it' do
+          it 'returns the referenced project' do
+            expect(self).to receive(:user_can_reference_project?).
+              with(project2).and_return(true)
+
+            expect(project_from_ref('cross/reference')).to eq project2
+          end
+        end
+
+        context 'and the user does not have permission to read it' do
+          it 'returns nil' do
+            expect(self).to receive(:user_can_reference_project?).
+              with(project2).and_return(false)
+
+            expect(project_from_ref('cross/reference')).to be_nil
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18d55c4818fba947685b17cfa7cf24434978fa41
--- /dev/null
+++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe EmojiFilter do
+    def filter(html, contexts = {})
+      described_class.call(html, contexts)
+    end
+
+    before do
+      ActionController::Base.asset_host = 'https://foo.com'
+    end
+
+    it 'replaces supported emoji' do
+      doc = filter('<p>:heart:</p>')
+      expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+    end
+
+    it 'ignores unsupported emoji' do
+      exp = act = '<p>:foo:</p>'
+      doc = filter(act)
+      expect(doc.to_html).to match Regexp.escape(exp)
+    end
+
+    it 'correctly encodes the URL' do
+      doc = filter('<p>:+1:</p>')
+      expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+    end
+
+    it 'matches at the start of a string' do
+      doc = filter(':+1:')
+      expect(doc.css('img').size).to eq 1
+    end
+
+    it 'matches at the end of a string' do
+      doc = filter('This gets a :-1:')
+      expect(doc.css('img').size).to eq 1
+    end
+
+    it 'matches with adjacent text' do
+      doc = filter('+1 (:+1:)')
+      expect(doc.css('img').size).to eq 1
+    end
+
+    it 'matches multiple emoji in a row' do
+      doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
+      expect(doc.css('img').size).to eq 3
+    end
+
+    it 'has a title attribute' do
+      doc = filter(':-1:')
+      expect(doc.css('img').first.attr('title')).to eq ':-1:'
+    end
+
+    it 'has an alt attribute' do
+      doc = filter(':-1:')
+      expect(doc.css('img').first.attr('alt')).to eq ':-1:'
+    end
+
+    it 'has an align attribute' do
+      doc = filter(':8ball:')
+      expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
+    end
+
+    it 'has an emoji class' do
+      doc = filter(':cat:')
+      expect(doc.css('img').first.attr('class')).to eq 'emoji'
+    end
+
+    it 'has height and width attributes' do
+      doc = filter(':dog:')
+      img = doc.css('img').first
+
+      expect(img.attr('width')).to eq '20'
+      expect(img.attr('height')).to eq '20'
+    end
+
+    it 'keeps whitespace intact' do
+      doc = filter('This deserves a :+1:, big time.')
+
+      expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+    end
+
+    it 'uses a custom asset_root context' do
+      root = Gitlab.config.gitlab.url + 'gitlab/root'
+
+      doc = filter(':smile:', asset_root: root)
+      expect(doc.css('img').first.attr('src')).to start_with(root)
+    end
+
+    it 'uses a custom asset_host context' do
+      ActionController::Base.asset_host = 'https://cdn.example.com'
+
+      doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
+      expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27e930ef7daf3df4aae5a249808fa21158aada4f
--- /dev/null
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe ExternalIssueReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    def helper
+      IssuesHelper
+    end
+
+    let(:project) { create(:empty_project) }
+    let(:issue)   { double('issue', iid: 123) }
+
+    context 'JIRA issue references' do
+      let(:reference) { "JIRA-#{issue.iid}" }
+
+      before do
+        jira = project.create_jira_service
+
+        props = {
+          'title'         => 'JIRA tracker',
+          'project_url'   => 'http://jira.example/issues/?jql=project=A',
+          'issues_url'    => 'http://jira.example/browse/:id',
+          'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa'
+        }
+
+        jira.update_attributes(properties: props, active: true)
+      end
+
+      after do
+        project.jira_service.destroy
+      end
+
+      it 'requires project context' do
+        expect { described_class.call('Issue JIRA-123', {}) }.
+          to raise_error(ArgumentError, /:project/)
+      end
+
+      %w(pre code a style).each do |elem|
+        it "ignores valid references contained inside '#{elem}' element" do
+          exp = act = "<#{elem}>Issue JIRA-#{issue.iid}</#{elem}>"
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      it 'ignores valid references when using default tracker' do
+        expect(project).to receive(:default_issues_tracker?).and_return(true)
+
+        exp = act = "Issue #{reference}"
+        expect(filter(act).to_html).to eq exp
+      end
+
+      %w(pre code a style).each do |elem|
+        it "ignores references contained inside '#{elem}' element" do
+          exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      it 'links to a valid reference' do
+        doc = filter("Issue #{reference}")
+        expect(doc.css('a').first.attr('href'))
+          .to eq helper.url_for_issue(reference, project)
+      end
+
+      it 'links to the external tracker' do
+        doc = filter("Issue #{reference}")
+        link = doc.css('a').first.attr('href')
+
+        expect(link).to eq "http://jira.example/browse/#{reference}"
+      end
+
+      it 'links with adjacent text' do
+        doc = filter("Issue (#{reference}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+      end
+
+      it 'includes a title attribute' do
+        doc = filter("Issue #{reference}")
+        expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
+      end
+
+      it 'escapes the title attribute' do
+        allow(project.external_issue_tracker).to receive(:title).
+          and_return(%{"></a>whatever<a title="})
+
+        doc = filter("Issue #{reference}")
+        expect(doc.text).to eq "Issue #{reference}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("Issue #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("Issue #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path context' do
+        doc = filter("Issue #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f95b37d69544422117aaadc35e2b50111d1ca6be
--- /dev/null
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe IssueReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    def helper
+      IssuesHelper
+    end
+
+    let(:project) { create(:empty_project) }
+    let(:issue)   { create(:issue, project: project) }
+
+    it 'requires project context' do
+      expect { described_class.call('Issue #123', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %w(pre code a style).each do |elem|
+      it "ignores valid references contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>Issue ##{issue.iid}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'internal reference' do
+      let(:reference) { "##{issue.iid}" }
+
+      it 'ignores valid references when using non-default tracker' do
+        expect(project).to receive(:issue_exists?).with(issue.iid).and_return(false)
+
+        exp = act = "Issue ##{issue.iid}"
+        expect(filter(act).to_html).to eq exp
+      end
+
+      it 'links to a valid reference' do
+        doc = filter("See #{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}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid issue IDs' do
+        exp = act = "Fixed ##{issue.iid + 1}"
+
+        expect(project).to receive(:issue_exists?).with(issue.iid + 1)
+        expect(filter(act).to_html).to eq exp
+      end
+
+      it 'includes a title attribute' do
+        doc = 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}")
+        expect(doc.text).to eq "Issue #{reference}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("Issue #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("Issue #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path context' do
+        doc = filter("Issue #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).not_to match %r(https?://)
+        expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
+      end
+    end
+
+    context 'cross-project reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, namespace: namespace) }
+      let(:issue)     { create(:issue, project: project2) }
+      let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
+
+      context 'when user can access reference' do
+        before { allow_cross_reference! }
+
+        it 'ignores valid references when cross-reference project uses external tracker' do
+          expect_any_instance_of(Project).to receive(:issue_exists?).
+            with(issue.iid).and_return(false)
+
+          exp = act = "Issue ##{issue.iid}"
+          expect(filter(act).to_html).to eq exp
+        end
+
+        it 'links to a valid reference' do
+          doc = 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}.)")
+          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 #{project2.path_with_namespace}##{issue.iid + 1}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'when user cannot access reference' do
+        before { disallow_cross_reference! }
+
+        it 'ignores valid references' do
+          exp = act = "See #{reference}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c84e568e17269417d42fa28421c0f1f368f7934c
--- /dev/null
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -0,0 +1,148 @@
+require 'spec_helper'
+require 'html/pipeline'
+
+module Gitlab::Markdown
+  describe LabelReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project)   { create(:empty_project) }
+    let(:label)     { create(:label, project: project) }
+    let(:reference) { "~#{label.id}" }
+
+    it 'requires project context' do
+      expect { described_class.call('Label ~123', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %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
+      end
+    end
+
+    it 'includes default classes' do
+      doc = filter("Label #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
+    end
+
+    it 'includes an optional custom class' do
+      doc = filter("Label #{reference}", reference_class: 'custom')
+      expect(doc.css('a').first.attr('class')).to include 'custom'
+    end
+
+    it 'supports an :only_path context' do
+      doc = filter("Label #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true)
+    end
+
+    describe 'label span element' do
+      it 'includes default classes' do
+        doc = 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}")
+        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}")
+
+        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 = 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 ~#{label.id + 1}"
+
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'String-based single-word references' do
+      let(:label)     { create(:label, name: 'gfm', project: project) }
+      let(:reference) { "~#{label.name}" }
+
+      it 'links to a valid reference' do
+        doc = filter("See #{reference}")
+
+        expect(doc.css('a').first.attr('href')).to eq urls.
+          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}.)")
+        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+      end
+
+      it 'ignores invalid label names' do
+        exp = act = "Label ~#{label.name.reverse}"
+
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'String-based multi-word references in quotes' do
+      let(:label) { create(:label, name: 'gfm references', project: project) }
+
+      context 'in single quotes' do
+        let(:reference) { "~'#{label.name}'" }
+
+        it 'links to a valid reference' do
+          doc = filter("See #{reference}")
+
+          expect(doc.css('a').first.attr('href')).to eq urls.
+            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}.)")
+          expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+        end
+
+        it 'ignores invalid label names' do
+          exp = act = "Label ~'#{label.name.reverse}'"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'in double quotes' do
+        let(:reference) { %(~"#{label.name}") }
+
+        it 'links to a valid reference' do
+          doc = filter("See #{reference}")
+
+          expect(doc.css('a').first.attr('href')).to eq urls.
+            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}.)")
+          expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+        end
+
+        it 'ignores invalid label names' do
+          exp = act = %(Label ~"#{label.name.reverse}")
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    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
new file mode 100644
index 0000000000000000000000000000000000000000..0f66442269b7dba839a6f0add9005ababbb5981d
--- /dev/null
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe MergeRequestReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project) { create(:project) }
+    let(:merge)   { create(:merge_request, source_project: project) }
+
+    it 'requires project context' do
+      expect { described_class.call('MergeRequest !123', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %w(pre code a style).each do |elem|
+      it "ignores valid references contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>Merge !#{merge.iid}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'internal reference' do
+      let(:reference) { "!#{merge.iid}" }
+
+      it 'links to a valid reference' do
+        doc = 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}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid merge IDs' do
+        exp = act = "Merge !#{merge.iid + 1}"
+
+        expect(filter(act).to_html).to eq exp
+      end
+
+      it 'includes a title attribute' do
+        doc = 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}")
+        expect(doc.text).to eq "Merge #{reference}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("Merge #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("Merge #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path context' do
+        doc = filter("Merge #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).not_to match %r(https?://)
+        expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+      end
+    end
+
+    context 'cross-project reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:project, namespace: namespace) }
+      let(:merge)     { create(:merge_request, source_project: project2) }
+      let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
+
+      context 'when user can access reference' do
+        before { allow_cross_reference! }
+
+        it 'links to a valid reference' do
+          doc = filter("See #{reference}")
+
+          expect(doc.css('a').first.attr('href')).
+            to eq urls.namespace_project_merge_request_url(project2.namespace,
+                                                          project, merge)
+        end
+
+        it 'links with adjacent text' do
+          doc = 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 #{project2.path_with_namespace}!#{merge.iid + 1}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'when user cannot access reference' do
+        before { disallow_cross_reference! }
+
+        it 'ignores valid references' do
+          exp = act = "See #{reference}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..79533a90b558d2e8f7797252d632e61530d8e98f
--- /dev/null
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe SnippetReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project)   { create(:empty_project) }
+    let(:snippet)   { create(:project_snippet, project: project) }
+    let(:reference) { "$#{snippet.id}" }
+
+    it 'requires project context' do
+      expect { described_class.call('Snippet $123', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    %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
+      end
+    end
+
+    context 'internal reference' do
+      it 'links to a valid reference' do
+        doc = 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}.)")
+        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+      end
+
+      it 'ignores invalid snippet IDs' do
+        exp = act = "Snippet $#{snippet.id + 1}"
+
+        expect(filter(act).to_html).to eq exp
+      end
+
+      it 'includes a title attribute' do
+        doc = 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}")
+        expect(doc.text).to eq "Snippet #{reference}"
+      end
+
+      it 'includes default classes' do
+        doc = filter("Snippet #{reference}")
+        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+      end
+
+      it 'includes an optional custom class' do
+        doc = filter("Snippet #{reference}", reference_class: 'custom')
+        expect(doc.css('a').first.attr('class')).to include 'custom'
+      end
+
+      it 'supports an :only_path context' do
+        doc = filter("Snippet #{reference}", only_path: true)
+        link = doc.css('a').first.attr('href')
+
+        expect(link).not_to match %r(https?://)
+        expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+      end
+    end
+
+    context 'cross-project reference' do
+      let(:namespace) { create(:namespace, name: 'cross-reference') }
+      let(:project2)  { create(:empty_project, namespace: namespace) }
+      let(:snippet)   { create(:project_snippet, project: project2) }
+      let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
+
+      context 'when user can access reference' do
+        before { allow_cross_reference! }
+
+        it 'links to a valid reference' do
+          doc = 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}.)")
+          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 #{project2.path_with_namespace}$#{snippet.id + 1}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+
+      context 'when user cannot access reference' do
+        before { disallow_cross_reference! }
+
+        it 'ignores valid references' do
+          exp = act = "See #{reference}"
+
+          expect(filter(act).to_html).to eq exp
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a5eb927072e5f5a79baf989efdfaf7455f7eb400
--- /dev/null
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe UserReferenceFilter do
+    include ReferenceFilterSpecHelper
+
+    let(:project) { create(:empty_project) }
+    let(:user)    { create(:user) }
+
+    it 'requires project context' do
+      expect { described_class.call('Example @mention', {}) }.
+        to raise_error(ArgumentError, /:project/)
+    end
+
+    it 'ignores invalid users' do
+      exp = act = 'Hey @somebody'
+      expect(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 @#{user.username}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    context 'mentioning a user' do
+      it 'links to a User' do
+        doc = filter("Hey @#{user.username}")
+        expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+      end
+
+      # TODO (rspeicher): This test might be overkill
+      it 'links to a User with a period' do
+        user = create(:user, name: 'alphA.Beta')
+
+        doc = filter("Hey @#{user.username}")
+        expect(doc.css('a').length).to eq 1
+      end
+
+      # TODO (rspeicher): This test might be overkill
+      it 'links to a User with an underscore' do
+        user = create(:user, name: 'ping_pong_king')
+
+        doc = filter("Hey @#{user.username}")
+        expect(doc.css('a').length).to eq 1
+      end
+    end
+
+    context 'mentioning a group' do
+      let(:group) { create(:group) }
+      let(:user)  { create(:user) }
+
+      it 'links to a Group that the current user can read' do
+        group.add_user(user, Gitlab::Access::DEVELOPER)
+
+        doc = filter("Hey @#{group.name}", current_user: user)
+        expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+      end
+
+      it 'ignores references to a Group that the current user cannot read' do
+        doc = filter("Hey @#{group.name}", current_user: user)
+        expect(doc.to_html).to eq "Hey @#{group.name}"
+      end
+    end
+
+    it 'links with adjacent text' do
+      skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.'
+      doc = filter("Mention me (@#{user.username}.)")
+      expect(doc.to_html).to match(/\(<a.+>@#{user.username}<\/a>\.\)/)
+    end
+
+    it 'supports a special @all mention' do
+      doc = filter("Hey @all")
+      expect(doc.css('a').length).to eq 1
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.namespace_project_url(project.namespace, project)
+    end
+
+    it 'includes default classes' do
+      doc = filter("Hey @#{user.username}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+    end
+
+    it 'includes an optional custom class' do
+      doc = filter("Hey @#{user.username}", reference_class: 'custom')
+      expect(doc.css('a').first.attr('class')).to include 'custom'
+    end
+
+    it 'supports an :only_path context' do
+      doc = filter("Hey @#{user.username}", 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
+  end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index c9fb62b61ae7a8218efc464484e362baf4685e60..6fba140f69d8b2091257d2781fe4f86554290233 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -74,7 +74,7 @@ describe Gitlab::ReferenceExtractor do
   end
 
   it 'handles all possible kinds of references' do
-    accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym }
+    accessors = described_class::TYPES.map { |t| "#{t}s".to_sym }
     expect(subject).to respond_to(*accessors)
   end
 
@@ -106,6 +106,15 @@ describe Gitlab::ReferenceExtractor do
     expect(subject.merge_requests).to eq([@m1, @m0])
   end
 
+  it 'accesses valid labels' do
+    @l0 = create(:label, title: 'one', project: project)
+    @l1 = create(:label, title: 'two', project: project)
+    @l2 = create(:label)
+
+    subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
+    expect(subject.labels).to eq([@l0, @l1])
+  end
+
   it 'accesses valid snippets' do
     @s0 = create(:project_snippet, project: project)
     @s1 = create(:project_snippet, project: project)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e6d5545f81284d501473c97ac9be8bba0c8639a6..327f3e6d23cb47167f880d93f4c69ea334adc61a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -110,17 +110,22 @@ describe API::API, api: true  do
     end
 
     it 'should return 400 error if name not given' do
-      post api('/users', admin), email: 'test@example.com', password: 'pass1234'
+      post api('/users', admin), attributes_for(:user).except(:name)
       expect(response.status).to eq(400)
     end
 
     it 'should return 400 error if password not given' do
-      post api('/users', admin), email: 'test@example.com', name: 'test'
+      post api('/users', admin), attributes_for(:user).except(:password)
       expect(response.status).to eq(400)
     end
 
-    it "should return 400 error if email not given" do
-      post api('/users', admin), password: 'pass1234', name: 'test'
+    it 'should return 400 error if email not given' do
+      post api('/users', admin), attributes_for(:user).except(:email)
+      expect(response.status).to eq(400)
+    end
+
+    it 'should return 400 error if username not given' do
+      post api('/users', admin), attributes_for(:user).except(:username)
       expect(response.status).to eq(400)
     end
 
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bcee5715cad051658e518e7ff590799ee5598a50
--- /dev/null
+++ b/spec/support/reference_filter_spec_helper.rb
@@ -0,0 +1,47 @@
+# Common methods and setup for Gitlab::Markdown reference filter specs
+#
+# Must be included into specs manually
+module ReferenceFilterSpecHelper
+  extend ActiveSupport::Concern
+
+  included do
+    before { set_default_url_options }
+  end
+
+  # Allow *_url helpers to work
+  def set_default_url_options
+    Rails.application.routes.default_url_options = {
+      host: 'example.foo'
+    }
+  end
+
+  # Shortcut to Rails' auto-generated routes helpers, to avoid including the
+  # module
+  def urls
+    Rails.application.routes.url_helpers
+  end
+
+  # Perform `call` on the described class
+  #
+  # Automatically passes the current `project` value to the context if none is
+  # provided.
+  #
+  # html     - String text to pass to the filter's `call` method.
+  # contexts - Hash context for the filter. (default: {project: project})
+  #
+  # Returns the String text returned by the filter's `call` method.
+  def filter(html, contexts = {})
+    contexts.reverse_merge!(project: project)
+    described_class.call(html, contexts)
+  end
+
+  def allow_cross_reference!
+    allow_any_instance_of(described_class).
+      to receive(:user_can_reference_project?).and_return(true)
+  end
+
+  def disallow_cross_reference!
+    allow_any_instance_of(described_class).
+      to receive(:user_can_reference_project?).and_return(false)
+  end
+end
diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
new file mode 100644
index 0000000000000000000000000000000000000000..3815731fd2fd82fbcfe6f4237c101102e77c1593
--- /dev/null
+++ b/vendor/assets/javascripts/jasmine-fixture.js
@@ -0,0 +1,433 @@
+/* jasmine-fixture - 1.2.2
+ * Makes injecting HTML snippets into the DOM easy & clean!
+ * https://github.com/searls/jasmine-fixture
+ */
+(function() {
+  var createHTMLBlock,
+    __slice = [].slice;
+
+  (function($) {
+    var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
+    root = this;
+    originalJasmineFixture = root.jasmineFixture;
+    originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
+    originalAffix = root.affix;
+    _ = function(list) {
+      return {
+        inject: function(iterator, memo) {
+          var item, _i, _len, _results;
+          _results = [];
+          for (_i = 0, _len = list.length; _i < _len; _i++) {
+            item = list[_i];
+            _results.push(memo = iterator(memo, item));
+          }
+          return _results;
+        }
+      };
+    };
+    root.jasmineFixture = function($) {
+      var $whatsTheRootOf, affix, create, jasmineFixture, noConflict;
+      affix = function(selectorOptions) {
+        return create.call(this, selectorOptions, true);
+      };
+      create = function(selectorOptions, attach) {
+        var $top;
+        $top = null;
+        _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
+          var $el;
+          if (elementSelector === ">") {
+            return $parent;
+          }
+          $el = createHTMLBlock($, elementSelector);
+          if (attach || $top) {
+            $el.appendTo($parent);
+          }
+          $top || ($top = $el);
+          return $el;
+        }, $whatsTheRootOf(this));
+        return $top;
+      };
+      noConflict = function() {
+        var currentJasmineFixture, _ref1;
+        currentJasmineFixture = jasmine.fixture;
+        root.jasmineFixture = originalJasmineFixture;
+        if ((_ref1 = root.jasmine) != null) {
+          _ref1.fixture = originalJasmineDotFixture;
+        }
+        root.affix = originalAffix;
+        return currentJasmineFixture;
+      };
+      $whatsTheRootOf = function(that) {
+        if (that.jquery != null) {
+          return that;
+        } else if ($('#jasmine_content').length > 0) {
+          return $('#jasmine_content');
+        } else {
+          return $('<div id="jasmine_content"></div>').appendTo('body');
+        }
+      };
+      jasmineFixture = {
+        affix: affix,
+        create: create,
+        noConflict: noConflict
+      };
+      ewwSideEffects(jasmineFixture);
+      return jasmineFixture;
+    };
+    ewwSideEffects = function(jasmineFixture) {
+      var _ref1;
+      if ((_ref1 = root.jasmine) != null) {
+        _ref1.fixture = jasmineFixture;
+      }
+      $.fn.affix = root.affix = jasmineFixture.affix;
+      return afterEach(function() {
+        return $('#jasmine_content').remove();
+      });
+    };
+    if ($) {
+      return jasmineFixture = root.jasmineFixture($);
+    } else {
+      return root.affix = function() {
+        var nowJQueryExists;
+        nowJQueryExists = window.jQuery || window.$;
+        if (nowJQueryExists != null) {
+          jasmineFixture = root.jasmineFixture(nowJQueryExists);
+          return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
+        } else {
+          throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
+        }
+      };
+    }
+  })(window.jQuery || window.$);
+
+  createHTMLBlock = (function() {
+    var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
+    createHTMLBlock = function($, ZenObject, data, functions, indexes) {
+      var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
+      if ($.isPlainObject(ZenObject)) {
+        ZenCode = ZenObject.main;
+      } else {
+        ZenCode = ZenObject;
+        ZenObject = {
+          main: ZenCode
+        };
+      }
+      origZenCode = ZenCode;
+      if (indexes === undefined) {
+        indexes = {};
+      }
+      if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
+        if ($.isArray(data)) {
+          forScope = ZenCode;
+        } else {
+          obj = parseEnclosure(ZenCode, "!");
+          obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
+          forScope = parseVariableScope(ZenCode);
+        }
+        while (forScope.charAt(0) === "@") {
+          forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
+        }
+        zo = ZenObject;
+        zo.main = forScope;
+        el = $();
+        if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
+          if (!$.isArray(data) && obj.indexOf(":") > 0) {
+            indexName = obj.substring(0, obj.indexOf(":"));
+            obj = obj.substr(obj.indexOf(":") + 1);
+          }
+          arr = ($.isArray(data) ? data : data[obj]);
+          zc = zo.main;
+          if ($.isArray(arr) || $.isPlainObject(arr)) {
+            $.map(arr, function(value, index) {
+              var next;
+              zo.main = zc;
+              if (indexName !== undefined) {
+                indexes[indexName] = index;
+              }
+              if (!$.isPlainObject(value)) {
+                value = {
+                  value: value
+                };
+              }
+              next = createHTMLBlock($, zo, value, functions, indexes);
+              if (el.length !== 0) {
+                return $.each(next, function(index, value) {
+                  return el.push(value);
+                });
+              }
+            });
+          }
+          if (!$.isArray(data)) {
+            ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
+          } else {
+            ZenCode = "";
+          }
+        } else if (ZenCode.substring(0, 4) === "!if:") {
+          result = parseContents("!" + obj + "!", data, indexes);
+          if (result !== "undefined" || result !== "false" || result !== "") {
+            el = createHTMLBlock($, zo, data, functions, indexes);
+          }
+          ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
+        }
+        ZenObject.main = ZenCode;
+      } else if (ZenCode.charAt(0) === "(") {
+        paren = parseEnclosure(ZenCode, "(", ")");
+        inner = paren.substring(1, paren.length - 1);
+        ZenCode = ZenCode.substr(paren.length);
+        zo = ZenObject;
+        zo.main = inner;
+        el = createHTMLBlock($, zo, data, functions, indexes);
+      } else {
+        blocks = ZenCode.match(regZenTagDfn);
+        block = blocks[0];
+        if (block.length === 0) {
+          return "";
+        }
+        if (block.indexOf("@") >= 0) {
+          ZenCode = parseReferences(ZenCode, ZenObject);
+          zo = ZenObject;
+          zo.main = ZenCode;
+          return createHTMLBlock($, zo, data, functions, indexes);
+        }
+        block = parseContents(block, data, indexes);
+        blockClasses = parseClasses($, block);
+        if (regId.test(block)) {
+          blockId = regId.exec(block)[1];
+        }
+        blockAttrs = parseAttributes(block, data);
+        blockTag = (block.charAt(0) === "{" ? "span" : "div");
+        if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
+          blockTag = regTag.exec(block)[1];
+        }
+        if (block.search(regCBrace) !== -1) {
+          blockHTML = block.match(regCBrace)[1];
+        }
+        blockAttrs = $.extend(blockAttrs, {
+          id: blockId,
+          "class": blockClasses,
+          html: blockHTML
+        });
+        el = $("<" + blockTag + ">", blockAttrs);
+        el.attr(blockAttrs);
+        el = bindEvents(block, el, functions);
+        el = bindData(block, el, data);
+        ZenCode = ZenCode.substr(blocks[0].length);
+        ZenObject.main = ZenCode;
+      }
+      if (ZenCode.length > 0) {
+        if (ZenCode.charAt(0) === ">") {
+          if (ZenCode.charAt(1) === "(") {
+            zc = parseEnclosure(ZenCode.substr(1), "(", ")");
+            ZenCode = ZenCode.substr(zc.length + 1);
+          } else if (ZenCode.charAt(1) === "!") {
+            obj = parseEnclosure(ZenCode.substr(1), "!");
+            forScope = parseVariableScope(ZenCode.substr(1));
+            zc = obj + forScope;
+            ZenCode = ZenCode.substr(zc.length + 1);
+          } else {
+            len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
+            zc = ZenCode.substring(1, len);
+            ZenCode = ZenCode.substr(len);
+          }
+          zo = ZenObject;
+          zo.main = zc;
+          els = $(createHTMLBlock($, zo, data, functions, indexes));
+          els.appendTo(el);
+        }
+        if (ZenCode.charAt(0) === "+") {
+          zo = ZenObject;
+          zo.main = ZenCode.substr(1);
+          el2 = createHTMLBlock($, zo, data, functions, indexes);
+          $.each(el2, function(index, value) {
+            return el.push(value);
+          });
+        }
+      }
+      ret = el;
+      return ret;
+    };
+    bindData = function(ZenCode, el, data) {
+      var datas, i, split;
+      if (ZenCode.search(regDatas) === 0) {
+        return el;
+      }
+      datas = ZenCode.match(regDatas);
+      if (datas === null) {
+        return el;
+      }
+      i = 0;
+      while (i < datas.length) {
+        split = regData.exec(datas[i]);
+        if (split[3] === undefined) {
+          $(el).data(split[1], data[split[1]]);
+        } else {
+          $(el).data(split[1], data[split[3]]);
+        }
+        i++;
+      }
+      return el;
+    };
+    bindEvents = function(ZenCode, el, functions) {
+      var bindings, fn, i, split;
+      if (ZenCode.search(regEvents) === 0) {
+        return el;
+      }
+      bindings = ZenCode.match(regEvents);
+      if (bindings === null) {
+        return el;
+      }
+      i = 0;
+      while (i < bindings.length) {
+        split = regEvent.exec(bindings[i]);
+        if (split[2] === undefined) {
+          fn = functions[split[1]];
+        } else {
+          fn = functions[split[2]];
+        }
+        $(el).bind(split[1], fn);
+        i++;
+      }
+      return el;
+    };
+    parseAttributes = function(ZenBlock, data) {
+      var attrStrs, attrs, i, parts;
+      if (ZenBlock.search(regAttrDfn) === -1) {
+        return undefined;
+      }
+      attrStrs = ZenBlock.match(regAttrDfn);
+      attrs = {};
+      i = 0;
+      while (i < attrStrs.length) {
+        parts = regAttr.exec(attrStrs[i]);
+        attrs[parts[1]] = "";
+        if (parts[3] !== undefined) {
+          attrs[parts[1]] = parseContents(parts[3], data);
+        }
+        i++;
+      }
+      return attrs;
+    };
+    parseClasses = function($, ZenBlock) {
+      var classes, clsString, i;
+      ZenBlock = ZenBlock.match(regTagNotContent)[0];
+      if (ZenBlock.search(regClasses) === -1) {
+        return undefined;
+      }
+      classes = ZenBlock.match(regClasses);
+      clsString = "";
+      i = 0;
+      while (i < classes.length) {
+        clsString += " " + regClass.exec(classes[i])[1];
+        i++;
+      }
+      return $.trim(clsString);
+    };
+    parseContents = function(ZenBlock, data, indexes) {
+      var html;
+      if (indexes === undefined) {
+        indexes = {};
+      }
+      html = ZenBlock;
+      if (data === undefined) {
+        return html;
+      }
+      while (regExclamation.test(html)) {
+        html = html.replace(regExclamation, function(str, str2) {
+          var begChar, fn, val;
+          begChar = "";
+          if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
+            return str;
+          }
+          if (str.charAt(0) !== "!") {
+            begChar = str.charAt(0);
+            str = str.substring(2, str.length - 1);
+          }
+          fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
+          val = unescape(fn(data, indexes));
+          return begChar + val;
+        });
+      }
+      html = html.replace(/\\./g, function(str) {
+        return str.charAt(1);
+      });
+      return unescape(html);
+    };
+    parseEnclosure = function(ZenCode, open, close, count) {
+      var index, ret;
+      if (close === undefined) {
+        close = open;
+      }
+      index = 1;
+      if (count === undefined) {
+        count = (ZenCode.charAt(0) === open ? 1 : 0);
+      }
+      if (count === 0) {
+        return;
+      }
+      while (count > 0 && index < ZenCode.length) {
+        if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
+          count--;
+        } else {
+          if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
+            count++;
+          }
+        }
+        index++;
+      }
+      ret = ZenCode.substring(0, index);
+      return ret;
+    };
+    parseReferences = function(ZenCode, ZenObject) {
+      ZenCode = ZenCode.replace(regReference, function(str) {
+        var fn;
+        str = str.substr(1);
+        fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
+        return fn(ZenObject, parseReferences);
+      });
+      return ZenCode;
+    };
+    parseVariableScope = function(ZenCode) {
+      var forCode, rest, tag;
+      if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
+        return undefined;
+      }
+      forCode = parseEnclosure(ZenCode, "!");
+      ZenCode = ZenCode.substr(forCode.length);
+      if (ZenCode.charAt(0) === "(") {
+        return parseEnclosure(ZenCode, "(", ")");
+      }
+      tag = ZenCode.match(regZenTagDfn)[0];
+      ZenCode = ZenCode.substr(tag.length);
+      if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
+        return tag;
+      } else if (ZenCode.charAt(0) === ">") {
+        rest = "";
+        rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
+        return tag + ">" + rest;
+      }
+      return undefined;
+    };
+    regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
+    regTag = /(\w+)/i;
+    regId = /(?:^|\b)#([\w-!]+)/i;
+    regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
+    /*
+     See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
+    */
+
+    regClasses = /(\.[\w-]+)(?!["\w])/g;
+    regClass = /\.([\w-]+)/i;
+    regReference = /(@[\w$_][\w$_\d]+)/i;
+    regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
+    regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
+    regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i;
+    regCBrace = /\{(([^\}]|\\\})+)\}/i;
+    regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
+    regEvents = /\~[\w$]+(=[\w$]+)?/g;
+    regEvent = /\~([\w$]+)=([\w$]+)/i;
+    regDatas = /&[\w$]+(=[\w$]+)?/g;
+    regData = /&([\w$]+)(=([\w$]+))?/i;
+    return createHTMLBlock;
+  })();
+
+}).call(this);