diff --git a/.gitignore b/.gitignore
index 1eb785451f4904144168fcdf7b941584a9be91d7..8f861d76a373b764865e8201dcde465f1d5c9ef7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
 .sass-cache/
 .secret
 .vagrant
+.byebug_history
 Vagrantfile
 backups/*
 config/aws.yml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c477721f9da67a1912242535d8fa9a6f5f71556c..bd013d50faafbb5f347476dc8eb0015d457d14b5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,16 +12,18 @@ cache:
 
 variables:
   MYSQL_ALLOW_EMPTY_PASSWORD: "1"
+  # retry tests only in CI environment
+  RSPEC_RETRY_RETRY_COUNT: "3"
 
 before_script:
   - source ./scripts/prepare_build.sh
   - ruby -v
   - which ruby
-  - gem install bundler --no-ri --no-rdoc
+  - retry gem install bundler --no-ri --no-rdoc
   - cp config/gitlab.yml.example config/gitlab.yml
   - touch log/application.log
   - touch log/test.log
-  - bundle install --without postgres production --jobs $(nproc)  "${FLAGS[@]}"
+  - retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
   - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
 
 stages:
@@ -69,15 +71,6 @@ spec:services:
     - ruby
     - mysql
 
-spec:benchmark:
-  stage: test
-  script:
-    - RAILS_ENV=test bundle exec rake spec:benchmark
-  tags:
-    - ruby
-    - mysql
-  allow_failure: true
-
 spec:other:
   stage: test
   script:
@@ -241,22 +234,6 @@ spec:services:ruby22:
     - ruby
     - mysql
 
-spec:benchmark:ruby22:
-  stage: test
-  image: ruby:2.2
-  only:
-  - master
-  script:
-    - RAILS_ENV=test bundle exec rake spec:benchmark
-  cache:
-    key: "ruby22"
-    paths:
-    - vendor
-  tags:
-    - ruby
-    - mysql
-  allow_failure: true
-
 spec:other:ruby22:
   stage: test
   image: ruby:2.2
@@ -330,4 +307,4 @@ notify:slack:
     - master@gitlab-org/gitlab-ce
     - tags@gitlab-org/gitlab-ce
     - master@gitlab-org/gitlab-ee
-    - tags@gitlab-org/gitlab-ee
\ No newline at end of file
+    - tags@gitlab-org/gitlab-ee
diff --git a/CHANGELOG b/CHANGELOG
index 3613f842662d740db546288f06f632c5a2fde584..d11f02e6e27e398831a847c6113e08b907e604ae 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,19 +1,42 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.6.0 (unreleased)
+  - Support Golang subpackage fetching (Stan Hu)
   - Contributions to forked projects are included in calendar
   - Improve the formatting for the user page bio (Connor Shea)
+  - Removed the default password from the initial admin account created during
+    setup. A password can be provided during setup (see installation docs), or
+    GitLab will ask the user to create a new one upon first visit.
   - Fix issue when pushing to projects ending in .wiki
   - Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
   - Don't load all of GitLab in mail_room
+  - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
+  - Memoize @group in Admin::GroupsController (Yatish Mehta)
   - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
   - Strip leading and trailing spaces in URL validator (evuez)
+  - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
   - Return empty array instead of 404 when commit has no statuses in commit status API
+  - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip)
+  - Rewrite logo to simplify SVG code (Sean Lang)
+  - Refactor and greatly improve search performance
   - Add support for cross-project label references
   - Update documentation to reflect Guest role not being enforced on internal projects
   - Allow search for logged out users
+  - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
   - Don't show Issues/MRs from archived projects in Groups view
   - Increase the notes polling timeout over time (Roberto Dip)
+  - Add shortcut to toggle markdown preview (Florent Baldino)
+  - Show labels in dashboard and group milestone views
+  - Add main language of a project in the list of projects (Tiago Botelho)
+  - Add ability to show archived projects on dashboard, explore and group pages
+  - Move group activity to separate page
+
+v 8.5.5
+  - Ensure removing a project removes associated Todo entries
+  - Prevent a 500 error in Todos when author was removed
+  - Fix pagination for filtered dashboard and explore pages
+  - Fix "Show all" link behavior
+  - Add #upcoming filter to Milestone filter (Tiago Botelho)
 
 v 8.5.4
   - Do not cache requests for badges (including builds badge)
@@ -23,6 +46,7 @@ v 8.5.3
   - Sort starred projects on dashboard based on last activity by default
   - Show commit message in JIRA mention comment
   - Makes issue page and merge request page usable on mobile browsers.
+  - Improved UI for profile settings
 
 v 8.5.2
   - Fix sidebar overlapping content when screen width was below 1200px
@@ -64,7 +88,7 @@ v 8.5.1
 v 8.5.0
   - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
   - Cache various Repository methods to improve performance (Yorick Peterse)
-  - Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
+  - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
   - Ensure rake tasks that don't need a DB connection can be run without one
   - Update New Relic gem to 3.14.1.311 (Stan Hu)
   - Add "visibility" flag to GET /projects api endpoint
@@ -197,7 +221,7 @@ v 8.4.0
   - Add housekeeping function to project settings page
   - The default GitLab logo now acts as a loading indicator
   - Fix caching issue where build status was not updating in project dashboard (Stan Hu)
-  - Accept 2xx status codes for successful Web hook triggers (Stan Hu)
+  - Accept 2xx status codes for successful Webhook triggers (Stan Hu)
   - Fix missing date of month in network graph when commits span a month (Stan Hu)
   - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
   - Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
@@ -297,7 +321,7 @@ v 8.3.0
   - Fix broken group avatar upload under "New group" (Stan Hu)
   - Update project repositorize size and commit count during import:repos task (Stan Hu)
   - Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
-  - Handle and report SSL errors in Web hook test (Stan Hu)
+  - Handle and report SSL errors in Webhook test (Stan Hu)
   - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
   - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
   - WIP identifier on merge requests no longer requires trailing space
@@ -517,7 +541,7 @@ v 8.1.0
   - Ensure code blocks are properly highlighted after a note is updated
   - Fix wrong access level badge on MR comments
   - Hide password in the service settings form
-  - Move CI web hooks page to project settings area
+  - Move CI webhooks page to project settings area
   - Fix User Identities API. It now allows you to properly create or update user's identities.
   - Add user preference to change layout width (Peter Göbel)
   - Use commit status in merge request widget as preferred source of CI status
@@ -560,7 +584,7 @@ v 8.0.3
   - Fix URL shown in Slack notifications
   - Fix bug where projects would appear to be stuck in the forked import state (Stan Hu)
   - Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu)
-  - Add work_in_progress key to MR web hooks (Ben Boeckel)
+  - Add work_in_progress key to MR webhooks (Ben Boeckel)
 
 v 8.0.2
   - Fix default avatar not rendering in network graph (Stan Hu)
@@ -851,7 +875,7 @@ v 7.12.0
   - Fix milestone "Browse Issues" button.
   - Set milestone on new issue when creating issue from index with milestone filter active.
   - Make namespace API available to all users (Stan Hu)
-  - Add web hook support for note events (Stan Hu)
+  - Add webhook support for note events (Stan Hu)
   - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
   - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
   - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
@@ -958,7 +982,7 @@ v 7.11.0
   - Add "Create Merge Request" buttons to commits and branches pages and push event.
   - Show user roles by comments.
   - Fix automatic blocking of auto-created users from Active Directory.
-  - Call merge request web hook for each new commits (Arthur Gautier)
+  - Call merge request webhook for each new commits (Arthur Gautier)
   - Use SIGKILL by default in Sidekiq::MemoryKiller
   - Fix mentioning of private groups.
   - Add style for <kbd> element in markdown
@@ -1132,7 +1156,7 @@ v 7.9.0
   - Add brakeman (security scanner for Ruby on Rails)
   - Slack username and channel options
   - Add grouped milestones from all projects to dashboard.
-  - Web hook sends pusher email as well as commiter
+  - Webhook sends pusher email as well as commiter
   - Add Bitbucket omniauth provider.
   - Add Bitbucket importer.
   - Support referencing issues to a project whose name starts with a digit
@@ -1255,7 +1279,7 @@ v 7.8.0
   - Allow notification email to be set separately from primary email.
   - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
   - Don't have Markdown preview fail for long comments/wiki pages.
-  - When test web hook - show error message instead of 500 error page if connection to hook url was reset
+  - When test webhook - show error message instead of 500 error page if connection to hook url was reset
   - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
   - Added persistent collapse button for left side nav bar (Jason Blanchard)
   - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
@@ -1272,7 +1296,7 @@ v 7.8.0
   - Show projects user contributed to on user page. Show stars near project on user page.
   - Improve database performance for GitLab
   - Add Asana service (Jeremy Benoist)
-  - Improve project web hooks with extra data
+  - Improve project webhooks with extra data
 
 v 7.7.2
   - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
@@ -1757,7 +1781,7 @@ v 6.4.0
   - Side-by-side diff view (Steven Thonus)
   - Internal projects (Jason Hollingsworth)
   - Allow removal of avatar (Drew Blessing)
-  - Project web hooks now support issues and merge request events
+  - Project webhooks now support issues and merge request events
   - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
   - Expire event cache on avatar creation/removal (Drew Blessing)
   - Archiving old projects (Steven Thonus)
@@ -1827,7 +1851,7 @@ v 6.2.0
   - Added search for projects by name to api (Izaak Alpert)
   - Make default user theme configurable (Izaak Alpert)
   - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
-  - Rake tasks for web hooks management (Jonhnny Weslley)
+  - Rake tasks for webhooks management (Jonhnny Weslley)
   - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
   - API: Remove group
   - API: Remove project
@@ -2030,7 +2054,7 @@ v 4.2.0
   - Async gitolite calls
   - added satellites logs
   - can_create_group, can_create_team booleans for User
-  - Process web hooks async
+  - Process webhooks async
   - GFM: Fix images escaped inside links
   - Network graph improved
   - Switchable branches for network graph
@@ -2064,7 +2088,7 @@ v 4.1.0
 
 v 4.0.0
   - Remove project code and path from API. Use id instead
-  - Return valid cloneable url to repo for web hook
+  - Return valid cloneable url to repo for webhook
   - Fixed backup issue
   - Reorganized settings
   - Fixed commits compare
diff --git a/Gemfile b/Gemfile
index c66ef3cffada3d03b5aac83df8182e143304915f..1550afb1b56e8941e29634e2044d5863f90b617f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -30,7 +30,7 @@ gem 'omniauth-github',        '~> 1.1.1'
 gem 'omniauth-gitlab',        '~> 1.0.0'
 gem 'omniauth-google-oauth2', '~> 0.2.0'
 gem 'omniauth-kerberos',      '~> 0.3.0', group: :kerberos
-gem 'omniauth-saml',          '~> 1.4.2'
+gem 'omniauth-saml',          '~> 1.5.0'
 gem 'omniauth-shibboleth',    '~> 1.2.0'
 gem 'omniauth-twitter',       '~> 1.2.0'
 gem 'omniauth_crowd',         '~> 2.2.0'
@@ -263,7 +263,9 @@ group :development, :test do
   gem 'database_cleaner',   '~> 1.4.0'
   gem 'factory_girl_rails', '~> 4.6.0'
   gem 'rspec-rails',        '~> 3.3.0'
+  gem 'rspec-retry'
   gem 'spinach-rails',      '~> 0.2.1'
+  gem 'spinach-rerun-reporter', '~> 0.0.2'
 
   # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
   gem 'minitest', '~> 5.7.0'
@@ -273,7 +275,7 @@ group :development, :test do
 
   gem 'capybara',            '~> 2.4.0'
   gem 'capybara-screenshot', '~> 1.0.0'
-  gem 'poltergeist',         '~> 1.8.1'
+  gem 'poltergeist',         '~> 1.9.0'
 
   gem 'teaspoon', '~> 1.0.0'
   gem 'teaspoon-jasmine', '~> 2.2.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 22c86e4ae8fc480fd20a2ad3f93cc01aeb64bc63..d4e28db00d6b4430b6c7c02375b2817129ce3120 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -358,7 +358,7 @@ GEM
       posix-spawn (~> 0.3)
     gitlab_emoji (0.3.1)
       gemojione (~> 2.2, >= 2.2.1)
-    gitlab_git (9.0.0)
+    gitlab_git (9.0.1)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -532,8 +532,8 @@ GEM
     omniauth-oauth2 (1.3.1)
       oauth2 (~> 1.0)
       omniauth (~> 1.2)
-    omniauth-saml (1.4.2)
-      omniauth (~> 1.1)
+    omniauth-saml (1.5.0)
+      omniauth (~> 1.3)
       ruby-saml (~> 1.1, >= 1.1.1)
     omniauth-shibboleth (1.2.1)
       omniauth (>= 1.0.0)
@@ -552,7 +552,7 @@ GEM
     parser (2.2.3.0)
       ast (>= 1.1, < 3.0)
     pg (0.18.4)
-    poltergeist (1.8.1)
+    poltergeist (1.9.0)
       capybara (~> 2.1)
       cliver (~> 0.3.1)
       multi_json (~> 1.0)
@@ -679,6 +679,8 @@ GEM
       rspec-expectations (~> 3.3.0)
       rspec-mocks (~> 3.3.0)
       rspec-support (~> 3.3.0)
+    rspec-retry (0.4.5)
+      rspec-core
     rspec-support (3.3.0)
     rubocop (0.35.1)
       astrolabe (~> 1.3)
@@ -690,7 +692,7 @@ GEM
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
     ruby-progressbar (1.7.5)
-    ruby-saml (1.1.1)
+    ruby-saml (1.1.2)
       nokogiri (>= 1.5.10)
       uuid (~> 2.3)
     ruby2ruby (2.2.0)
@@ -764,6 +766,8 @@ GEM
       capybara (>= 2.0.0)
       railties (>= 3)
       spinach (>= 0.4)
+    spinach-rerun-reporter (0.0.2)
+      spinach (~> 0.8)
     spring (1.6.4)
     spring-commands-rspec (1.0.4)
       spring (>= 0.9.1)
@@ -971,14 +975,14 @@ DEPENDENCIES
   omniauth-gitlab (~> 1.0.0)
   omniauth-google-oauth2 (~> 0.2.0)
   omniauth-kerberos (~> 0.3.0)
-  omniauth-saml (~> 1.4.2)
+  omniauth-saml (~> 1.5.0)
   omniauth-shibboleth (~> 1.2.0)
   omniauth-twitter (~> 1.2.0)
   omniauth_crowd (~> 2.2.0)
   org-ruby (~> 0.9.12)
   paranoia (~> 2.0)
   pg (~> 0.18.2)
-  poltergeist (~> 1.8.1)
+  poltergeist (~> 1.9.0)
   pry-rails
   quiet_assets (~> 1.0.2)
   rack-attack (~> 4.3.1)
@@ -999,6 +1003,7 @@ DEPENDENCIES
   rouge (~> 1.10.1)
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.3.0)
+  rspec-retry
   rubocop (~> 0.35.0)
   ruby-fogbugz (~> 0.2.1)
   sanitize (~> 2.0)
@@ -1017,6 +1022,7 @@ DEPENDENCIES
   six (~> 0.2.0)
   slack-notifier (~> 1.2.0)
   spinach-rails (~> 0.2.1)
+  spinach-rerun-reporter (~> 0.0.2)
   spring (~> 1.6.4)
   spring-commands-rspec (~> 1.0.4)
   spring-commands-spinach (~> 1.0.0)
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 3e0fdb3f795a18eb07048d82f08dbf9a67ca4282..2ddf8612db30ac4b36ddc18d4b1fb52a86b35cee 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -4,6 +4,7 @@
   namespaces_path: "/api/:version/namespaces.json"
   group_projects_path: "/api/:version/groups/:id/projects.json"
   projects_path: "/api/:version/projects.json"
+  labels_path: "/api/:version/projects/:id/labels"
 
   group: (group_id, callback) ->
     url = Api.buildUrl(Api.group_path)
@@ -61,6 +62,19 @@
     ).done (projects) ->
       callback(projects)
 
+  newLabel: (project_id, data, callback) ->
+    url = Api.buildUrl(Api.labels_path)
+    url = url.replace(':id', project_id)
+
+    data.private_token = gon.api_token
+    $.ajax(
+      url: url
+      type: "POST"
+      data: data
+      dataType: "json"
+    ).done (label) ->
+      callback(label)
+
   # Return group projects list. Filtered by query
   groupProjects: (group_id, query, callback) ->
     url = Api.buildUrl(Api.group_projects_path)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 321da10a00997dc565441a70c5da0b499b49158c..1212e89975b00dae57f8b089f9729b25c366a21c 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -220,17 +220,17 @@ $ ->
     .off 'breakpoint:change'
     .on 'breakpoint:change', (e, breakpoint) ->
       if breakpoint is 'sm' or breakpoint is 'xs'
-        $gutterIcon = $('aside .gutter-toggle').find('i')
+        $gutterIcon = $('.js-sidebar-toggle').find('i')
         if $gutterIcon.hasClass('fa-angle-double-right')
           $gutterIcon.closest('a').trigger('click')
 
   $(document)
-    .off 'click', 'aside .gutter-toggle'
-    .on 'click', 'aside .gutter-toggle', (e, triggered) ->
+    .off 'click', '.js-sidebar-toggle'
+    .on 'click', '.js-sidebar-toggle', (e, triggered) ->
       e.preventDefault()
       $this = $(this)
       $thisIcon = $this.find 'i'
-      $allGutterToggleIcons = $('.gutter-toggle i')
+      $allGutterToggleIcons = $('.js-sidebar-toggle i')
       if $thisIcon.hasClass('fa-angle-double-right')
         $allGutterToggleIcons
           .removeClass('fa-angle-double-right')
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 8f89d3e61a2b64d7344328b2086492ffb54cacdb..03a4487416136a45db0ac73d6e8a2215e859e478 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,6 +1,6 @@
 class @AwardsHandler
   constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
-    $(".add-award").click (event) =>
+    $(".js-add-award").on "click", (event) =>
       event.stopPropagation()
       event.preventDefault()
 
@@ -9,27 +9,46 @@ class @AwardsHandler
     $("html").on 'click', (event) ->
       if !$(event.target).closest(".emoji-menu").length
         if $(".emoji-menu").is(":visible")
-          $(".emoji-menu").hide()
+          $(".emoji-menu").removeClass "is-visible"
+
+    $(".awards")
+      .off "click"
+      .on "click", ".js-emoji-btn", @handleClick
 
     @renderFrequentlyUsedBlock()
-    @setupSearch()
+
+  handleClick: (e) ->
+    e.preventDefault()
+    emoji = $(this)
+      .find(".icon")
+      .data "emoji"
+    awards_handler.addAward emoji
 
   showEmojiMenu: ->
     if $(".emoji-menu").length
-      $(".emoji-menu").show()
-      $("#emoji_search").focus()
-    else
-      $.get "/emojis", (response) ->
-        $(".add-award").after response
-        $(".emoji-menu").show()
+      if $(".emoji-menu").is ".is-visible"
+        $(".emoji-menu").removeClass "is-visible"
+        $("#emoji_search").blur()
+      else
+        $(".emoji-menu").addClass "is-visible"
         $("#emoji_search").focus()
+    else
+      $('.js-add-award').addClass "is-loading"
+      $.get "/emojis", (response) =>
+        $('.js-add-award').removeClass "is-loading"
+        $(".js-award-holder").append response
+        setTimeout =>
+          $(".emoji-menu").addClass "is-visible"
+          $("#emoji_search").focus()
+          @setupSearch()
+        , 200
 
   addAward: (emoji) ->
     emoji = @normilizeEmojiName(emoji)
     @postEmoji emoji, =>
       @addAwardToEmojiBar(emoji)
 
-    $(".emoji-menu").hide()
+    $(".emoji-menu").removeClass "is-visible"
 
   addAwardToEmojiBar: (emoji) ->
     @addEmojiToFrequentlyUsedList(emoji)
@@ -39,7 +58,7 @@ class @AwardsHandler
       if @isActive(emoji)
         @decrementCounter(emoji)
       else
-        counter = @findEmojiIcon(emoji).siblings(".counter")
+        counter = @findEmojiIcon(emoji).siblings(".js-counter")
         counter.text(parseInt(counter.text()) + 1)
         counter.parent().addClass("active")
         @addMeToAuthorList(emoji)
@@ -53,7 +72,7 @@ class @AwardsHandler
     @findEmojiIcon(emoji).parent().hasClass("active")
 
   decrementCounter: (emoji) ->
-    counter = @findEmojiIcon(emoji).siblings(".counter")
+    counter = @findEmojiIcon(emoji).siblings(".js-counter")
     emojiIcon = counter.parent()
     if parseInt(counter.text()) > 1
       counter.text(parseInt(counter.text()) - 1)
@@ -70,9 +89,13 @@ class @AwardsHandler
 
   removeMeFromAuthorList: (emoji) ->
     award_block = @findEmojiIcon(emoji).parent()
-    authors = award_block.attr("data-original-title").split(", ")
+    authors = award_block
+      .attr("data-original-title")
+      .split(", ")
     authors.splice(authors.indexOf("me"),1)
-    award_block.closest(".award").attr("data-original-title", authors.join(", "))
+    award_block
+      .closest(".js-emoji-btn")
+      .attr("data-original-title", authors.join(", "))
     @resetTooltip(award_block)
 
   addMeToAuthorList: (emoji) ->
@@ -98,14 +121,18 @@ class @AwardsHandler
     emojiCssClass = @resolveNameToCssClass(emoji)
 
     nodes = []
-    nodes.push("<div class='award active' title='me'>")
-    nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>")
-    nodes.push("<div class='counter'>1</div>")
-    nodes.push("</div>")
-
-    emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
-
-    $(".award").tooltip()
+    nodes.push(
+      "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>",
+      "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
+      "<span class='award-control-text js-counter'>1</span>",
+      "</button>"
+    )
+
+    emoji_node = $(nodes.join("\n"))
+      .insertBefore(".js-award-holder")
+      .find(".emoji-icon")
+      .data("emoji", emoji)
+    $('.award-control').tooltip()
 
   resolveNameToCssClass: (emoji) ->
     emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
@@ -128,7 +155,7 @@ class @AwardsHandler
         callback.call()
 
   findEmojiIcon: (emoji) ->
-    $(".award [data-emoji='#{emoji}']")
+    $(".awards > .js-emoji-btn [data-emoji='#{emoji}']")
 
   scrollToAwards: ->
     $('body, html').animate({
@@ -164,13 +191,13 @@ class @AwardsHandler
       term = $(ev.target).val()
 
       # Clean previous search results
-      $("ul.emoji-search,h5.emoji-search").remove()
+      $("ul.emoji-menu-search, h5.emoji-search").remove()
 
       if term
         # Generate a search result block
         h5 = $("<h5>").text("Search results").addClass("emoji-search")
         found_emojis = @searchEmojis(term).show()
-        ul = $("<ul>").addClass("emoji-search").append(found_emojis)
+        ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
         $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
         $(".emoji-menu-content").append(h5).append(ul)
       else
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee
index 44d5ddb7d951f39e2d65c47e539891892033fdd8..7afe8bf79e2614b8016a4615d7b2773a720de30e 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/ci/build.coffee
@@ -4,6 +4,8 @@ class CiBuild
   constructor: (build_url, build_status) ->
     clearInterval(CiBuild.interval)
 
+    @initScrollButtonAffix()
+
     if build_status == "running" || build_status == "pending"
       #
       # Bind autoscroll button to follow build output
@@ -38,4 +40,15 @@ class CiBuild
   checkAutoscroll: ->
     $("html,body").scrollTop $("#build-trace").height()  if "enabled" is $("#autoscroll-button").data("state")
 
+  initScrollButtonAffix: ->
+    $buildScroll = $('#js-build-scroll')
+    $body = $('body')
+    $buildTrace = $('#build-trace')
+
+    $buildScroll.affix(
+      offset:
+        bottom: ->
+          $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
+    )
+
 @CiBuild = CiBuild
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index d7feb5d5c87ea3bf10e202c7c0ad0844b1acbd48..ee81fee5868198ffa5fc56a597ddb2fcd0b6233e 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -23,7 +23,7 @@ class Dispatcher
         new Issue()
         shortcut_handler = new ShortcutsIssuable()
         new ZenMode()
-      when 'projects:milestones:show'
+      when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
         new Milestone()
       when 'projects:milestones:new', 'projects:milestones:edit'
         new ZenMode()
@@ -74,8 +74,9 @@ class Dispatcher
         shortcut_handler = new ShortcutsNavigation()
 
         new TreeView() if $('#tree-slider').length
-      when 'groups:show'
+      when 'groups:activity'
         new Activities()
+      when 'groups:show'
         shortcut_handler = new ShortcutsNavigation()
       when 'groups:group_members:index'
         new GroupMembers()
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..8e1449bc59ca0a3f49745a8485b9ef4d7e7c1209
--- /dev/null
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -0,0 +1,270 @@
+class GitLabDropdownFilter
+  BLUR_KEYCODES = [27, 40]
+
+  constructor: (@dropdown, @options) ->
+    @input = @dropdown.find(".dropdown-input .dropdown-input-field")
+
+    # Key events
+    timeout = ""
+    @input.on "keyup", (e) =>
+      if e.keyCode is 13 && @input.val() isnt ""
+        if @options.enterCallback
+          @options.enterCallback()
+        return
+
+      clearTimeout timeout
+      timeout = setTimeout =>
+        blur_field = @shouldBlur e.keyCode
+        search_text = @input.val()
+
+        if blur_field
+          @input.blur()
+
+        if @options.remote
+          @options.query search_text, (data) =>
+            @options.callback(data)
+        else
+          @filter search_text
+      , 250
+
+  shouldBlur: (keyCode) ->
+    return BLUR_KEYCODES.indexOf(keyCode) >= 0
+
+  filter: (search_text) ->
+    data = @options.data()
+    results = data
+
+    if search_text isnt ""
+      results = fuzzaldrinPlus.filter(data, search_text,
+        key: @options.keys
+      )
+
+    @options.callback results
+
+class GitLabDropdownRemote
+  constructor: (@dataEndpoint, @options) ->
+
+  execute: ->
+    if typeof @dataEndpoint is "string"
+      @fetchData()
+    else if typeof @dataEndpoint is "function"
+      if @options.beforeSend
+        @options.beforeSend()
+
+      # Fetch the data by calling the data funcfion
+      @dataEndpoint "", (data) =>
+        if @options.success
+          @options.success(data)
+
+        if @options.beforeSend
+          @options.beforeSend()
+
+  # Fetch the data through ajax if the data is a string
+  fetchData: ->
+    $.ajax(
+      url: @dataEndpoint,
+      dataType: @options.dataType,
+      beforeSend: =>
+        if @options.beforeSend
+          @options.beforeSend()
+      success: (data) =>
+        if @options.success
+          @options.success(data)
+    )
+
+class GitLabDropdown
+  LOADING_CLASS = "is-loading"
+  PAGE_TWO_CLASS = "is-page-two"
+  ACTIVE_CLASS = "is-active"
+
+  constructor: (@el, @options) ->
+    self = @
+    @dropdown = $(@el).parent()
+    search_fields = if @options.search then @options.search.fields else [];
+
+    if @options.data
+      # Remote data
+      @remote = new GitLabDropdownRemote @options.data, {
+        dataType: @options.dataType,
+        beforeSend: @toggleLoading.bind(@)
+        success: (data) =>
+          @fullData = data
+
+          @parseData @fullData
+      }
+
+    # Init filiterable
+    if @options.filterable
+      @filter = new GitLabDropdownFilter @dropdown,
+        remote: @options.filterRemote
+        query: @options.data
+        keys: @options.search.fields
+        data: =>
+          return @fullData
+        callback: (data) =>
+          @parseData data
+        enterCallback: =>
+          @selectFirstRow()
+
+    # Event listeners
+    @dropdown.on "shown.bs.dropdown", @opened
+    @dropdown.on "hidden.bs.dropdown", @hidden
+
+    if @dropdown.find(".dropdown-toggle-page").length
+      @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
+        e.preventDefault()
+        e.stopPropagation()
+
+        @togglePage()
+
+    if @options.selectable
+      selector = ".dropdown-content a"
+
+      if @dropdown.find(".dropdown-toggle-page").length
+        selector = ".dropdown-page-one .dropdown-content a"
+
+      @dropdown.on "click", selector, (e) ->
+        self.rowClicked $(@)
+
+        if self.options.clicked
+          self.options.clicked()
+
+  toggleLoading: ->
+    $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
+
+  togglePage: ->
+    menu = $('.dropdown-menu', @dropdown)
+
+    if menu.hasClass(PAGE_TWO_CLASS)
+      if @remote
+        @remote.execute()
+
+    menu.toggleClass PAGE_TWO_CLASS
+
+  parseData: (data) ->
+    @renderedData = data
+
+    # Render each row
+    html = $.map data, (obj) =>
+      return @renderItem(obj)
+
+    if @options.filterable and data.length is 0
+      # render no matching results
+      html = [@noResults()]
+
+    # Render the full menu
+    full_html = @renderMenu(html.join(""))
+
+    @appendMenu(full_html)
+
+  opened: =>
+    contentHtml = $('.dropdown-content', @dropdown).html()
+    if @remote && contentHtml is ""
+      @remote.execute()
+
+    if @options.filterable
+      @dropdown.find(".dropdown-input-field").focus()
+
+  hidden: =>
+    if @options.filterable
+      @dropdown.find(".dropdown-input-field").blur().val("")
+
+    if @dropdown.find(".dropdown-toggle-page").length
+      $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
+
+
+  # Render the full menu
+  renderMenu: (html) ->
+    menu_html = ""
+
+    if @options.renderMenu
+      menu_html = @options.renderMenu(html)
+    else
+      menu_html = "<ul>#{html}</ul>"
+
+    return menu_html
+
+  # Append the menu into the dropdown
+  appendMenu: (html) ->
+    selector = '.dropdown-content'
+    if @dropdown.find(".dropdown-toggle-page").length
+      selector = ".dropdown-page-one .dropdown-content"
+
+    $(selector, @dropdown).html html
+
+  # Render the row
+  renderItem: (data) ->
+    html = ""
+
+    return "<li class='divider'></li>" if data is "divider"
+
+    if @options.renderRow
+      # Call the render function
+      html = @options.renderRow(data)
+    else
+      selected = if @options.isSelected then @options.isSelected(data) else false
+      url = if @options.url then @options.url(data) else "#"
+      text = if @options.text then @options.text(data) else ""
+      cssClass = "";
+
+      if selected
+        cssClass = "is-active"
+
+      html = "<li>"
+      html += "<a href='#{url}' class='#{cssClass}'>"
+      html += text
+      html += "</a>"
+      html += "</li>"
+
+    return html
+
+  noResults: ->
+    html = "<li>"
+    html += "<a href='#' class='is-focused'>"
+    html += "No matching results."
+    html += "</a>"
+    html += "</li>"
+
+  rowClicked: (el) ->
+    fieldName = @options.fieldName
+    field = @dropdown.parent().find("input[name='#{fieldName}']")
+
+    if el.hasClass(ACTIVE_CLASS)
+      field.remove()
+    else
+      fieldName = @options.fieldName
+      selectedIndex = el.parent().index()
+      if @renderedData
+        selectedObject = @renderedData[selectedIndex]
+      value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
+
+      if @options.multiSelect
+        oldValue = field.val()
+        if oldValue
+          value = "#{oldValue},#{value}"
+      else
+        @dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS
+        field.remove()
+
+      # Toggle active class for the tick mark
+      el.toggleClass "is-active"
+
+      if value
+        if !field.length
+          # Create hidden input for form
+          input = "<input type='hidden' name='#{fieldName}' />"
+          @dropdown.before input
+
+        @dropdown.parent().find("input[name='#{fieldName}']").val value
+
+  selectFirstRow: ->
+    selector = '.dropdown-content li:first-child a'
+    if @dropdown.find(".dropdown-toggle-page").length
+      selector = ".dropdown-page-one .dropdown-content li:first-child a"
+
+    # similute a click on the first link
+    $(selector).trigger "click"
+
+$.fn.glDropdown = (opts) ->
+  return @.each ->
+    new GitLabDropdown @, opts
diff --git a/app/assets/javascripts/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..c5740f27ddd5a154a0ef94efa8a69a4b5e9adab5
--- /dev/null
+++ b/app/assets/javascripts/issue_status_select.js.coffee
@@ -0,0 +1,11 @@
+class @IssueStatusSelect
+  constructor: ->
+    $('.js-issue-status').each (i, el) ->
+      fieldName = $(el).data("field-name")
+
+      $(el).glDropdown(
+        selectable: true
+        fieldName: fieldName
+        id: (obj, el) ->
+          $(el).data("id")
+      )
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..5ade2cb66cb5cb17cb69488afe60e61c1f6c613c
--- /dev/null
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -0,0 +1,92 @@
+class @LabelsSelect
+  constructor: ->
+    $('.js-label-select').each (i, dropdown) ->
+      projectId = $(dropdown).data('project-id')
+      labelUrl = $(dropdown).data("labels")
+      selectedLabel = $(dropdown).data('selected')
+      if selectedLabel
+        selectedLabel = selectedLabel.split(",")
+      newLabelField = $('#new_label_name')
+      newColorField = $('#new_label_color')
+      showNo = $(dropdown).data('show-no')
+      showAny = $(dropdown).data('show-any')
+
+      if newLabelField.length
+        $('.suggest-colors-dropdown a').on "click", (e) ->
+          e.preventDefault()
+          e.stopPropagation()
+          newColorField.val $(this).data("color")
+          $('.js-dropdown-label-color-preview')
+            .css 'background-color', $(this).data("color")
+            .addClass 'is-active'
+
+        $('.js-new-label-btn').on "click", (e) ->
+          e.preventDefault()
+          e.stopPropagation()
+
+          if newLabelField.val() isnt "" && newColorField.val() isnt ""
+            $('.js-new-label-btn').disable()
+
+            # Create new label with API
+            Api.newLabel projectId, {
+              name: newLabelField.val()
+              color: newColorField.val()
+            }, (label) ->
+              $('.js-new-label-btn').enable()
+              $('.dropdown-menu-back', $(dropdown).parent()).trigger "click"
+
+      $(dropdown).glDropdown(
+        data: (term, callback) ->
+          # We have to fetch the JS version of the labels list because there is no
+          # public facing JSON url for labels
+          $.ajax(
+            url: labelUrl
+          ).done (data) ->
+            html = $(data)
+            data = []
+            html.find('.label-row a').each ->
+              data.push(
+                title: $(@).text().trim()
+              )
+
+            if showNo
+              data.unshift(
+                id: "0"
+                title: 'No label'
+              )
+
+            if showAny
+              data.unshift(
+                title: 'Any label'
+              )
+
+            if data.length > 2
+              data.splice 2, 0, "divider"
+
+            callback data
+        renderRow: (label) ->
+          if $.isArray(selectedLabel)
+            selected = ""
+            $.each selectedLabel, (i, selectedLbl) ->
+              selectedLbl = selectedLbl.trim()
+              if selected is "" && label.title is selectedLbl
+                selected = "is-active"
+          else
+            selected = if label.title is selectedLabel then "is-active" else ""
+
+          "<li>
+            <a href='#' class='#{selected}'>
+              #{label.title}
+            </a>
+          </li>"
+        filterable: true
+        search:
+          fields: ['title']
+        selectable: true
+        fieldName: $(dropdown).data('field-name')
+        id: (label) ->
+          label.title
+        clicked: ->
+          if $(dropdown).hasClass "js-filter-submit"
+            $(dropdown).parents('form').submit()
+      )
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
index 98fc8f173401e10487494ad5c025de363baed0dd..2a0b94794450e75a80d454fc4e37e35ab477bd08 100644
--- a/app/assets/javascripts/markdown_preview.js.coffee
+++ b/app/assets/javascripts/markdown_preview.js.coffee
@@ -6,6 +6,7 @@
 class @MarkdownPreview
   # Minimum number of users referenced before triggering a warning
   referenceThreshold: 10
+  ajaxCache: {}
 
   showPreview: (form) ->
     preview = form.find('.js-md-preview')
@@ -24,12 +25,16 @@ class @MarkdownPreview
   renderMarkdown: (text, success) ->
     return unless window.markdown_preview_path
 
+    return success(@ajaxCache.response) if text == @ajaxCache.text
+
     $.ajax
       type: 'POST'
       url: window.markdown_preview_path
       data: { text: text }
       dataType: 'json'
-      success: success
+      success: (response) =>
+        @ajaxCache = text: text, response: response
+        success(response)
 
   hideReferencedUsers: (form) ->
     referencedUsers = form.find('.referenced-users')
@@ -49,6 +54,7 @@ markdownPreview = new MarkdownPreview()
 
 previewButtonSelector = '.js-md-preview-button'
 writeButtonSelector   = '.js-md-write-button'
+lastTextareaPreviewed = null
 
 $.fn.setupMarkdownPreview = ->
   $form = $(this)
@@ -58,10 +64,10 @@ $.fn.setupMarkdownPreview = ->
   form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
   form_textarea.on 'blur',  -> markdownPreview.showPreview($form)
 
-$(document).on 'click', previewButtonSelector, (e) ->
-  e.preventDefault()
+$(document).on 'markdown-preview:show', (e, $form) ->
+  return unless $form
 
-  $form = $(this).closest('form')
+  lastTextareaPreviewed = $form.find('textarea.markdown-area')
 
   # toggle tabs
   $form.find(writeButtonSelector).parent().removeClass('active')
@@ -73,10 +79,10 @@ $(document).on 'click', previewButtonSelector, (e) ->
 
   markdownPreview.showPreview($form)
 
-$(document).on 'click', writeButtonSelector, (e) ->
-  e.preventDefault()
+$(document).on 'markdown-preview:hide', (e, $form) ->
+  return unless $form
 
-  $form = $(this).closest('form')
+  lastTextareaPreviewed = null
 
   # toggle tabs
   $form.find(writeButtonSelector).parent().addClass('active')
@@ -84,4 +90,30 @@ $(document).on 'click', writeButtonSelector, (e) ->
 
   # toggle content
   $form.find('.md-write-holder').show()
+  $form.find('textarea.markdown-area').focus()
   $form.find('.md-preview-holder').hide()
+
+$(document).on 'markdown-preview:toggle', (e, keyboardEvent) ->
+  $target = $(keyboardEvent.target)
+
+  if $target.is('textarea.markdown-area')
+    $(document).triggerHandler('markdown-preview:show', [$target.closest('form')])
+    keyboardEvent.preventDefault()
+  else if lastTextareaPreviewed
+    $target = lastTextareaPreviewed
+    $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')])
+    keyboardEvent.preventDefault()
+
+$(document).on 'click', previewButtonSelector, (e) ->
+  e.preventDefault()
+
+  $form = $(this).closest('form')
+
+  $(document).triggerHandler('markdown-preview:show', [$form])
+
+$(document).on 'click', writeButtonSelector, (e) ->
+  e.preventDefault()
+
+  $form = $(this).closest('form')
+
+  $(document).triggerHandler('markdown-preview:hide', [$form])
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 58373ba87a51bdfd31fdcabd4062bdccf4821800..8322b4c46ad0c90126df97c5fc2f62aac4cf928a 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -189,7 +189,7 @@ class @MergeRequestTabs
     $('.container-fluid').removeClass('container-limited')
 
   shrinkView: ->
-    $gutterIcon = $('.gutter-toggle i')
+    $gutterIcon = $('.js-sidebar-toggle i')
 
     # Wait until listeners are set
     setTimeout( ->
@@ -197,4 +197,3 @@ class @MergeRequestTabs
       if $gutterIcon.is('.fa-angle-double-right')
         $gutterIcon.closest('a').trigger('click',[true])
     , 0)
-
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index e6d8518bec862f6619d8cf43b841d4c6cfb72906..0037a3a21c268fb917600ccf9dd72cc94450162a 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -69,7 +69,7 @@ class @Milestone
 
     @bindIssuesSorting()
     @bindMergeRequestSorting()
-    @bindTabsSwitching
+    @bindTabsSwitching()
 
   bindIssuesSorting: ->
     $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
@@ -104,7 +104,7 @@ class @Milestone
 
     ).disableSelection()
 
-  bindMergeRequestSorting: ->
+  bindTabsSwitching: ->
     $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
       currentTabClass  = $(e.target).data('show')
       previousTabClass =  $(e.relatedTarget).data('show')
@@ -112,7 +112,8 @@ class @Milestone
       $(previousTabClass).hide()
       $(currentTabClass).removeClass('hidden')
       $(currentTabClass).show()
-      
+
+  bindMergeRequestSorting: ->
     $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
       connectWith: ".merge_requests-sortable-list",
       dropOnEmpty: true,
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..5e884454a6585e9963263dcda575f83ef7fccc34
--- /dev/null
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -0,0 +1,60 @@
+class @MilestoneSelect
+  constructor: ->
+    $('.js-milestone-select').each (i, dropdown) ->
+      projectId = $(dropdown).data('project-id')
+      milestonesUrl = $(dropdown).data('milestones')
+      selectedMilestone = $(dropdown).data('selected')
+      showNo = $(dropdown).data('show-no')
+      showAny = $(dropdown).data('show-any')
+      useId = $(dropdown).data('use-id')
+
+      $(dropdown).glDropdown(
+        data: (term, callback) ->
+          $.ajax(
+            url: milestonesUrl
+          ).done (data) ->
+            html = $(data)
+            data = []
+            html.find('.milestone strong a').each ->
+              link = $(@).attr("href").split("/")
+              data.push(
+                id: link[link.length - 1]
+                title: $(@).text().trim()
+              )
+
+            if showNo
+              data.unshift(
+                id: "0"
+                title: 'No Milestone'
+              )
+
+            if showAny
+              data.unshift(
+                title: 'Any Milestone'
+              )
+
+            if data.length > 2
+              data.splice 2, 0, "divider"
+
+            callback(data)
+        filterable: true
+        search:
+          fields: ['title']
+        selectable: true
+        fieldName: $(dropdown).data('field-name')
+        text: (milestone) ->
+          milestone.title
+        id: (milestone) ->
+          if !useId
+            if milestone.title isnt "Any milestone"
+              milestone.title
+            else
+              ""
+          else
+            milestone.id
+        isSelected: (milestone) ->
+          milestone.title is selectedMilestone
+        clicked: ->
+          if $(dropdown).hasClass "js-filter-submit"
+            $(dropdown).parents('form').submit()
+      )
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index c95ead22e6ce7e6a7e6560f8be150527c4409391..75d7f52bbb6e7e7e29ba6129a7ac7238be218ddc 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -30,8 +30,11 @@ class @Notes
     $(document).on "ajax:success", ".js-main-target-form", @addNote
     $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
 
+    # catch note ajax errors
+    $(document).on "ajax:error", ".js-main-target-form", @addNoteError
+
     # change note in UI after update
-    $(document).on "ajax:success", "form.edit_note", @updateNote
+    $(document).on "ajax:success", "form.edit-note", @updateNote
 
     # Edit note link
     $(document).on "click", ".js-note-edit", @showEditForm
@@ -51,6 +54,9 @@ class @Notes
     $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
     $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
 
+    # reset main target form when clicking discard
+    $(document).on "click", ".js-note-discard", @resetMainTargetForm
+
     # update the file name when an attachment is selected
     $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
 
@@ -72,7 +78,7 @@ class @Notes
   cleanBinding: ->
     $(document).off "ajax:success", ".js-main-target-form"
     $(document).off "ajax:success", ".js-discussion-note-form"
-    $(document).off "ajax:success", "form.edit_note"
+    $(document).off "ajax:success", "form.edit-note"
     $(document).off "click", ".js-note-edit"
     $(document).off "click", ".note-edit-cancel"
     $(document).off "click", ".js-note-delete"
@@ -85,6 +91,7 @@ class @Notes
     $(document).off "keyup", ".js-note-text"
     $(document).off "click", ".js-note-target-reopen"
     $(document).off "click", ".js-note-target-close"
+    $(document).off "click", ".js-note-discard"
 
     $('.note .js-task-list-container').taskList('disable')
     $(document).off 'tasklist:changed', '.note .js-task-list-container'
@@ -219,7 +226,7 @@ class @Notes
   Resets text and preview.
   Resets buttons.
   ###
-  resetMainTargetForm: ->
+  resetMainTargetForm: (e) =>
     form = $(".js-main-target-form")
 
     # remove validation errors
@@ -231,6 +238,8 @@ class @Notes
 
     form.find(".js-note-text").data("autosave").reset()
 
+    @updateTargetButtons(e)
+
   reenableTargetFormSubmitButton: ->
     form = $(".js-main-target-form")
 
@@ -274,8 +283,10 @@ class @Notes
     form.removeClass "js-new-note-form"
     form.find('.div-dropzone').remove()
 
+    # hide discard button
+    form.find('.js-note-discard').hide()
+
     # setup preview buttons
-    form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
     previewButton = form.find(".js-md-preview-button")
 
     textarea = form.find(".js-note-text")
@@ -309,6 +320,10 @@ class @Notes
   addNote: (xhr, note, status) =>
     @renderNote(note)
 
+  addNoteError: (xhr, note, status) =>
+    flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert')
+    flash.pinTo('.md-area')
+
   ###
   Called in response to the new note form being submitted
 
@@ -347,22 +362,26 @@ class @Notes
     note = $(this).closest(".note")
     note.find(".note-body > .note-text").hide()
     note.find(".note-header").hide()
-    base_form = note.find(".note-edit-form")
-    form = base_form.clone().insertAfter(base_form)
-    form.addClass('current-note-edit-form gfm-form')
-    form.find('.div-dropzone').remove()
+    form = note.find(".note-edit-form")
+    isNewForm = form.is(':not(.gfm-form)')
+    if isNewForm
+      form.addClass('gfm-form')
+    form.addClass('current-note-edit-form')
+    form.show()
 
     # Show the attachment delete link
     note.find(".js-note-attachment-delete").show()
 
     # Setup markdown form
-    GitLab.GfmAutoComplete.setup()
-    new DropzoneInput(form)
+    if isNewForm
+      GitLab.GfmAutoComplete.setup()
+      new DropzoneInput(form)
 
-    form.show()
     textarea = form.find("textarea")
     textarea.focus()
-    autosize(textarea)
+
+    if isNewForm
+      autosize(textarea)
 
     # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
     # The textarea has the correct value, Chrome just won't show it unless we
@@ -371,7 +390,8 @@ class @Notes
     textarea.val ""
     textarea.val value
 
-    disableButtonIfEmptyField textarea, form.find(".js-comment-button")
+    if isNewForm
+      disableButtonIfEmptyField textarea, form.find(".js-comment-button")
 
   ###
   Called in response to clicking the edit note link
@@ -383,7 +403,9 @@ class @Notes
     note = $(this).closest(".note")
     note.find(".note-body > .note-text").show()
     note.find(".note-header").show()
-    note.find(".current-note-edit-form").remove()
+    note.find(".current-note-edit-form")
+      .removeClass("current-note-edit-form")
+      .hide()
 
   ###
   Called in response to deleting a note of any kind.
@@ -462,6 +484,11 @@ class @Notes
     form.find("#note_line_code").val dataHolder.data("lineCode")
     form.find("#note_noteable_type").val dataHolder.data("noteableType")
     form.find("#note_noteable_id").val dataHolder.data("noteableId")
+    form.find('.js-note-discard')
+        .show()
+        .removeClass('js-note-discard')
+        .addClass('js-close-discussion-note-form')
+        .text(form.find('.js-close-discussion-note-form').data('cancel-text'))
     @setupNoteForm form
     form.find(".js-note-text").focus()
     form.addClass "js-discussion-note-form"
@@ -561,21 +588,52 @@ class @Notes
   updateCloseButton: (e) =>
     textarea = $(e.target)
     form = textarea.parents('form')
-    form.find('.js-note-target-close').text('Close')
+    closebtn = form.find('.js-note-target-close')
+    closebtn.text(closebtn.data('original-text'))
 
   updateTargetButtons: (e) =>
     textarea = $(e.target)
     form = textarea.parents('form')
+    reopenbtn = form.find('.js-note-target-reopen')
+    closebtn = form.find('.js-note-target-close')
+    discardbtn = form.find('.js-note-discard')
+
     if textarea.val().trim().length > 0
-      form.find('.js-note-target-reopen').text('Comment & reopen')
-      form.find('.js-note-target-close').text('Comment & close')
-      form.find('.js-note-target-reopen').addClass('btn-comment-and-reopen')
-      form.find('.js-note-target-close').addClass('btn-comment-and-close')
+      reopentext = reopenbtn.data('alternative-text')
+      closetext = closebtn.data('alternative-text')
+
+      if reopenbtn.text() isnt reopentext
+        reopenbtn.text(reopentext)
+
+      if closebtn.text() isnt closetext
+        closebtn.text(closetext)
+
+      if reopenbtn.is(':not(.btn-comment-and-reopen)')
+        reopenbtn.addClass('btn-comment-and-reopen')
+
+      if closebtn.is(':not(.btn-comment-and-close)')
+        closebtn.addClass('btn-comment-and-close')
+
+      if discardbtn.is(':hidden')
+        discardbtn.show()
     else
-      form.find('.js-note-target-reopen').text('Reopen')
-      form.find('.js-note-target-close').text('Close')
-      form.find('.js-note-target-reopen').removeClass('btn-comment-and-reopen')
-      form.find('.js-note-target-close').removeClass('btn-comment-and-close')
+      reopentext = reopenbtn.data('original-text')
+      closetext = closebtn.data('original-text')
+
+      if reopenbtn.text() isnt reopentext
+        reopenbtn.text(reopentext)
+
+      if closebtn.text() isnt closetext
+        closebtn.text(closetext)
+
+      if reopenbtn.is(':not(.btn-comment-and-reopen)')
+        reopenbtn.removeClass('btn-comment-and-reopen')
+
+      if closebtn.is(':not(.btn-comment-and-close)')
+        closebtn.removeClass('btn-comment-and-close')
+
+      if discardbtn.is(':visible')
+        discardbtn.hide()
 
   initTaskList: ->
     @enableTaskList()
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 9110b732adc825d36a6377571cdf5b23859b74a8..59d44c30bee04297c9736bb89786996364880dc2 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -4,12 +4,13 @@ class @Profile
     $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
       $(this).parents('form').submit()
 
-    $('.update-username form').on 'ajax:before', ->
-      $('.loading-gif').show()
+    $('.update-username').on 'ajax:before', ->
+      $('.loading-username').show()
       $(this).find('.update-success').hide()
       $(this).find('.update-failed').hide()
 
-    $('.update-username form').on 'ajax:complete', ->
+    $('.update-username').on 'ajax:complete', ->
+      $('.loading-username').hide()
       $(this).find('.btn-save').enable()
       $(this).find('.loading-gif').hide()
 
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index ed5206368ce7a1c85057384f7f5ad9d9f397337c..e4c4bf3b273043b2492b317c856c8c1e9c219c07 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -2,6 +2,7 @@
   init: ->
     $(".projects-list-filter").off('keyup')
     this.initSearch()
+    this.initPagination()
 
   initSearch: ->
     @timer = null
@@ -29,3 +30,8 @@
         # Change url so if user reload a page - search results are saved
         history.replaceState {page: project_filter_url}, document.title, project_filter_url
       dataType: "json"
+
+  initPagination: ->
+    $('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
+      $('.projects-list-holder').replaceWith(data.html)
+    )
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 9c7c2474aa4730f8c57058ed8b2736133b790f53..100e3aac5352a2b8b7c6d9da32ec67ffe382937c 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -4,11 +4,15 @@ class @Shortcuts
     Mousetrap.reset()
     Mousetrap.bind('?', @selectiveHelp)
     Mousetrap.bind('s', Shortcuts.focusSearch)
+    Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
     Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
 
   selectiveHelp: (e) =>
     Shortcuts.showHelp(e, @enabledHelp)
 
+  toggleMarkdownPreview: (e) =>
+    $(document).triggerHandler('markdown-preview:toggle', [e])
+
   @showHelp: (e, location) ->
     if $('#modal-shortcuts').length > 0
       $('#modal-shortcuts').modal('show')
@@ -35,3 +39,14 @@ $(document).on 'click.more_help', '.js-more-help-button', (e) ->
   $(@).remove()
   $('.hidden-shortcut').show()
   e.preventDefault()
+
+Mousetrap.stopCallback = (->
+  defaultStopCallback = Mousetrap.stopCallback
+
+  return (e, element, combo) ->
+    # allowed shortcuts if textarea, input, contenteditable are focused
+    if ['ctrl+shift+p', 'command+shift+p'].indexOf(combo) != -1
+      return false
+    else
+      return defaultStopCallback.apply(@, arguments)
+)()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 9467011799fd33f1342095fcad80ffdde73cf0fe..987c6f4b8d2123bafb1d156a11350b5632648d4d 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -3,6 +3,81 @@ class @UsersSelect
     @usersPath = "/autocomplete/users.json"
     @userPath = "/autocomplete/users/:id.json"
 
+    $('.js-user-search').each (i, dropdown) =>
+      @projectId = $(dropdown).data('project-id')
+      @showCurrentUser = $(dropdown).data('current-user')
+      showNullUser = $(dropdown).data('null-user')
+      showAnyUser = $(dropdown).data('any-user')
+      firstUser = $(dropdown).data('first-user')
+      selectedId = $(dropdown).data('selected')
+
+      $(dropdown).glDropdown(
+        data: (term, callback) =>
+          @users term, (users) =>
+            if term.length is 0
+              showDivider = 0
+
+              if firstUser
+                # Move current user to the front of the list
+                for obj, index in users
+                  if obj.username == firstUser
+                    users.splice(index, 1)
+                    users.unshift(obj)
+                    break
+
+              if showNullUser
+                showDivider += 1
+                users.unshift(
+                  name: 'Unassigned',
+                  id: 0
+                )
+
+              if showAnyUser
+                showDivider += 1
+                name = showAnyUser
+                name = 'Any User' if name == true
+                anyUser = {
+                  name: name,
+                  id: null
+                }
+                users.unshift(anyUser)
+
+            if showDivider
+              users.splice(showDivider, 0, "divider")
+
+            # Send the data back
+            callback users
+        filterable: true
+        filterRemote: true
+        search:
+          fields: ['name', 'username']
+        selectable: true
+        fieldName: $(dropdown).data('field-name')
+        clicked: ->
+          if $(dropdown).hasClass "js-filter-submit"
+            $(dropdown).parents('form').submit()
+        renderRow: (user) ->
+          username = if user.username then "@#{user.username}" else ""
+          avatar = if user.avatar_url then user.avatar_url else false
+          selected = if user.id is selectedId then "is-active" else ""
+          img = ""
+
+          if avatar
+            img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
+
+          "<li>
+            <a href='#' class='dropdown-menu-user-link #{selected}'>
+              #{img}
+              <strong class='dropdown-menu-user-full-name'>
+                #{user.name}
+              </strong>
+              <span class='dropdown-menu-user-username'>
+                #{username}
+              </span>
+            </a>
+          </li>"
+      )
+
     $('.ajax-users-select').each (i, select) =>
       @projectId = $(select).data('project-id')
       @groupId = $(select).data('group-id')
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d7e4153ddc0d511ea88f7748f2b8345567791523..6edabe20136839ab5250ca75c51b940b8959a64d 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -28,6 +28,10 @@
   border-bottom: 1px solid $border-color;
   color: $gl-gray;
 
+  a {
+    color: $md-link-color;
+  }
+
   &.oneline-block {
     line-height: 42px;
   }
@@ -153,3 +157,7 @@
     float: right;
   }
 }
+
+.content-block-small {
+  padding: 10px 0;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c98e43ad09f586ffa00355b420e829eb7346dd6e..ff551f151f1eb7eb1c3e507ef35da3afe3d921a3 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -12,11 +12,13 @@
 .prepend-top-default { margin-top: $gl-padding !important; }
 .prepend-top-20 { margin-top:20px }
 .prepend-left-10 { margin-left:10px }
-.prepend-left-default { margin-left:$gl-padding }
+.prepend-left-default { margin-left: $gl-padding; }
 .prepend-left-20 { margin-left:20px }
 .append-right-5 { margin-right: 5px }
 .append-right-10 { margin-right:10px }
+.append-right-default { margin-right: $gl-padding; }
 .append-right-20 { margin-right:20px }
+.append-bottom-0 { margin-bottom:0 }
 .append-bottom-10 { margin-bottom:10px }
 .append-bottom-15 { margin-bottom:15px }
 .append-bottom-20 { margin-bottom:20px }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4878b333f94b10cfdfb1536dbb6f37cde449d64..5b647fc6176b5e179f4a8d8d72c767350a745367 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -17,6 +17,47 @@
   .dropdown-menu {
     display: block;
   }
+
+  .dropdown-menu-toggle {
+    border-color: $dropdown-toggle-hover-border-color;
+
+    .fa {
+      color: $dropdown-toggle-hover-icon-color;
+    }
+  }
+}
+
+.dropdown-menu-toggle {
+  position: relative;
+  width: 160px;
+  padding: 6px 20px 6px 10px;
+  background-color: $dropdown-toggle-bg;
+  color: $dropdown-toggle-color;
+  font-size: 15px;
+  text-align: left;
+  border: 1px solid $dropdown-toggle-border-color;
+  border-radius: 2px;
+  outline: 0;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+
+  .fa {
+    position: absolute;
+    top: 50%;
+    right: 6px;
+    margin-top: -4px;
+    color: $dropdown-toggle-icon-color;
+    font-size: 10px;
+  }
+
+  &:hover, {
+    border-color: $dropdown-toggle-hover-border-color;
+
+    .fa {
+      color: $dropdown-toggle-hover-icon-color;
+    }
+  }
 }
 
 .dropdown-menu {
@@ -24,7 +65,7 @@
   position: absolute;
   top: 100%;
   left: 0;
-  z-index: 9999;
+  z-index: 9;
   width: 240px;
   margin-top: 2px;
   margin-bottom: 0;
@@ -36,6 +77,21 @@
   border-radius: $border-radius-base;
   box-shadow: 0 2px 4px $dropdown-shadow-color;
 
+  &.is-loading {
+    .dropdown-content {
+      display: none;
+    }
+
+    .dropdown-loading {
+      display: block;
+    }
+  }
+
+  ul {
+    margin: 0;
+    padding: 0;
+  }
+
   li {
     text-align: left;
     list-style: none;
@@ -61,13 +117,70 @@
     white-space: nowrap;
     overflow: hidden;
 
-    &:hover {
+    &:hover,
+    &:focus,
+    &.is-focused {
       background-color: $dropdown-link-hover-bg;
       text-decoration: none;
+      outline: 0;
     }
   }
 }
 
+.dropdown-menu-paging {
+  .dropdown-page-two,
+  .dropdown-menu-back {
+    display: none;
+  }
+
+  &.is-page-two {
+    .dropdown-page-one {
+      display: none;
+    }
+
+    .dropdown-page-two,
+    .dropdown-menu-back {
+      display: block;
+    }
+  }
+}
+
+.dropdown-menu-user {
+  .avatar {
+    float: left;
+    width: 30px;
+    height: 30px;
+    margin: 0 10px 0 0;
+  }
+}
+
+.dropdown-menu-user-link {
+  padding-top: 7px;
+  padding-bottom: 7px;
+}
+
+.dropdown-menu-user-full-name {
+  display: block;
+  margin-bottom: 2px;
+  font-weight: 600;
+  line-height: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.dropdown-menu-user-username {
+  display: block;
+  line-height: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.dropdown-select {
+  width: 280px;
+}
+
 .dropdown-menu-align-right {
   left: auto;
   right: 0;
@@ -81,8 +194,9 @@
       &::before {
         content: "\f00c";
         position: absolute;
-        left: 4px;
-        top: 8px;
+        left: 5px;
+        top: 50%;
+        margin-top: -7px;
         font: normal normal normal 14px/1 FontAwesome;
         font-size: inherit;
         text-rendering: auto;
@@ -94,9 +208,136 @@
 }
 
 .dropdown-header {
-  padding-left: 10px;
-  padding-right: 10px;
+  padding-left: 5px;
+  padding-right: 5px;
   color: $dropdown-header-color;
   font-size: 13px;
   line-height: 22px;
 }
+
+.dropdown-title {
+  position: relative;
+  margin-bottom: 10px;
+  padding-left: 30px;
+  padding-right: 30px;
+  padding-bottom: 10px;
+  font-weight: 600;
+  line-height: 1;
+  text-align: center;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  border-bottom: 1px solid $dropdown-divider-color;
+  overflow: hidden;
+}
+
+.dropdown-title-button {
+  position: absolute;
+  top: -1px;
+  padding: 0;
+  color: $dropdown-title-btn-color;
+  font-size: 14px;
+  border: 0;
+  background: none;
+  outline: 0;
+
+  &:hover {
+    color: darken($dropdown-title-btn-color, 15%);
+  }
+}
+
+.dropdown-menu-close {
+  right: 0;
+}
+
+.dropdown-menu-back {
+  left: 0;
+}
+
+.dropdown-input {
+  position: relative;
+  margin-bottom: 10px;
+
+  .fa {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    color: #C7C7C7;
+    font-size: 12px;
+    pointer-events: none;
+  }
+}
+
+.dropdown-input-field {
+  width: 100%;
+  padding: 0 7px;
+  color: $dropdown-input-color;
+  line-height: 30px;
+  border: 1px solid $dropdown-divider-color;
+  border-radius: 2px;
+  outline: 0;
+
+  &:focus {
+    color: $dropdown-link-color;
+    border-color: $dropdown-input-focus-border;
+    box-shadow: 0 0 4px $dropdown-input-focus-shadow;
+
+    + .fa {
+      color: $dropdown-link-color;
+    }
+  }
+
+  &:hover {
+    + .fa {
+      color: $dropdown-link-color;
+    }
+  }
+}
+
+.dropdown-content {
+  max-height: 215px;
+  overflow-y: scroll;
+}
+
+.dropdown-footer {
+  padding-top: 10px;
+  margin-top: 10px;
+  font-size: 13px;
+  border-top: 1px solid $dropdown-divider-color;
+}
+
+.dropdown-footer-list {
+  font-size: 14px;
+
+  a {
+    padding-left: 10px;
+  }
+}
+
+.dropdown-loading {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  display: none;
+  z-index: 9;
+  background-color: $dropdown-loading-bg;
+  font-size: 28px;
+
+  .fa {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    margin-top: -14px;
+    margin-left: -14px;
+  }
+}
+
+.dropdown-menu-labels {
+  .label {
+    position: relative;
+    width: 30px;
+    margin-right: 5px;
+    text-indent: -99999px;
+  }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 07907e6e5a6376dc4be42f0c6ecb29c5a2551526..b034a4882c1a819b1c9a22028929de8324e38cc6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -169,6 +169,7 @@
      */
     &.code {
       padding: 0;
+      -webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
     }
   }
 }
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index eab4162867793d0ae394febb0e56b3d28ad2cf96..c431e2b0df3de88990a14bbef455ab6972b8a3e1 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -1,5 +1,6 @@
 .filter-item {
   margin-right: 6px;
+  vertical-align: top;
 }
 
 @media (min-width: 800px)  {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 7de874c8bcd85796ae81909700336bf3f1eb4fe0..b2fbc95e04364662ba462b568d5bd8a9d8f027ef 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -63,7 +63,7 @@
     border-bottom: none;
 
     /* Small devices (phones, tablets, 768px and lower) */
-    @media (max-width: $screen-sm-min) {
+    @media (max-width: $screen-sm-max) {
       width: 100%;
     }
   }
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 48570abff490a245da4d3ac783b8fecab5d9843c..9381cb3281cae9e7da4c643a4c19d7b021410478 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -149,13 +149,13 @@
     }
 
     &:hover > a.anchor {
-      $size: 16px;
+      $size: 14px;
       position: absolute;
       right: 100%;
       top: 50%;
-      margin-top: -$size/2;
-      margin-right: 0px;
-      padding-right: 20px;
+      margin-top: -11px;
+      margin-right: 0;
+      padding-right: 15px;
       display: inline-block;
       width: $size;
       height: $size;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index cc84a5ff932cda0e95a239630b9783273f74f54c..0261c384a587cd7d62ffea91911914d48ff8066d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -34,13 +34,15 @@ $error-exclamation-point: #E62958;
 $border-radius-default: 3px;
 $list-title-color: #333333;
 $list-text-color: #555555;
-$profile-settings-link-color: $md-link-color;
 
 $btn-transparent-color: #8F8F8F;
 
 $ssh-key-icon-color: #8F8F8F;
 $ssh-key-icon-size: 18px;
 
+$provider-btn-group-border: #E5E5E5;
+$provider-btn-not-active-color: #4688F1;
+
 /*
  * Color schema
  */
@@ -70,7 +72,7 @@ $orange-light: rgba(252, 109, 38, 0.80);
 $orange-normal: #E75E40;
 $orange-dark: #CE5237;
 
-$red-light: #F43263;
+$red-light: #F06559;
 $red-normal: #E52C5A;
 $red-dark: #D22852;
 
@@ -94,7 +96,7 @@ $border-orange-light: #fc6d26;
 $border-orange-normal: #CE5237;
 $border-orange-dark: #C14E35;
 
-$border-red-light: #E52C5A;
+$border-red-light: #F24F41;
 $border-red-normal: #D22852;
 $border-red-dark: #CA264F;
 
@@ -138,3 +140,22 @@ $dropdown-shadow-color: rgba(#000, .1);
 $dropdown-divider-color: rgba(#000, .1);
 $dropdown-header-color: #959494;
 $dropdown-caret-color: #54565B;
+$dropdown-title-btn-color: #BFBFBF;
+$dropdown-input-color: #C7C7C7;
+$dropdown-input-focus-border: rgb(58, 171, 240);
+$dropdown-input-focus-shadow: rgba(#000, .2);
+$dropdown-loading-bg: rgba(#fff, .6);
+
+$dropdown-toggle-bg: #fff;
+$dropdown-toggle-color: #626262;
+$dropdown-toggle-border-color: #EAEAEA;
+$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%);
+$dropdown-toggle-icon-color: #C4C4C4;
+$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
+
+/*
+ *  Award emoji
+ */
+$award-emoji-menu-bg: #FFF;
+$award-emoji-menu-border: #F1F2F4;
+$award-emoji-new-btn-icon-color: #DCDCDC;
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 87dd30f41114daf3ab6d6bad96e781b38aadcaab..28994e60baa5094a5d2379666b4851c83db8f5e4 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -1,125 +1,133 @@
 .awards {
-  @include clearfix;
   line-height: 34px;
 
   .emoji-icon {
     width: 20px;
     height: 20px;
-    margin: 7px 0 0 5px;
   }
+}
 
-  .award {
-    @include border-radius(5px);
-
-    border: 1px solid;
-    padding: 0px 10px;
-    float: left;
-    margin-right: 5px;
-    border-color: $border-color;
-    cursor: pointer;
+.emoji-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  margin-top: 3px;
+  z-index: 1000;
+  min-width: 160px;
+  font-size: 14px;
+  background-color: $award-emoji-menu-bg;
+  border: 1px solid $award-emoji-menu-border;
+  border-radius: $border-radius-base;
+  box-shadow: 0 6px 12px rgba(0,0,0,.175);
+  pointer-events: none;
+  opacity: 0;
+  transform: scale(.2);
+  transform-origin: 0 -45px;
+  transition: all .3s cubic-bezier(.87,-.41,.19,1.44);
+
+  &.is-visible {
+    pointer-events: all;
+    opacity: 1;
+    transform: scale(1);
+  }
 
-    &:hover {
-      background-color: #dce0e5;
+  .emoji-menu-content {
+    padding: $gl-padding;
+    width: 300px;
+    height: 300px;
+    overflow-y: scroll;
+
+    input.emoji-search{
+      background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
+      background-repeat: no-repeat;
+      background-position: right 5px center;
+      background-size: 16px;
     }
+  }
+}
 
-    &.active {
-      border-color: $border-gray-light;
-      background-color: $gray-light;
-
-      &:hover {
-        background-color: #dce0e5;
-      }
+.emoji-menu-list {
+  list-style: none;
+  padding-left: 0;
+  margin-bottom: 0;
+}
 
-      .counter {
-        font-weight: bold;
-      }
-    }
+.emoji-menu-list-item {
+  padding: 3px;
+  margin-left: 1px;
+  margin-right: 1px;
+}
 
-    .icon {
-      float: left;
-      margin-right: 10px;
-    }
+.emoji-menu-btn {
+  display: block;
+  cursor: pointer;
+  width: 30px;
+  height: 30px;
+  padding: 0;
+  background: none;
+  border: 0;
+  border-radius: $border-radius-base;
+  transition: transform .15s cubic-bezier(.3, 0, .2, 2);
+
+  &:hover {
+    background-color: transparent;
+    outline: 0;
+    transform: scale(1.3);
+  }
 
-    .counter {
-      float: left;
-    }
+  &:focus,
+  &:active {
+    outline: 0;
   }
 
-  .awards-controls {
+  .emoji-icon {
+    display: inline-block;
     position: relative;
-    margin-left: 10px;
-    float: left;
+    top: 3px;
+  }
+}
 
-    .add-award {
-      font-size: 24px;
-      color: $gl-gray;
-      position: relative;
-      top: 2px;
+.award-menu-holder {
+  display: inline-block;
+  position: relative;
+}
 
-      &:hover,
-      &:link {
-        text-decoration: none;
-      }
-    }
+.award-control {
+  margin-right: 5px;
+  padding-left: 5px;
+  padding-right: 5px;
+  line-height: 20px;
+  outline: 0;
+
+  &.active,
+  &:active {
+    background-color: $white-dark;
+    box-shadow: none;
+    outline: 0;
+  }
 
-    .emoji-menu{
-      position: absolute;
-      top: 100%;
-      left: 0;
-      z-index: 1000;
+  &.is-loading {
+    .award-control-icon {
       display: none;
-      float: left;
-      min-width: 160px;
-      padding: 5px 0;
-      margin: 2px 0 0;
-      font-size: 14px;
-      text-align: left;
-      list-style: none;
-      background-color: #fff;
-      -webkit-background-clip: padding-box;
-      background-clip: padding-box;
-      border: 1px solid #ccc;
-      border: 1px solid rgba(0,0,0,.15);
-      border-radius: 4px;
-      -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
-      box-shadow: 0 6px 12px rgba(0,0,0,.175);
-
-      .emoji-menu-content {
-        padding: $gl-padding;
-        width: 300px;
-        height: 300px;
-        overflow-y: scroll;
-
-        h5 {
-          clear: left;
-        }
-
-        ul {
-          list-style-type: none;
-          margin-left: -20px;
-          margin-bottom: 20px;
-          overflow: auto;
-        }
-
-        input.emoji-search{
-          background: image-url("icon-search.png") 240px no-repeat;
-        }
-
-        li {
-          cursor: pointer;
-          width: 30px;
-          height: 30px;
-          text-align: center;
-          float: left;
-          margin: 3px;
-          list-decorate: none;
-          @include border-radius(5px);
-
-          &:hover {
-            background-color: #ccc;
-          }
-        }
-      }
     }
+
+    .award-control-icon-loading {
+      display: block;
+    }
+  }
+
+  .icon,
+  .award-control-icon {
+    float: left;
+    margin-right: 5px;
+    font-size: 20px;
+  }
+
+  .award-control-icon-loading {
+    display: none;
+  }
+
+  .award-control-icon {
+    color: $award-emoji-new-btn-icon-color;
   }
 }
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 3c2997c1d5adc11330a4db8794abbdea1736e962..75f298019e32af333ebfd5525044093a1a05b28b 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -27,10 +27,25 @@
   }
 
   .scroll-controls {
-    position: fixed;
-    bottom: 10px;
-    left: 250px;
-    z-index: 100;
+    &.affix-top {
+      position: absolute;
+      top: 10px;
+      right: 25px;
+    }
+
+    &.affix-bottom {
+      position: absolute;
+      right: 25px;
+    }
+
+    &.affix {
+      right: 30px;
+      bottom: 15px;
+
+      @media (min-width: $screen-md-min) {
+        right: 26%;
+      }
+    }
 
     a {
       display: block;
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index e53d6fc6bdc185996d113afe500466c2e7a87332..c0cc30d33a64a44a63070b25e03c7dbb44114cbe 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -90,6 +90,7 @@
   position: relative;
   font-family: $monospace_font;
   $left: 12px;
+  overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
   .max-width-marker {
     width: 72ch;
     color: rgba(0, 0, 0, 0.0);
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 1c78aafdb873c9b9d7c3d3cd534faf74774333e1..5ec0966194cfdb40f05a3565954b6f608bc4860e 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -7,6 +7,28 @@
     display: inline-block;
     margin-right: 10px;
   }
+
+  &.suggest-colors-dropdown {
+    margin-bottom: 5px;
+
+    a {
+      @include border-radius(0);
+      width: 36.7px;
+      margin-right: 0;
+      margin-bottom: -5px;
+    }
+  }
+}
+
+.dropdown-label-color-preview {
+  display: none;
+  margin-top: 5px;
+  width: 100%;
+  height: 25px;
+
+  &.is-active {
+    display: block;
+  }
 }
 
 .label-row {
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index d24adbf67e653db0025a26682b003e7d8f1466db..d0e72a4422c321f5edb32c9084ebac957b62dbe0 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -19,10 +19,11 @@ li.milestone {
     width: 105px;
   }
 
-  .issue-row {
+  .issuable-row {
     .color-label {
       border-radius: 2px;
       padding: 3px !important;
+      margin-right: 7px;
     }
 
     // Issue title
@@ -44,20 +45,15 @@ li.milestone {
   }
 }
 
-.issues-sortable-list {
-  .issue-detail {
+.issues-sortable-list, .merge_requests-sortable-list {
+  .issuable-detail {
     display: block;
+    margin-top: 7px;
 
-    .issue-number{
+    .issuable-number {
       color: rgba(0,0,0,0.44);
       margin-right: 5px;
     }
-    .color-label {
-      padding: 6px 10px;
-      margin-right: 7px;
-      margin-top: 10px;
-    }
-
     .avatar {
       float: none;
     }
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 4826b994e372243a68da6ada51f88adad24f2ce9..248c56e459d7041a37648a51da6c78c6717cfef2 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -1,7 +1,6 @@
-.account-page {
-  fieldset {
-    margin-bottom: 15px;
-    padding-bottom: 15px;
+.profile-avatar-form-option {
+  hr {
+    margin: 10px 0;
   }
 }
 
@@ -20,7 +19,7 @@
 
 .account-btn-link,
 .profile-settings-sidebar a {
-  color: $profile-settings-link-color;
+  color: $md-link-color;
 }
 
 .oauth-buttons {
@@ -172,6 +171,47 @@
 
 .profile-settings-content {
   a {
-    color: $profile-settings-link-color;
+    color: $md-link-color;
+  }
+}
+
+.change-username-title {
+  color: $gl-warning;
+}
+
+.remove-account-title {
+  color: $gl-danger;
+}
+
+.provider-btn-group {
+  display: inline-block;
+  margin-right: 10px;
+  border: 1px solid $provider-btn-group-border;
+  border-radius: 3px;
+
+  &:last-child {
+    margin-right: 0;
+  }
+}
+
+.provider-btn-image {
+  display: inline-block;
+  padding: 5px 10px;
+  border-right: 1px solid $provider-btn-group-border;
+
+  > img {
+    width: 20px;
+  }
+}
+
+.provider-btn {
+  display: inline-block;
+  padding: 5px 10px;
+  margin-left: -3px;
+  line-height: 22px;
+  background-color: $gray-light;
+
+  &.not-active {
+    color: $provider-btn-not-active-color;
   }
 }
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 0161642d87136a977ee1298daed388b4463f4d79..639d639d5b0796aed4dc875879d5c30c4007f420 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -26,5 +26,13 @@
   margin-right: 10px;
   font-size: $gl-font-size;
   border: 1px solid;
-  line-height: 40px;
+  line-height: 32px;
+}
+
+.markdown-snippet-copy {
+  position: fixed;
+  top: -10px;
+  left: -10px;
+  max-height: 0;
+  max-width: 0;
 }
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 4d3e48f7f817a16fd22e577af09d827e68a034e8..668396a0f20d2404674cbd93a2c939152bd00b7b 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -55,7 +55,7 @@ def destroy
   private
 
   def group
-    @group = Group.find_by(path: params[:id])
+    @group ||= Group.find_by(path: params[:id])
   end
 
   def group_params
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index fb74919ea23bd0e59802cb58b76f4a2cdb745f77..1f55b18e0b160b8c6c11f5eaa2202f9b03d20d79 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -246,6 +246,8 @@ def check_2fa_requirement
 
   def ldap_security_check
     if current_user && current_user.requires_ldap_check?
+      return unless current_user.try_obtain_ldap_lease
+
       unless Gitlab::LDAP::Access.allowed?(current_user)
         sign_out current_user
         flash[:alert] = "Access denied for your LDAP account."
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f63b703d101250db6096e2964c44bfb606c59ead
--- /dev/null
+++ b/app/controllers/concerns/filter_projects.rb
@@ -0,0 +1,15 @@
+# == FilterProjects
+#
+# Controller concern to handle projects filtering
+# * by name
+# * by archived state
+#
+module FilterProjects
+  extend ActiveSupport::Concern
+
+  def filter_projects(projects)
+    projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
+    projects = projects.non_archived if params[:archived].blank?
+    projects
+  end
+end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index dc880b634e53330cfe33c25a2f10f99e4ccb76ea..0e8b63872ca1cc9bc8919802bb320ab676da40ab 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,18 +1,15 @@
 class Dashboard::ProjectsController < Dashboard::ApplicationController
+  include FilterProjects
+
   before_action :event_filter
 
   def index
-    @projects = current_user.authorized_projects.sorted_by_activity.non_archived
-    @projects = @projects.sort(@sort = params[:sort])
+    @projects = current_user.authorized_projects.sorted_by_activity
+    @projects = filter_projects(@projects)
     @projects = @projects.includes(:namespace)
+    @projects = @projects.sort(@sort = params[:sort])
+    @projects = @projects.page(params[:page]).per(PER_PAGE)
 
-    terms = params[:filter_projects]
-
-    if terms.present?
-      @projects = @projects.search(terms)
-    end
-
-    @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
     @last_push = current_user.recent_push
 
     respond_to do |format|
@@ -32,16 +29,11 @@ def index
 
   def starred
     @projects = current_user.starred_projects.sorted_by_activity
+    @projects = filter_projects(@projects)
     @projects = @projects.includes(:namespace, :forked_from_project, :tags)
     @projects = @projects.sort(@sort = params[:sort])
+    @projects = @projects.page(params[:page]).per(PER_PAGE)
 
-    terms = params[:filter_projects]
-
-    if terms.present?
-      @projects = @projects.search(terms)
-    end
-
-    @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
     @last_push = current_user.recent_push
     @groups = []
 
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index a384f3004db6ca9c24c6d1e8283b817edb80e563..8271ca87436a9891c56e9c6b42f4358a60b45280 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,14 +1,14 @@
 class Explore::ProjectsController < Explore::ApplicationController
+  include FilterProjects
+
   def index
     @projects = ProjectsFinder.new.execute(current_user)
     @tags = @projects.tags_on(:tags)
     @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
     @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
-    @projects = @projects.non_archived
-    @projects = @projects.search(params[:search]) if params[:search].present?
-    @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+    @projects = filter_projects(@projects)
     @projects = @projects.sort(@sort = params[:sort])
-    @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
+    @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
 
     respond_to do |format|
       format.html
@@ -22,9 +22,8 @@ def index
 
   def trending
     @projects = TrendingProjectsFinder.new.execute(current_user)
-    @projects = @projects.non_archived
-    @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
-    @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
+    @projects = filter_projects(@projects)
+    @projects = @projects.page(params[:page]).per(PER_PAGE)
 
     respond_to do |format|
       format.html
@@ -38,9 +37,9 @@ def trending
 
   def starred
     @projects = ProjectsFinder.new.execute(current_user)
-    @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+    @projects = filter_projects(@projects)
     @projects = @projects.reorder('star_count DESC')
-    @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
+    @projects = @projects.page(params[:page]).per(PER_PAGE)
 
     respond_to do |format|
       format.html
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index ca5ce1e204650848cc33d8869ec34921c9a05f85..360930f95a80e5a3f6468895641567d3afb76bff 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,4 +1,5 @@
 class GroupsController < Groups::ApplicationController
+  include FilterProjects
   include IssuesAction
   include MergeRequestsAction
 
@@ -14,7 +15,7 @@ class GroupsController < Groups::ApplicationController
 
   # Load group projects
   before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
-  before_action :event_filter, only: [:show, :events]
+  before_action :event_filter, only: [:activity]
 
   layout :determine_layout
 
@@ -41,7 +42,8 @@ def create
   def show
     @last_push = current_user.recent_push if current_user
     @projects = @projects.includes(:namespace)
-    @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+    @projects = filter_projects(@projects)
+    @projects = @projects.sort(@sort = params[:sort])
     @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
 
     respond_to do |format|
@@ -60,8 +62,10 @@ def show
     end
   end
 
-  def events
+  def activity
     respond_to do |format|
+      format.html
+
       format.json do
         load_events
         pager_json("events/_events", @events.count)
@@ -98,7 +102,7 @@ def group
   end
 
   def load_projects
-    @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
+    @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
   end
 
   # Dont allow unauthorized access to group
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index b64dbbd89ce9eb0d15f80716961c6d3b4b53e7d6..a6bebc46b061225c6799958d2679f155e9c04f12 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -7,6 +7,9 @@ def show
     @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
     if @blob
       headers['X-Content-Type-Options'] = 'nosniff'
+
+      return if cached_blob?
+
       headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
       headers['Content-Disposition'] = 'inline'
       headers['Content-Type'] = safe_content_type(@blob)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 21f30f278c839ce6faedd9d75cbab76c0f0d8c2f..da46731d945eacc310ebe013498c4ff23dd13be2 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -32,10 +32,6 @@ def edit
   end
 
   def show
-    @issues = @milestone.issues
-    @users = @milestone.participants.uniq
-    @merge_requests = @milestone.merge_requests
-    @labels = @milestone.labels
   end
 
   def create
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index d9723acb1d96ee8982577bed7ac498533d625119..10de0e60530673a166278196a66813fb1acc5a34 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -13,6 +13,8 @@ def show
     if @blob
       headers['X-Content-Type-Options'] = 'nosniff'
 
+      return if cached_blob?
+
       if @blob.lfs_pointer?
         send_lfs_object
       else
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index aea08ecce3e377dcfc54b18b2e263d226cc7ec8e..c70add86a20f926a524c5aee2484b451c2044bdb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,7 +1,6 @@
 class ProjectsController < ApplicationController
   include ExtractsPath
 
-  prepend_before_action :render_go_import, only: [:show]
   skip_before_action :authenticate_user!, only: [:show, :activity]
   before_action :project, except: [:new, :create]
   before_action :repository, except: [:new, :create]
@@ -242,16 +241,6 @@ def autocomplete_emojis
     end
   end
 
-  def render_go_import
-    return unless params["go-get"] == "1"
-
-    @namespace = params[:namespace_id]
-    @id = params[:project_id] || params[:id]
-    @id = @id.gsub(/\.git\Z/, "")
-
-    render "go_import", layout: false
-  end
-
   def repo_exists?
     project.repository_exists? && !project.empty_repo?
   end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f7240edd61890c3b73350a4de38c748acea43208..19e8c7a92be03a127619c98949cfc21283631201 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -244,10 +244,17 @@ def by_author(items)
     items
   end
 
+  def filter_by_upcoming_milestone?
+    params[:milestone_title] == '#upcoming'
+  end
+
   def by_milestone(items)
     if milestones?
       if filter_by_no_milestone?
         items = items.where(milestone_id: [-1, nil])
+      elsif filter_by_upcoming_milestone?
+        upcoming = Milestone.where(project_id: projects).upcoming
+        items = items.joins(:milestone).where(milestones: { title: upcoming.title })
       else
         items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
 
@@ -263,11 +270,9 @@ def by_milestone(items)
   def by_label(items)
     if labels?
       if filter_by_no_label?
-        items = items.
-          joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
-          where(label_links: { id: nil })
+        items = items.without_label
       else
-        items = items.joins(:labels).where(labels: { title: label_names })
+        items = items.with_label(label_names)
 
         if projects
           items = items.where(labels: { project_id: projects })
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 3b4e0362e04baaf6203dc497173086297396b3b6..0e5a8f5ee0fe12efd09996da2b59923c2e7de1e4 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -52,7 +52,10 @@ def group_projects(current_user, group)
 
   def all_projects(current_user)
     if current_user
-      [current_user.authorized_projects, public_and_internal_projects]
+      [
+        current_user.authorized_projects,
+        public_and_internal_projects
+      ]
     else
       [Project.public_only]
     end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 07b5759443b2cf8346cc34f3858b3196bfa87d8b..a41172816b817515d01bc7a3a98af0068ce2c8ba 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -4,7 +4,7 @@ def execute(current_user, params = {})
 
     case filter
     when :all then
-      snippets(current_user).fresh.non_expired
+      snippets(current_user).fresh
     when :by_user then
       by_user(current_user, params[:user], params[:scope])
     when :by_project
@@ -27,7 +27,7 @@ def snippets(current_user)
   end
 
   def by_user(current_user, user, scope)
-    snippets = user.snippets.fresh.non_expired
+    snippets = user.snippets.fresh
 
     return snippets.are_public unless current_user
 
@@ -48,7 +48,7 @@ def by_user(current_user, user, scope)
   end
 
   def by_project(current_user, project)
-    snippets = project.snippets.fresh.non_expired
+    snippets = project.snippets.fresh
 
     if current_user
       if project.team.member?(current_user.id)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f0aa2b57121763d0a12b67d5eb1e3e779c8fc51c..d1b1c61b7106ee271a78a39753fd732b036baf7b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -72,7 +72,7 @@ def avatar_icon(user_or_email = nil, size = nil, scale = 2)
     if user_or_email.is_a?(User)
       user = user_or_email
     else
-      user = User.find_by(email: user_or_email.downcase)
+      user = User.find_by_any_email(user_or_email.try(:downcase))
     end
 
     if user
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7f63a2e2cb4f7938910c54efe5d89c113fec3af9..0f77b3b299a11dd66dcd2e185a1bf792204fe721 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -152,4 +152,25 @@ def safe_content_type(blob)
       'application/octet-stream'
     end
   end
+
+  def cached_blob?
+    stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
+
+    # Because we are opionated we set the cache headers ourselves.
+    response.cache_control[:public] = @project.public?
+
+    if @ref && @commit && @ref == @commit.id
+      # This is a link to a commit by its commit SHA. That means that the blob
+      # is immutable. The only reason to invalidate the cache is if the commit
+      # was deleted or if the user lost access to the repository.
+      response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
+    else
+      # A branch or tag points at this blob. That means that the expected blob
+      # value may change over time.
+      response.cache_control[:max_age] = Blob::CACHE_TIME
+    end
+
+    response.etag = @blob.id
+    !stale
+  end
 end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index d8bee21c82e95753236f6b6701ed0249411f7060..f20779f2fbb41e539048947ae3a2e3d297d6b4bd 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -42,12 +42,12 @@ def ci_icon_for_status(status)
     icon(icon_name + ' fw')
   end
 
-  def render_ci_status(ci_commit)
+  def render_ci_status(ci_commit, tooltip_placement: 'auto left')
     link_to ci_status_icon(ci_commit),
       ci_status_path(ci_commit),
       class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
       title: "Build #{ci_status_label(ci_commit)}",
-      data: { toggle: 'tooltip', placement: 'left' }
+      data: { toggle: 'tooltip', placement: tooltip_placement }
   end
 
   def no_runners_for_project?(project)
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index a09e91578b69b2850240608a79069f7b6b7eafc4..f994c9e61707032023e15abb407c168d5695c457 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -211,4 +211,15 @@ def truncate_sha(sha)
   def clean(string)
     Sanitize.clean(string, remove_contents: true)
   end
+
+  def limited_commits(commits)
+    if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+      [
+        commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
+        commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
+      ]
+    else
+      [commits, 0]
+    end
+  end
 end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..74f326e0b83815ebf8f491fe462ff2468b59e863
--- /dev/null
+++ b/app/helpers/dropdowns_helper.rb
@@ -0,0 +1,100 @@
+module DropdownsHelper
+  def dropdown_tag(toggle_text, options: {}, &block)
+    content_tag :div, class: "dropdown" do
+      data_attr = { toggle: "dropdown" }
+
+      if options.has_key?(:data)
+        data_attr = options[:data].merge(data_attr)
+      end
+
+      dropdown_output = dropdown_toggle(toggle_text, data_attr, options)
+
+      dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.has_key?(:dropdown_class)}") do
+        output = ""
+
+        if options.has_key?(:title)
+          output << dropdown_title(options[:title])
+        end
+
+        if options.has_key?(:filter)
+          output << dropdown_filter(options[:placeholder])
+        end
+
+        output << content_tag(:div, class: "dropdown-content") do
+          capture(&block) if block && !options.has_key?(:footer_content)
+        end
+
+        if block && options.has_key?(:footer_content)
+          output << content_tag(:div, class: "dropdown-footer") do
+            capture(&block)
+          end
+        end
+
+        output << dropdown_loading
+
+        output.html_safe
+      end
+
+      dropdown_output.html_safe
+    end
+  end
+
+  def dropdown_toggle(toggle_text, data_attr, options)
+    content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
+      output = content_tag(:span, toggle_text, class: "dropdown-toggle-text")
+      output << icon('chevron-down')
+      output.html_safe
+    end
+  end
+
+  def dropdown_title(title, back: false)
+    content_tag :div, class: "dropdown-title" do
+      title_output = ""
+
+      if back
+        title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do
+          icon('arrow-left')
+        end
+      end
+
+      title_output << content_tag(:span, title)
+
+      title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
+        icon('times')
+      end
+
+      title_output.html_safe
+    end
+  end
+
+  def dropdown_filter(placeholder)
+    content_tag :div, class: "dropdown-input" do
+      filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
+      filter_output << icon('search')
+
+      filter_output.html_safe
+    end
+  end
+
+  def dropdown_content(&block)
+    content_tag(:div, class: "dropdown-content") do
+      if block
+        capture(&block)
+      end
+    end
+  end
+
+  def dropdown_footer(&block)
+    content_tag(:div, class: "dropdown-footer") do
+      if block
+        capture(&block)
+      end
+    end
+  end
+
+  def dropdown_loading
+    content_tag :div, class: "dropdown-loading" do
+      icon('spinner spin')
+    end
+  end
+end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
index 3648757428b29cbc74ce0005e3005dc035d72c50..337b0aacbb52685d26e2fb7b77db94c4c5b194ab 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -1,5 +1,5 @@
 module ExploreHelper
-  def explore_projects_filter_path(options={})
+  def filter_projects_path(options={})
     exist_opts = {
       sort: params[:sort],
       scope: params[:scope],
@@ -9,15 +9,7 @@ def explore_projects_filter_path(options={})
     }
 
     options = exist_opts.merge(options)
-
-    path = if explore_controller?
-             explore_projects_path
-           elsif current_action?(:starred)
-             starred_dashboard_projects_path
-           else
-             dashboard_projects_path
-           end
-
+    path = request.path
     path << "?#{options.to_param}"
     path
   end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 7de81d8dfdb2b7fb2c2b179b4585abe0b7fa2df7..e8ac8788d9dcc3af98c0d8f257fbf426847197dd 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -9,6 +9,32 @@ def milestones_filter_path(opts = {})
     end
   end
 
+  def milestones_label_path(opts = {})
+    if @project
+      namespace_project_issues_path(@project.namespace, @project, opts)
+    elsif @group
+      issues_group_path(@group, opts)
+    else
+      issues_dashboard_path(opts)
+    end
+  end
+
+  def milestones_browse_issuables_path(milestone, type:)
+    opts = { milestone_title: milestone.title }
+
+    if @project
+      polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
+    elsif @group
+      polymorphic_url([type, @group], opts)
+    else
+      polymorphic_url([type, :dashboard], opts)
+    end
+  end
+
+  def milestone_issues_by_label_count(milestone, label, state:)
+    milestone.issues.with_label(label.title).send(state).size
+  end
+
   def milestone_progress_bar(milestone)
     options = {
       class: 'progress-bar progress-bar-success',
@@ -33,6 +59,7 @@ def projects_milestones_options
     grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
     grouped_milestones.unshift(Milestone::None)
     grouped_milestones.unshift(Milestone::Any)
+    grouped_milestones.unshift(Milestone::Upcoming)
 
     options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
   end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 1eb790b1796f78a6c355ca4ef4f87bc9550dd457..494dad0b41ef0980f683fc208bbcdf51e627c5b1 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -40,7 +40,7 @@ def help_autocomplete
       { label: "help: Rake Tasks Help",    url: help_page_path("raketasks", "README") },
       { label: "help: SSH Keys Help",      url: help_page_path("ssh", "README") },
       { label: "help: System Hooks Help",  url: help_page_path("system_hooks", "system_hooks") },
-      { label: "help: Web Hooks Help",     url: help_page_path("web_hooks", "web_hooks") },
+      { label: "help: Webhooks Help",      url: help_page_path("web_hooks", "web_hooks") },
       { label: "help: Workflow Help",      url: help_page_path("workflow", "README") },
     ]
   end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 41ae404899294bf32a5a312eb574170d6acd3cc6..0a5a8eb5aeec8de8f353858e304c2743d5587756 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,14 +1,4 @@
 module SnippetsHelper
-  def lifetime_select_options
-    options = [
-        ['forever', nil],
-        ['1 day',   "#{Date.current + 1.day}"],
-        ['1 week',  "#{Date.current + 1.week}"],
-        ['1 month', "#{Date.current + 1.month}"]
-    ]
-    options_for_select(options)
-  end
-
   def reliable_snippet_path(snippet)
     if snippet.project_id?
       namespace_project_snippet_path(snippet.project.namespace,
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index f9026b887dadf819c6779742ebbf47f9974a1d9a..2f2d2721d6df0f775c6287dd0917f05ef08f7680 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -16,6 +16,16 @@ def sort_options_hash
     }
   end
 
+  def projects_sort_options_hash
+    {
+      sort_value_name => sort_title_name,
+      sort_value_recently_updated => sort_title_recently_updated,
+      sort_value_oldest_updated => sort_title_oldest_updated,
+      sort_value_recently_created => sort_title_recently_created,
+      sort_value_oldest_created => sort_title_oldest_created,
+    }
+  end
+
   def sort_title_oldest_updated
     'Oldest updated'
   end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f34554d557c042a0f0ef30639c5e35fcfa95b1bb..fe9e0aab71732052f451ece80c425aaa4ad16736 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -9,6 +9,7 @@ def allowed(user, subject)
       when CommitStatus then commit_status_abilities(user, subject)
       when Project then project_abilities(user, subject)
       when Issue then issue_abilities(user, subject)
+      when ExternalIssue then external_issue_abilities(user, subject)
       when Note then note_abilities(user, subject)
       when ProjectSnippet then project_snippet_abilities(user, subject)
       when PersonalSnippet then personal_snippet_abilities(user, subject)
@@ -424,6 +425,10 @@ def abilities
       end
     end
 
+    def external_issue_abilities(user, subject)
+      project_abilities(user, subject.project)
+    end
+
     private
 
     def named_abilities(name)
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 8ee9f3006b2b1c329fcc0576f2609a1b302bfbc5..72e6c5fa3fd9628dbaf8d027119e78be1c249701 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -1,5 +1,8 @@
 # Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
 class Blob < SimpleDelegator
+  CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
+  CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
+
   # Wrap a Gitlab::Git::Blob object, or return nil when given nil
   #
   # This method prevents the decorated object from evaluating to "truthy" when
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index e725a6d468c2f861ea2eea1708990f4c1322b113..90349a07594966e60529a79aa37647eb837ab6a2 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -23,7 +23,7 @@ class Runner < ActiveRecord::Base
 
     LAST_CONTACT_TIME = 5.minutes.ago
     AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
-    
+
     has_many :builds, class_name: 'Ci::Build'
     has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
     has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id
@@ -46,9 +46,23 @@ class Runner < ActiveRecord::Base
 
     acts_as_taggable
 
+    # Searches for runners matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # This method performs a *partial* match on tokens, thus a query for "a"
+    # will match any runner where the token contains the letter "a". As a result
+    # you should *not* use this method for non-admin purposes as otherwise users
+    # might be able to query a list of all runners.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def self.search(query)
-      where('LOWER(ci_runners.token) LIKE :query OR LOWER(ci_runners.description) like :query',
-            query: "%#{query.try(:downcase)}%")
+      t = arel_table
+      pattern = "%#{query}%"
+
+      where(t[:token].matches(pattern).or(t[:description].matches(pattern)))
     end
 
     def set_default_values
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 286d6655861494e26576fe91cd51783a0c3833c2..3c42f582937f00d833d4c6f059a3d33a4e707d3c 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -29,12 +29,15 @@ module Issuable
     scope :assigned, -> { where("assignee_id IS NOT NULL") }
     scope :unassigned, -> { where("assignee_id IS NULL") }
     scope :of_projects, ->(ids) { where(project_id: ids) }
+    scope :of_milestones, ->(ids) { where(milestone_id: ids) }
     scope :opened, -> { with_state(:opened, :reopened) }
     scope :only_opened, -> { with_state(:opened) }
     scope :only_reopened, -> { with_state(:reopened) }
     scope :closed, -> { with_state(:closed) }
     scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
     scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+    scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
+    scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
 
     scope :join_project, -> { joins(:project) }
     scope :references_project, -> { references(:project) }
@@ -58,12 +61,29 @@ module Issuable
   end
 
   module ClassMethods
+    # Searches for records with a matching title.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      where("LOWER(title) like :query", query: "%#{query.downcase}%")
+      where(arel_table[:title].matches("%#{query}%"))
     end
 
+    # Searches for records with a matching title or description.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def full_search(query)
-      where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%")
+      t = arel_table
+      pattern = "%#{query}%"
+
+      where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
     end
 
     def sort(method)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d67df7c1d9c1933592f0ceebb40ebdd6f0af3b61
--- /dev/null
+++ b/app/models/concerns/milestoneish.rb
@@ -0,0 +1,25 @@
+module Milestoneish
+  def closed_items_count
+    issues.closed.size + merge_requests.closed_and_merged.size
+  end
+
+  def total_items_count
+    issues.size + merge_requests.size
+  end
+
+  def complete?
+    total_items_count == closed_items_count
+  end
+
+  def percent_complete
+    ((closed_items_count * 100) / total_items_count).abs
+  rescue ZeroDivisionError
+    0
+  end
+
+  def remaining_days
+    return 0 if !due_date || expired?
+
+    (due_date - Date.today).to_i
+  end
+end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 0171f7d54b7d6a56157d4bdcd4a1b664aba71fd6..ddd4bad5c216fc8d729302ab820cbd1c21ab3f3c 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,16 +2,19 @@ class GlobalLabel
   attr_accessor :title, :labels
   alias_attribute :name, :title
 
+  delegate :color, :description, to: :@first_label
+
   def self.build_collection(labels)
     labels = labels.group_by(&:title)
 
-    labels.map do |title, label|
-      new(title, label)
+    labels.map do |title, labels|
+      new(title, labels)
     end
   end
 
   def initialize(title, labels)
     @title = title
     @labels = labels
+    @first_label = labels.find { |lbl| lbl.description.present? } || labels.first
   end
 end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 7ee276255a009faba98cb1c4150835bed01e5728..97bd79af083027cdbd9c0fcac1250364fb397ac8 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -1,4 +1,6 @@
 class GlobalMilestone
+  include Milestoneish
+
   attr_accessor :title, :milestones
   alias_attribute :name, :title
 
@@ -28,33 +30,7 @@ def expired?
   end
 
   def projects
-    milestones.map { |milestone| milestone.project }
-  end
-
-  def issue_count
-    milestones.map { |milestone| milestone.issues.count }.sum
-  end
-
-  def merge_requests_count
-    milestones.map { |milestone| milestone.merge_requests.count }.sum
-  end
-
-  def open_items_count
-    milestones.map { |milestone| milestone.open_items_count }.sum
-  end
-
-  def closed_items_count
-    milestones.map { |milestone| milestone.closed_items_count }.sum
-  end
-
-  def total_items_count
-    milestones.map { |milestone| milestone.total_items_count }.sum
-  end
-
-  def percent_complete
-    ((closed_items_count * 100) / total_items_count).abs
-  rescue ZeroDivisionError
-    0
+    @projects ||= Project.for_milestones(milestones.map(&:id))
   end
 
   def state
@@ -76,35 +52,20 @@ def closed?
   end
 
   def issues
-    @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
+    @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
   end
 
   def merge_requests
-    @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
+    @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
   end
 
   def participants
     @participants ||= milestones.map(&:participants).flatten.compact.uniq
   end
 
-  def opened_issues
-    issues.values_at("opened", "reopened").compact.flatten
-  end
-
-  def closed_issues
-    issues['closed']
-  end
-
-  def opened_merge_requests
-    merge_requests.values_at("opened", "reopened").compact.flatten
-  end
-
-  def closed_merge_requests
-    merge_requests.values_at("closed", "merged", "locked").compact.flatten
-  end
-
-  def complete?
-    total_items_count == closed_items_count
+  def labels
+    @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
+                           .sort_by!(&:title)
   end
 
   def due_date
diff --git a/app/models/group.rb b/app/models/group.rb
index 76042b3e3fd341ef46c1e8bc3d2cf0acd13d0aa1..afbc29220135b8977e4ad4fe1830160c82c486d9 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -33,8 +33,18 @@ class Group < Namespace
   after_destroy :post_destroy_hook
 
   class << self
+    # Searches for groups matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%")
+      table   = Namespace.arel_table
+      pattern = "%#{query}%"
+
+      where(table[:name].matches(pattern).or(table[:path].matches(pattern)))
     end
 
     def sort(method)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d9fbbb85003aeb64477510fe3c106a2675e2ad89..4da2d829fa0be8d2d67446d06b1dd7b280a0fa5a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -137,11 +137,8 @@ class MergeRequest < ActiveRecord::Base
   scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
   scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
   scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
-  scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
   scope :of_projects, ->(ids) { where(target_project_id: ids) }
-  scope :opened, -> { with_states(:opened, :reopened) }
   scope :merged, -> { with_state(:merged) }
-  scope :closed, -> { with_state(:closed) }
   scope :closed_and_merged, -> { with_states(:closed, :merged) }
 
   scope :join_project, -> { joins(:target_project) }
@@ -165,6 +162,24 @@ def self.link_reference_pattern
     super("merge_requests", /(?<merge_request>\d+)/)
   end
 
+  # Returns all the merge requests from an ActiveRecord:Relation.
+  #
+  # This method uses a UNION as it usually operates on the result of
+  # ProjectsFinder#execute. PostgreSQL in particular doesn't always like queries
+  # using multiple sub-queries especially when combined with an OR statement.
+  # UNIONs on the other hand perform much better in these cases.
+  #
+  # relation - An ActiveRecord::Relation that returns a list of Projects.
+  #
+  # Returns an ActiveRecord::Relation.
+  def self.in_projects(relation)
+    source = where(source_project_id: relation).select(:id)
+    target = where(target_project_id: relation).select(:id)
+    union  = Gitlab::SQL::Union.new([source, target])
+
+    where("merge_requests.id IN (#{union.to_sql})")
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index df08d3a6dfb3c82805bc1415d9b71f5b168234bf..33884118595cd39ea9c9340bea090a657daf5972 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -17,7 +17,7 @@ class MergeRequestDiff < ActiveRecord::Base
   include Sortable
 
   # Prevent store of diff if commits amount more then 500
-  COMMITS_SAFE_SIZE = 500
+  COMMITS_SAFE_SIZE = 100
 
   belongs_to :merge_request
 
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 7dc2f909b2f82367795642741ff396510e5c3894..374590ba0c51093c1990978062c9c6b20b4e0f76 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -19,17 +19,19 @@ class Milestone < ActiveRecord::Base
   MilestoneStruct = Struct.new(:title, :name, :id)
   None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
   Any = MilestoneStruct.new('Any Milestone', '', -1)
+  Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
 
   include InternalId
   include Sortable
   include Referable
   include StripAttribute
+  include Milestoneish
 
   belongs_to :project
   has_many :issues
   has_many :labels, -> { distinct.reorder('labels.title') },  through: :issues
   has_many :merge_requests
-  has_many :participants, through: :issues, source: :assignee
+  has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
 
   scope :active, -> { with_state(:active) }
   scope :closed, -> { with_state(:closed) }
@@ -57,9 +59,18 @@ class Milestone < ActiveRecord::Base
   alias_attribute :name, :title
 
   class << self
+    # Searches for milestones matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      query = "%#{query}%"
-      where("title like ? or description like ?", query, query)
+      t = arel_table
+      pattern = "%#{query}%"
+
+      where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
     end
   end
 
@@ -71,6 +82,10 @@ def self.link_reference_pattern
     super("milestones", /(?<milestone>\d+)/)
   end
 
+  def self.upcoming
+    self.where('due_date > ?', Time.now).order(due_date: :asc).first
+  end
+
   def to_reference(from_project = nil)
     escaped_title = self.title.gsub("]", "\\]")
 
@@ -92,30 +107,6 @@ def expired?
     end
   end
 
-  def open_items_count
-    self.issues.opened.count + self.merge_requests.opened.count
-  end
-
-  def closed_items_count
-    self.issues.closed.count + self.merge_requests.closed_and_merged.count
-  end
-
-  def total_items_count
-    self.issues.count + self.merge_requests.count
-  end
-
-  def percent_complete
-    ((closed_items_count * 100) / total_items_count).abs
-  rescue ZeroDivisionError
-    0
-  end
-
-  def remaining_days
-    return 0 if !due_date || expired?
-
-    (due_date - Date.today).to_i
-  end
-
   def expires_at
     if due_date
       if due_date.past?
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bdb33f3749543a8e9e38a8faec739ed4fb7a7d6f..55842df1e2d3138919a52d4984c62ddd2e593d20 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -52,8 +52,18 @@ def find_by_path_or_name(path)
       find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
     end
 
+    # Searches for namespaces matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation
     def search(query)
-      where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
+      t = arel_table
+      pattern = "%#{query}%"
+
+      where(t[:name].matches(pattern).or(t[:path].matches(pattern)))
     end
 
     def clean_path(path)
diff --git a/app/models/note.rb b/app/models/note.rb
index 3b20d5d22b6863a1b91636402ca3a9b9d0acddbe..2e084b5c80c74a88eccfe3827b003a645ddb9b95 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -44,6 +44,7 @@ class Note < ActiveRecord::Base
   delegate :name, :email, to: :author, prefix: true
 
   before_validation :set_award!
+  before_validation :clear_blank_line_code!
 
   validates :note, :project, presence: true
   validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
@@ -63,7 +64,7 @@ class Note < ActiveRecord::Base
   scope :nonawards, ->{ where(is_award: false) }
   scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
   scope :inline, ->{ where("line_code IS NOT NULL") }
-  scope :not_inline, ->{ where(line_code: [nil, '']) }
+  scope :not_inline, ->{ where(line_code: nil) }
   scope :system, ->{ where(system: true) }
   scope :user, ->{ where(system: false) }
   scope :common, ->{ where(noteable_type: ["", nil]) }
@@ -105,8 +106,18 @@ def build_discussion_id(type, id, line_code)
       [:discussion, type.try(:underscore), id, line_code].join("-").to_sym
     end
 
+    # Searches for notes matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String.
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      where("LOWER(note) like :query", query: "%#{query.downcase}%")
+      table   = arel_table
+      pattern = "%#{query}%"
+
+      where(table[:note].matches(pattern))
     end
 
     def grouped_awards
@@ -365,6 +376,10 @@ def set_award!
 
   private
 
+  def clear_blank_line_code!
+    self.line_code = nil if self.line_code.blank?
+  end
+
   def awards_supported?
     (for_issue? || for_merge_request?) && !for_diff_line?
   end
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 9cee3b70cb3ecf2f271320bfbf5ab57eceb9cc02..452f3913eef66cdb897ceae9bbd720e302dba8ee 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -10,7 +10,6 @@
 #  created_at       :datetime
 #  updated_at       :datetime
 #  file_name        :string(255)
-#  expires_at       :datetime
 #  type             :string(255)
 #  visibility_level :integer          default(0), not null
 #
diff --git a/app/models/project.rb b/app/models/project.rb
index 148eab692ff8171eb6123b9b1d7edb9488ff31b2..1f18ad78164fb4994c469c4a7f31d9e301636564 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -151,6 +151,7 @@ def update_forks_visibility_level
   has_many :releases, dependent: :destroy
   has_many :lfs_objects_projects, dependent: :destroy
   has_many :lfs_objects, through: :lfs_objects_projects
+  has_many :todos, dependent: :destroy
 
   has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
 
@@ -215,6 +216,7 @@ def update_forks_visibility_level
   scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
   scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
   scope :non_archived, -> { where(archived: false) }
+  scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
 
   state_machine :import_status, initial: :none do
     event :import_start do
@@ -264,13 +266,31 @@ def active
       joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
     end
 
+    # Searches for a list of projects based on the query given in `query`.
+    #
+    # On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
+    # search. On MySQL a regular "LIKE" is used as it's already
+    # case-insensitive.
+    #
+    # query - The search query as a String.
     def search(query)
-      joins(:namespace).
-        where('LOWER(projects.name) LIKE :query OR
-              LOWER(projects.path) LIKE :query OR
-              LOWER(namespaces.name) LIKE :query OR
-              LOWER(projects.description) LIKE :query',
-              query: "%#{query.try(:downcase)}%")
+      ptable  = arel_table
+      ntable  = Namespace.arel_table
+      pattern = "%#{query}%"
+
+      projects = select(:id).where(
+        ptable[:path].matches(pattern).
+          or(ptable[:name].matches(pattern)).
+          or(ptable[:description].matches(pattern))
+      )
+
+      namespaces = select(:id).
+        joins(:namespace).
+        where(ntable[:name].matches(pattern))
+
+      union = Gitlab::SQL::Union.new([projects, namespaces])
+
+      where("projects.id IN (#{union.to_sql})")
     end
 
     def search_by_visibility(level)
@@ -278,7 +298,10 @@ def search_by_visibility(level)
     end
 
     def search_by_title(query)
-      non_archived.where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
+      pattern = "%#{query}%"
+      table   = Project.arel_table
+
+      non_archived.where(table[:name].matches(pattern))
     end
 
     def find_with_namespace(id)
@@ -526,11 +549,11 @@ def find_service(list, name)
   end
 
   def ci_services
-    services.select { |service| service.category == :ci }
+    services.where(category: :ci)
   end
 
   def ci_service
-    @ci_service ||= ci_services.find(&:activated?)
+    @ci_service ||= ci_services.reorder(nil).find_by(active: true)
   end
 
   def jira_tracker?
@@ -907,13 +930,13 @@ def any_runners?(&block)
   end
 
   def valid_runners_token? token
-    self.runners_token && self.runners_token == token
+    self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
   end
 
   # TODO (ayufan): For now we use runners_token (backward compatibility)
   # In 8.4 every build will have its own individual token valid for time of build
   def valid_build_token? token
-    self.builds_enabled? && self.runners_token && self.runners_token == token
+    self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
   end
 
   def build_coverage_enabled?
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index e10b5529b4269c370eff512a41c26a7b1980248e..d9f0849d1470b5f316c35a49540f9b69b1df8214 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -26,7 +26,7 @@ class CiService < Service
   default_value_for :category, 'ci'
 
   def valid_token?(token)
-    self.respond_to?(:token) && self.token.present? && self.token == token
+    self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
   end
 
   def supported_events
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 9e2c1b0e18e8413b344ff08ed7c52330ee2a5951..1f7d85a5f3d4b2e2f85ba8b10a1331baa31e6595 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -10,7 +10,6 @@
 #  created_at       :datetime
 #  updated_at       :datetime
 #  file_name        :string(255)
-#  expires_at       :datetime
 #  type             :string(255)
 #  visibility_level :integer          default(0), not null
 #
@@ -23,6 +22,4 @@ class ProjectSnippet < Snippet
 
   # Scopes
   scope :fresh, -> { order("created_at DESC") }
-  scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
-  scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c135ab61f6a00f66b106d580cbacfd02ca3851d4..6441cd87e87715601aaa943f2744812611c6afb7 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -133,18 +133,18 @@ def add_branch(user, branch_name, target)
       rugged.branches.create(branch_name, target)
     end
 
-    expire_branches_cache
+    after_create_branch
     find_branch(branch_name)
   end
 
   def add_tag(tag_name, ref, message = nil)
-    expire_tags_cache
+    before_push_tag
 
     gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
   end
 
   def rm_branch(user, branch_name)
-    expire_branches_cache
+    before_remove_branch
 
     branch = find_branch(branch_name)
     oldrev = branch.try(:target)
@@ -155,12 +155,12 @@ def rm_branch(user, branch_name)
       rugged.branches.delete(branch_name)
     end
 
-    expire_branches_cache
+    after_remove_branch
     true
   end
 
   def rm_tag(tag_name)
-    expire_tags_cache
+    before_remove_tag
 
     gitlab_shell.rm_tag(path_with_namespace, tag_name)
   end
@@ -183,6 +183,14 @@ def commit_count
     end
   end
 
+  def branch_count
+    @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+  end
+
+  def tag_count
+    @tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count }
+  end
+
   # Return repo size in megabytes
   # Cached in redis
   def size
@@ -278,6 +286,16 @@ def expire_has_visible_content_cache
     @has_visible_content = nil
   end
 
+  def expire_branch_count_cache
+    cache.expire(:branch_count)
+    @branch_count = nil
+  end
+
+  def expire_tag_count_cache
+    cache.expire(:tag_count)
+    @tag_count = nil
+  end
+
   def rebuild_cache
     cache_keys.each do |key|
       cache.expire(key)
@@ -313,9 +331,17 @@ def before_change_head
     expire_root_ref_cache
   end
 
-  # Runs code before creating a new tag.
-  def before_create_tag
+  # Runs code before pushing (= creating or removing) a tag.
+  def before_push_tag
     expire_cache
+    expire_tags_cache
+    expire_tag_count_cache
+  end
+
+  # Runs code before removing a tag.
+  def before_remove_tag
+    expire_tags_cache
+    expire_tag_count_cache
   end
 
   # Runs code after a repository has been forked/imported.
@@ -330,12 +356,21 @@ def after_push_commit(branch_name)
 
   # Runs code after a new branch has been created.
   def after_create_branch
+    expire_branches_cache
     expire_has_visible_content_cache
+    expire_branch_count_cache
+  end
+
+  # Runs code before removing an existing branch.
+  def before_remove_branch
+    expire_branches_cache
   end
 
   # Runs code after an existing branch has been removed.
   def after_remove_branch
     expire_has_visible_content_cache
+    expire_branch_count_cache
+    expire_branches_cache
   end
 
   def method_missing(m, *args, &block)
@@ -812,6 +847,12 @@ def ls_files(ref)
     raw_repository.ls_files(actual_ref)
   end
 
+  def main_language
+    unless empty?
+      Linguist::Repository.new(rugged, rugged.head.target_id).language
+    end
+  end
+
   private
 
   def cache
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f876be7a4c8540ad104e1c086282def4183dd6dc..b9e835a448625115b5ee12201c15a7413783e9dd 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -10,7 +10,6 @@
 #  created_at       :datetime
 #  updated_at       :datetime
 #  file_name        :string(255)
-#  expires_at       :datetime
 #  type             :string(255)
 #  visibility_level :integer          default(0), not null
 #
@@ -46,8 +45,6 @@ class Snippet < ActiveRecord::Base
   scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
   scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
   scope :fresh,   -> { order("created_at DESC") }
-  scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
-  scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
 
   participant :author, :notes
 
@@ -111,21 +108,37 @@ def mode
     nil
   end
 
-  def expired?
-    expires_at && expires_at < Time.current
-  end
-
   def visibility_level_field
     visibility_level
   end
 
   class << self
+    # Searches for snippets with a matching title or file name.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String.
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
+      t = arel_table
+      pattern = "%#{query}%"
+
+      where(t[:title].matches(pattern).or(t[:file_name].matches(pattern)))
     end
 
+    # Searches for snippets with matching content.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String.
+    #
+    # Returns an ActiveRecord::Relation.
     def search_code(query)
-      where('(content LIKE :query)', query: "%#{query}%")
+      table   = Snippet.arel_table
+      pattern = "%#{query}%"
+
+      where(table[:content].matches(pattern))
     end
 
     def accessible_to(user)
diff --git a/app/models/user.rb b/app/models/user.rb
index 3098d49d58a033825ff76ce992b984c43c69b3e4..043bc825ade7236eeeeb2a46d1b45ce5a52acf6e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -286,8 +286,22 @@ def filter(filter_name)
       end
     end
 
+    # Searches users matching the given query.
+    #
+    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+    #
+    # query - The search query as a String
+    #
+    # Returns an ActiveRecord::Relation.
     def search(query)
-      where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
+      table   = arel_table
+      pattern = "%#{query}%"
+
+      where(
+        table[:name].matches(pattern).
+          or(table[:email].matches(pattern)).
+          or(table[:username].matches(pattern))
+      )
     end
 
     def by_login(login)
@@ -612,6 +626,13 @@ def requires_ldap_check?
     end
   end
 
+  def try_obtain_ldap_lease
+    # After obtaining this lease LDAP checks will be blocked for 600 seconds
+    # (10 minutes) for this user.
+    lease = Gitlab::ExclusiveLease.new("user_ldap_check:#{id}", timeout: 600)
+    lease.try_obtain
+  end
+
   def solo_owned_groups
     @solo_owned_groups ||= owned_groups.select do |group|
       group.owners == [self]
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 9ba200f7bde394d4d6bc8c779b9966af0a8b4be2..bd31a617747c0c60a81cafcd5419f2c50c3cc3a2 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -12,8 +12,9 @@ class GitPushService < BaseService
   #  1. Creates the push event
   #  2. Updates merge requests
   #  3. Recognizes cross-references from commit messages
-  #  4. Executes the project's web hooks
+  #  4. Executes the project's webhooks
   #  5. Executes the project's services
+  #  6. Checks if the project's main language has changed
   #
   def execute
     @project.repository.after_push_commit(branch_name)
@@ -42,11 +43,24 @@ def execute
       @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
       process_commit_messages
     end
+    # Checks if the main language has changed in the project and if so
+    # it updates it accordingly
+    update_main_language
     # Update merge requests that may be affected by this push. A new branch
     # could cause the last commit of a merge request to change.
     update_merge_requests
   end
 
+  def update_main_language
+    current_language = @project.repository.main_language
+
+    unless current_language == @project.main_language
+      return @project.update_attributes(main_language: current_language)
+    end
+
+    true
+  end
+
   protected
 
   def update_merge_requests
@@ -96,7 +110,9 @@ def process_commit_messages
         # a different branch.
         closed_issues = commit.closes_issues(current_user)
         closed_issues.each do |issue|
-          Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+          if can?(current_user, :update_issue, issue)
+            Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+          end
         end
       end
 
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index a62c5fc4fc41eaa0891045a387b84430faf309b0..c88c76728051e9861f98fbb850b45096821d4992 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,7 +2,7 @@ class GitTagPushService
   attr_accessor :project, :user, :push_data
 
   def execute(project, user, oldrev, newrev, ref)
-    project.repository.before_create_tag
+    project.repository.before_push_tag
 
     @project, @user = project, user
     @push_data = build_push_data(oldrev, newrev, ref)
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index 8f25c5e24967dc601fc62f1007fc5ec3296f979a..ebb67c7db65497f22a5b687abd1ba3c04f2c820d 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -21,7 +21,9 @@ def close_issues(merge_request)
 
       closed_issues = merge_request.closes_issues(current_user)
       closed_issues.each do |issue|
-        Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+        if can?(current_user, :update_issue, issue)
+          Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+        end
       end
     end
 
diff --git a/app/services/projects/import_export.rb b/app/services/projects/import_export.rb
index b0c0891edb5a4cd32668630c8658058ba9fbe366..373916a0c4c9d150d5ffe052296fafdf2ca29f67 100644
--- a/app/services/projects/import_export.rb
+++ b/app/services/projects/import_export.rb
@@ -11,13 +11,21 @@ def project_atts
     end
 
     def project_tree
-      %i(issues merge_requests labels milestones snippets releases events commit_statuses) + members
+      %i(issues labels milestones snippets releases events) + [members, merge_requests, commit_statuses]
     end
 
     private
 
+    def merge_requests
+      { merge_requests: { include: :merge_request_diff } }
+    end
+
+    def commit_statuses
+      { commit_statuses: { include: :commit } }
+    end
+
     def members
-      [{ project_members: { include: [user: { only: [:id, :email, :username] }] } }]
+      { project_members: { include: [user: { only: [:id, :email, :username] }] } }
     end
 
     def storage_path
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index e904cb6c6fcceccf68dc249b7ff25c77c42d8fc0..e1e94c5cc38dde3a444ab45f31a36079a2c984fd 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -10,9 +10,8 @@ def execute
       group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
       projects = ProjectsFinder.new.execute(current_user)
       projects = projects.in_namespace(group.id) if group
-      project_ids = projects.pluck(:id)
 
-      Gitlab::SearchResults.new(project_ids, params[:search])
+      Gitlab::SearchResults.new(projects, params[:search])
     end
   end
 end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index f630c0a37903a374f9edd0aa5ee11a3c68b45b8d..c08881dce4b8b2b398a9ebfb766f78c7d52b0958 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -7,7 +7,7 @@ def initialize(project, user, params)
     end
 
     def execute
-      Gitlab::ProjectSearchResults.new(project.id,
+      Gitlab::ProjectSearchResults.new(project,
                                        params[:search],
                                        params[:repository_ref])
     end
diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb
index 8ca0877321d759efbb7e1e9ec1f3aba6a5b45ac6..0b3e713e22000f5070fe9c5a22a1c30efd35dc52 100644
--- a/app/services/search/snippet_service.rb
+++ b/app/services/search/snippet_service.rb
@@ -7,8 +7,9 @@ def initialize(user, params)
     end
 
     def execute
-      snippet_ids = Snippet.accessible_to(current_user).pluck(:id)
-      Gitlab::SnippetSearchResults.new(snippet_ids, params[:search])
+      snippets = Snippet.accessible_to(current_user)
+
+      Gitlab::SnippetSearchResults.new(snippets, params[:search])
     end
   end
 end
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 40f88261c101bf3ee0003484327b676eed7ee726..9da3fcbd986c15c2b7a56f033e9c67c424161db1 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -15,7 +15,7 @@
   .nav-controls
     = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
       = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
-    = render 'explore/projects/dropdown'
+    = render 'shared/projects/dropdown'
     - if current_user.can_create_project?
       = link_to new_project_path, class: 'btn btn-new' do
         = icon('plus')
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
deleted file mode 100644
index 1408ebdd5dcf6953a63ae06a96ab531118871524..0000000000000000000000000000000000000000
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
-  %span.milestone-row
-    - project = issue.project
-    %strong #{project.name_with_namespace} &middot;
-    = link_to [project.namespace.becomes(Namespace), project, issue] do
-      %span.cgray ##{issue.iid}
-    = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
-  .pull-right.assignee-icon
-    - if issue.assignee
-      = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd90e324398e5981d32510384dc83d9..0000000000000000000000000000000000000000
--- a/app/views/dashboard/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
-  .panel-heading= title
-  %ul{ class: "well-list issues-sortable-list" }
-    - if issues
-      - issues.each do |issue|
-        = render 'issue', issue: issue
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
deleted file mode 100644
index 77c46de030b1b3bd56121581f9457828a9e68588..0000000000000000000000000000000000000000
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'),  class: 'mr-row', 'data-iid' => merge_request.iid }
-  %span.milestone-row
-    - project = merge_request.project
-    %strong #{project.name_with_namespace} &middot;
-    = link_to [project.namespace.becomes(Namespace), project, merge_request] do
-      %span.cgray ##{merge_request.iid}
-    = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
-  .pull-right.assignee-icon
-    - if merge_request.assignee
-      = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636db5cb19c5dbf8184ed9a9d5547432..0000000000000000000000000000000000000000
--- a/app/views/dashboard/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
-  .panel-heading= title
-  %ul{ class: "well-list merge_requests-sortable-list" }
-    - if merge_requests
-      - merge_requests.each do |merge_request|
-        = render 'merge_request', merge_request: merge_request
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 7c882a327025698db068b0f4e1149a9bd1543d50..6173ca6ab9bb5cf76c87b0180f3cecabdd9dcb1f 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -1,25 +1,6 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
-  .row
-    .col-sm-6
-      %strong
-        = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
-    .col-sm-6
-      .pull-right.light #{milestone.percent_complete}% complete
-  .row
-    .col-sm-6
-      = link_to issues_dashboard_path(milestone_title: milestone.title) do
-        = pluralize milestone.issue_count, 'Issue'
-      &middot;
-      = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
-        = pluralize milestone.merge_requests_count, 'Merge Request'
-    .col-sm-6
-      = milestone_progress_bar(milestone)
-  .row
-    .col-sm-6
-      .expiration
-        = render 'shared/milestone_expired', milestone: milestone
-      .projects
-        - milestone.milestones.each do |milestone|
-          = link_to milestone_path(milestone) do
-            %span.label.label-gray
-              = milestone.project.name_with_namespace
+= render 'shared/milestones/milestone',
+          milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
+          issues_path: issues_dashboard_path(milestone_title: milestone.title),
+          merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
+          milestone: milestone,
+          dashboard: true
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 3810267577c1a0cd0e0677cf18300252a3960ffc..60c84a2642079156bec92509f8fa3180e7bbefb2 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,105 +1,5 @@
-- page_title @milestone.title, "Milestones"
 - header_title "Milestones", dashboard_milestones_path
 
-.detail-page-header
-  .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
-    - if @milestone.closed?
-      Closed
-    - else
-      Open
-  %span.identifier
-    Milestone #{@milestone.title}
-
-.detail-page-description.gray-content-block.second-block
-  %h2.title
-    = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
-  .alert.alert-success.prepend-top-default
-    %span All issues for this milestone are closed. Navigate to the project to close the milestone.
-
-.table-holder
-  %table.table
-    %thead
-      %tr
-        %th Project
-        %th Open issues
-        %th State
-        %th Due date
-    - @milestone.milestones.each do |milestone|
-      %tr
-        %td
-          = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
-        %td
-          = milestone.issues.opened.count
-        %td
-          - if milestone.closed?
-            Closed
-          - else
-            Open
-        %td
-          = milestone.expires_at
-
-.context
-  %p.lead
-    Progress:
-    #{@milestone.closed_items_count} closed
-    &ndash;
-    #{@milestone.open_items_count} open
-  = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
-  %li.active
-    = link_to '#tab-issues', 'data-toggle' => 'tab' do
-      Issues
-      %span.badge= @milestone.issue_count
-  %li
-    = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
-      Merge Requests
-      %span.badge= @milestone.merge_requests_count
-  %li
-    = link_to '#tab-participants', 'data-toggle' => 'tab' do
-      Participants
-      %span.badge= @milestone.participants.count
-
-.tab-content
-  .tab-pane.active#tab-issues
-    .gray-content-block.middle-block
-      .pull-right
-        = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
-      .oneline
-        All issues in this milestone
-
-    .row.prepend-top-default
-      .col-md-6
-        = render 'issues', title: "Open", issues: @milestone.opened_issues
-      .col-md-6
-        = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
-  .tab-pane#tab-merge-requests
-    .gray-content-block.middle-block
-      .pull-right
-        = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
-      .oneline
-        All merge requests in this milestone
-
-    .row.prepend-top-default
-      .col-md-6
-        = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
-      .col-md-6
-        = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
-  .tab-pane#tab-participants
-    .gray-content-block.middle-block
-      .oneline
-        All participants to this milestone
-    %ul.bordered-list
-      - @milestone.participants.each do |user|
-        %li
-          = link_to user, title: user.name, class: "darken" do
-            = image_tag avatar_icon(user, 32), class: "avatar s32"
-            %strong= truncate(user.name, lenght: 40)
-            %br
-            %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index f878d36e7394008d913e1d7e467bd38a36049d93..45cfe3da188f5eda4b055958dea63895182423bd 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -4,7 +4,10 @@
 
     .todo-title
       %span.author-name
-        = link_to_author todo
+        - if todo.author
+          = link_to_author(todo)
+        - else
+          (removed)
       %span.todo-label
         = todo_action_name(todo)
         = todo_target_link(todo)
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
index b66e513e4d2ceda30f67ddbaf727449e977adcd2..3443a8e2307f5e3e611a1852c2e9e4d003589372 100644
--- a/app/views/emojis/index.html.haml
+++ b/app/views/emojis/index.html.haml
@@ -2,8 +2,10 @@
   .emoji-menu-content
     = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
     - AwardEmoji.emoji_by_category.each do |category, emojis|
-      %h5= AwardEmoji::CATEGORIES[category]
-      %ul
+      %h5.emoji-menu-title
+        = AwardEmoji::CATEGORIES[category]
+      %ul.clearfix.emoji-menu-list
         - emojis.each do |emoji|
-          %li
-            = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
\ No newline at end of file
+          %li.pull-left.text-center.emoji-menu-list-item
+            %button.emoji-menu-btn.text-center.js-emoji-btn{type: "button"}
+              = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
deleted file mode 100644
index a4b4cd8d6c7a30e584c753ebfcd7b124f5b2dfd4..0000000000000000000000000000000000000000
--- a/app/views/explore/projects/_dropdown.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.dropdown.inline
-  %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-    %span.light
-    - if @sort.present?
-      = sort_options_hash[@sort]
-    - else
-      = sort_title_recently_updated
-    %b.caret
-  %ul.dropdown-menu.dropdown-menu-align-right
-    %li
-      = link_to explore_projects_filter_path(sort: sort_value_name) do
-        = sort_title_name
-      = link_to explore_projects_filter_path(sort: sort_value_recently_created) do
-        = sort_title_recently_created
-      = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
-        = sort_title_oldest_created
-      = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
-        = sort_title_recently_updated
-      = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
-        = sort_title_oldest_updated
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 39e3e8e2738b19ec7a9b2348e39e0656fc50f9e2..cd485da5104f77aec9fd42ba85f4b388593b4a20 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -10,11 +10,11 @@
       %b.caret
     %ul.dropdown-menu
       %li
-        = link_to explore_projects_filter_path(visibility_level: nil) do
+        = link_to filter_projects_path(visibility_level: nil) do
           Any
       - Gitlab::VisibilityLevel.values.each do |level|
         %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
-          = link_to explore_projects_filter_path(visibility_level: level) do
+          = link_to filter_projects_path(visibility_level: level) do
             = visibility_level_icon(level)
             = visibility_level_label(level)
 
@@ -30,11 +30,11 @@
       %b.caret
     %ul.dropdown-menu
       %li
-        = link_to explore_projects_filter_path(tag: nil) do
+        = link_to filter_projects_path(tag: nil) do
           Any
 
       - @tags.each do |tag|
         %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
-          = link_to explore_projects_filter_path(tag: tag.name) do
-            %i.fa.fa-tag
+          = link_to filter_projects_path(tag: tag.name) do
+            = icon('tag')
             = tag.name
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..dc76599b7767fe4ded0b42c8c80e2292de72b7b1
--- /dev/null
+++ b/app/views/groups/_activities.html.haml
@@ -0,0 +1,12 @@
+.hidden-xs
+  = render "events/event_last_push", event: @last_push
+
+.nav-block
+  - if current_user
+    .controls
+      = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
+        %i.fa.fa-rss
+  = render 'shared/event_filter'
+
+.content_list
+= spinner
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 794aa57b55a3d8d107066792777e282009a60a11..7cd8e9bea46d866b401a648d2a91144d8db3efc5 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -3,9 +3,10 @@
     = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
       - if @projects.present?
         = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
-      - if can? current_user, :create_projects, @group
-        = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
-          = icon('plus')
-          New Project
+    = render 'shared/projects/dropdown'
+    - if can? current_user, :create_projects, @group
+      = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
+        = icon('plus')
+        New Project
 
 = render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f73e1d9e8652fdc398b4a2faefdb7673b9b63dfe
--- /dev/null
+++ b/app/views/groups/activity.html.haml
@@ -0,0 +1,9 @@
+= content_for :meta_tags do
+  - if current_user
+    = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
+
+- page_title    "Activity"
+- header_title  group_title(@group, "Activity", activity_group_path(@group))
+
+%section.activities
+  = render 'activities'
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
deleted file mode 100644
index 9b85d83d6d854cc8e0f48ed78bfd39c3b7edfeb9..0000000000000000000000000000000000000000
--- a/app/views/groups/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
-  %span.milestone-row
-    - project = issue.project
-    %strong #{project.name} &middot;
-    = link_to [project.namespace.becomes(Namespace), project, issue] do
-      %span.cgray ##{issue.iid}
-    = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
-  .pull-right.assignee-icon
-    - if issue.assignee
-      = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd90e324398e5981d32510384dc83d9..0000000000000000000000000000000000000000
--- a/app/views/groups/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
-  .panel-heading= title
-  %ul{ class: "well-list issues-sortable-list" }
-    - if issues
-      - issues.each do |issue|
-        = render 'issue', issue: issue
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
deleted file mode 100644
index e3aa4aad198f558929077e653dd482339f81fd29..0000000000000000000000000000000000000000
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'),  class: 'mr-row', 'data-iid' => merge_request.iid }
-  %span.milestone-row
-    - project = merge_request.project
-    %strong #{project.name} &middot;
-    = link_to [project.namespace.becomes(Namespace), project, merge_request] do
-      %span.cgray ##{merge_request.iid}
-    = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
-  .pull-right.assignee-icon
-    - if merge_request.assignee
-      = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636db5cb19c5dbf8184ed9a9d5547432..0000000000000000000000000000000000000000
--- a/app/views/groups/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
-  .panel-heading= title
-  %ul{ class: "well-list merge_requests-sortable-list" }
-    - if merge_requests
-      - merge_requests.each do |merge_request|
-        = render 'merge_request', merge_request: merge_request
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index a20bf75bc39e2d320651479f0a116a9f90935a9c..4c4e0a267288dc0459721c9aeae0dcfb5b4ae9b7 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,29 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
-  .row
-    .col-sm-6
-      %strong
-        = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
-    .col-sm-6
-      .pull-right.light #{milestone.percent_complete}% complete
-  .row
-    .col-sm-6
-      = link_to issues_group_path(@group, milestone_title: milestone.title) do
-        = pluralize milestone.issue_count, 'Issue'
-      &middot;
-      = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
-        = pluralize milestone.merge_requests_count, 'Merge Request'
-    .col-sm-6
-      = milestone_progress_bar(milestone)
-  .row
-    .col-sm-6
-      %div
-        - milestone.milestones.each do |milestone|
-          = link_to milestone_path(milestone) do
-            %span.label.label-gray
-              = milestone.project.name
-    .col-sm-6
-      - if can?(current_user, :admin_milestones, @group)
-        - if milestone.closed?
-          = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
-        - else
-          = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+= render 'shared/milestones/milestone',
+          milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
+          issues_path: issues_group_path(@group, milestone_title: milestone.title),
+          merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
+          milestone: milestone
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 1233da8552492f5da6d52ed5eaa1b0394b93c043..fb6f0da28f82b2829bb75f33a5539b6e59448322 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,112 +1,4 @@
-- page_title @milestone.title, "Milestones"
 = render "header_title"
-
-.detail-page-header
-  .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
-    - if @milestone.closed?
-      Closed
-    - else
-      Open
-  %span.identifier
-    Milestone #{@milestone.title}
-  .pull-right
-    - if can?(current_user, :admin_milestones, @group)
-      - if @milestone.active?
-        = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
-      - else
-        = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-
-.detail-page-description.gray-content-block.second-block
-  %h2.title
-    = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
-  .alert.alert-success.prepend-top-default
-    %span All issues for this milestone are closed. You may close the milestone now.
-
-.table-holder
-  %table.table
-    %thead
-      %tr
-        %th Project
-        %th Open issues
-        %th State
-        %th Due date
-    - @milestone.milestones.each do |milestone|
-      %tr
-        %td
-          = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
-        %td
-          = milestone.issues.opened.count
-        %td
-          - if milestone.closed?
-            Closed
-          - else
-            Open
-        %td
-          = milestone.expires_at
-
-.context
-  %p.lead
-    Progress:
-    #{@milestone.closed_items_count} closed
-    &ndash;
-    #{@milestone.open_items_count} open
-  = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
-  %li.active
-    = link_to '#tab-issues', 'data-toggle' => 'tab' do
-      Issues
-      %span.badge= @milestone.issue_count
-  %li
-    = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
-      Merge Requests
-      %span.badge= @milestone.merge_requests_count
-  %li
-    = link_to '#tab-participants', 'data-toggle' => 'tab' do
-      Participants
-      %span.badge= @milestone.participants.count
-
-.tab-content
-  .tab-pane.active#tab-issues
-    .gray-content-block.middle-block
-      .pull-right
-        = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
-      .oneline
-        All issues in this milestone
-
-    .row.prepend-top-default
-      .col-md-6
-        = render 'issues', title: "Open", issues: @milestone.opened_issues
-      .col-md-6
-        = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
-  .tab-pane#tab-merge-requests
-    .gray-content-block.middle-block
-      .pull-right
-        = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
-      .oneline
-        All merge requests in this milestone
-
-    .row.prepend-top-default
-      .col-md-6
-        = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
-      .col-md-6
-        = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
-  .tab-pane#tab-participants
-    .gray-content-block.middle-block
-      .oneline
-        All participants to this milestone
-
-    %ul.bordered-list
-      - @milestone.participants.each do |user|
-        %li
-          = link_to user, title: user.name, class: "darken" do
-            = image_tag avatar_icon(user, 32), class: "avatar s32"
-            %strong= truncate(user.name, lenght: 40)
-            %br
-            %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone, group: @group
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 6148d8cb3d2fc7ebcdf93a6e2f3eda84e5ba23e9..3cf0a4baacd26169d3029446c49539ec14db5aba 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -30,26 +30,13 @@
 
   %ul.nav-links
     %li.active
-      = link_to "#activity", 'data-toggle' => 'tab' do
-        Activity
-    %li
       = link_to "#projects", 'data-toggle' => 'tab' do
         Projects
 
 - if can?(current_user, :read_group, @group)
   %div{ class: container_class }
     .tab-content
-      .tab-pane.active#activity
-        .activity-filter-block
-          - if current_user
-            = render "events/event_last_push", event: @last_push
-
-            = render 'shared/event_filter'
-
-        .content_list{data: {href: events_group_path}}
-        = spinner
-
-      .tab-pane#projects
+      .tab-pane.active#projects
         = render "projects", projects: @projects
 
 - else
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 82d2d4aabedcb5b15bb7d2f83c4bd318570f2a5f..da3c3711cdd0bbf861c669cc92e30d4e756a6b70 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -22,6 +22,14 @@
                 %td.shortcut
                   .key ?
                 %td Show this dialog
+              %tr
+                %td.shortcut
+                  - if browser.mac?
+                    .key &#8984; shift p
+                  - else
+                    .key ctrl shift p
+
+                %td Toggle Markdown preview
             %tbody
               %tr
                 %th
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index a2c0a858930b8bfa979640efc701bc6cdcf95cfa..d084559abc3385844d9e6393a61e002e1e05eb18 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -18,6 +18,8 @@
       = link_to 'Nav', '#nav'
     %li
       = link_to 'Buttons', '#buttons'
+    %li
+      = link_to 'Dropdowns', '#dropdowns'
     %li
       = link_to 'Panels', '#panels'
     %li
@@ -180,9 +182,9 @@
       .nav-controls
         = text_field_tag 'sample', nil, class: 'form-control'
         .dropdown
-          %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+          %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
             %span Sort by name
-            %b.caret
+            = icon('chevron-down')
           %ul.dropdown-menu
             %li
               %a Sort by date
@@ -212,6 +214,227 @@
     %button.btn.btn-danger{:type => "button"} Danger
     %button.btn.btn-link{:type => "button"} Link
 
+  %h2#dropdowns Dropdowns
+
+  .example
+    .clearfix
+      .dropdown.inline.pull-left
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown
+          = icon('chevron-down')
+        %ul.dropdown-menu
+          %li
+            %a{href: "#"}
+              Dropdown Option
+      .dropdown.inline.pull-right
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown
+          = icon('chevron-down')
+        %ul.dropdown-menu.dropdown-menu-align-right
+          %li
+            %a{href: "#"}
+              Dropdown Option
+  .example
+    %div
+      .dropdown.inline
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown
+          = icon('chevron-down')
+        %ul.dropdown-menu.dropdown-menu-selectable
+          %li
+            %a.is-active{href: "#"}
+              Dropdown Option
+  .example
+    %div
+      .dropdown.inline
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown
+          = icon('chevron-down')
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable
+          .dropdown-title
+            %span Dropdown Title
+            %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+              = icon('times')
+          .dropdown-input
+            %input.dropdown-input-field{type: "search", placeholder: "Filter results"}
+            = icon('search')
+          .dropdown-content
+            %ul
+              %li
+                %a.is-active{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li.divider
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+          .dropdown-footer
+            %strong Tip:
+            If an author is not a member of this project, you can still filter by his name while using the search field.
+      .dropdown.inline
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown loading
+          = icon('chevron-down')
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable.is-loading
+          .dropdown-title
+            %span Dropdown Title
+            %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+              = icon('times')
+          .dropdown-input
+            %input.dropdown-input-field{type: "search", placeholder: "Filter results"}
+            = icon('search')
+          .dropdown-content
+            %ul
+              %li
+                %a.is-active{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li.divider
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+              %li
+                %a{href: "#"}
+                  Dropdown Option
+          .dropdown-footer
+            %strong Tip:
+            If an author is not a member of this project, you can still filter by his name while using the search field.
+          .dropdown-loading
+            = icon('spinner spin')
+
+  .example
+    %div
+      .dropdown.inline
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown user
+          = icon('chevron-down')
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-user
+          .dropdown-title
+            %span Dropdown Title
+            %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+              = icon('times')
+          .dropdown-input
+            %input.dropdown-input-field{type: "search", placeholder: "Filter results"}
+            = icon('search')
+          .dropdown-content
+            %ul
+              %li
+                %a.dropdown-menu-user-link.is-active{href: "#"}
+                  = link_to_member_avatar(current_user, size: 30)
+                  %strong.dropdown-menu-user-full-name
+                    = current_user.name
+                  .dropdown-menu-user-username
+                    = current_user.to_reference
+
+  .example
+    %div
+      .dropdown.inline
+        %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Dropdown page 2
+          = icon('chevron-down')
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-user.dropdown-menu-paging.is-page-two
+          .dropdown-page-one
+            .dropdown-title
+              %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
+                = icon('arrow-left')
+              %span Dropdown Title
+              %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+                = icon('times')
+            .dropdown-input
+              %input.dropdown-input-field{type: "search", placeholder: "Filter results"}
+              = icon('search')
+            .dropdown-content
+              %ul
+                %li
+                  %a.dropdown-menu-user-link.is-active{href: "#"}
+                    = link_to_member_avatar(current_user, size: 30)
+                    %strong.dropdown-menu-user-full-name
+                      = current_user.name
+                    .dropdown-menu-user-username
+                      = current_user.to_reference
+          .dropdown-page-two
+            .dropdown-title
+              %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
+                = icon('arrow-left')
+              %span Create label
+              %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+                = icon('times')
+            .dropdown-input
+              %input.dropdown-input-field{type: "search", placeholder: "Name new label"}
+            .dropdown-content
+              %button.btn.btn-primary
+                Create
+
+  .example
+    %div
+      .dropdown.inline
+        %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
+          Projects
+          = icon('chevron-down')
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable
+          .dropdown-title
+            %span Go to project
+            %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+              = icon('times')
+          .dropdown-input
+            %input.dropdown-input-field{type: "search", placeholder: "Filter results"}
+            = icon('search')
+          .dropdown-content
+          .dropdown-loading
+            = icon('spinner spin')
+    :javascript
+      $('#js-project-dropdown').glDropdown({
+        data: function (term, callback) {
+          Api.projects(term, "last_activity_at", function (data) {
+            callback(data);
+          });
+        },
+        text: function (project) {
+          return project.name_with_namespace || project.name;
+        },
+        selectable: true,
+        fieldName: "author_id",
+        filterable: true,
+        search: {
+          fields: ['name_with_namespace']
+        },
+        id: function (data) {
+          return data.id;
+        },
+        isSelected: function (data) {
+          return data.id === 2;
+        }
+      })
+
+  .example
+    %div
+      = dropdown_tag("Projects", options: { title: "Go to project", filter: true, placeholder: "Filter projects" })
+
   %h2#panels Panels
 
   .row
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index e5e2a59eaedbaf3894698c387eb69283d5bb7b82..59411ae1da10f5f9fbe5d899c594967cfc4a0d23 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -9,10 +9,15 @@
 
   = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
     = link_to group_path(@group), title: 'Home' do
-      = icon('dashboard fw')
+      = icon('group fw')
       %span
         Group
   - if can?(current_user, :read_group, @group)
+    = nav_link(path: 'groups#activity') do
+      = link_to activity_group_path(@group), title: 'Activity' do
+        = icon('dashboard fw')
+        %span
+          Activity
     - if current_user
       = nav_link(controller: [:group, :milestones]) do
         = link_to group_milestones_path(@group), title: 'Milestones' do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 970da78a5c9ca59abb7eb2271d19718e6b394154..3359716202f1392f63024f38336258add5911e43 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -19,10 +19,10 @@
         %span
           Deploy Keys
     = nav_link(controller: :hooks) do
-      = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
+      = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do
         = icon('link fw')
         %span
-          Web Hooks
+          Webhooks
     = nav_link(controller: :services) do
       = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
         = icon('cogs fw')
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 9fa96084f9423c98dba50ac879f5d643ac8ce584..6efd119f26096082090e9b2a1d1111543420cdf0 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -5,114 +5,113 @@
   .alert.alert-info
     Some options are unavailable for LDAP accounts
 
-.account-page.prepend-top-default
-  .panel.panel-default.update-token
-    .panel-heading
-      Reset Private token
-    .panel-body
-      = form_for @user, url: reset_private_token_profile_path, method: :put do |f|
-        .data
-          %p
-            Your private token is used to access application resources without authentication.
-            %br
-            It can be used for atom feeds or the API.
-            %span.cred
-              Keep it secret!
-
-          %p.cgray
-            - if current_user.private_token
-              = text_field_tag "token", current_user.private_token, class: "form-control"
-            - else
-              %span You don`t have one yet. Click generate to fix it.
-
-        .form-actions
-          - if current_user.private_token
-            = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
-          - else
-            = f.submit 'Generate', class: "btn btn-default"
-
-  .panel.panel-default
-    .panel-heading
+.row.prepend-top-default
+  .col-lg-3.profile-settings-sidebar
+    %h4.prepend-top-0
+      Private Token
+    %p
+      Your private token is used to access application resources without authentication.
+  .col-lg-9
+    = form_for @user, url: reset_private_token_profile_path, method: :put, html: {class: "private-token"} do |f|
+      %p.cgray
+        - if current_user.private_token
+          = label_tag "token", "Private token", class: "label-light"
+          = text_field_tag "token", current_user.private_token, class: "form-control"
+        - else
+          %span You don`t have one yet. Click generate to fix it.
+      %p.help-block
+        It can be used for atom feeds or the API. Keep it secret!
+      .prepend-top-default
+        - if current_user.private_token
+          = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
+        - else
+          = f.submit 'Generate', class: "btn btn-default"
+%hr
+.row.prepend-top-default
+  .col-lg-3.profile-settings-sidebar
+    %h4.prepend-top-0
       Two-factor Authentication
-    .panel-body
-      - if current_user.two_factor_enabled?
-        .pull-right
-          = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
+    %p
+      Increase your account's security by enabling two-factor authentication (2FA).
+  .col-lg-9
+    %p
+      Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
+    - if !current_user.two_factor_enabled?
+      %p
+        Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
+        More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
+      .append-bottom-10
+        = link_to 'Enable two-factor authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
+    - else
+      = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-danger',
               data: { confirm: 'Are you sure?' }
-        %p.text-success
-          %strong
-            Two-factor Authentication is enabled
-        %p
-          If you lose your recovery codes you can
-          %strong
-            = succeed ',' do
-              = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
-          invalidating all previous codes.
-
-      - else
-        %p
-          Increase your account's security by enabling two-factor authentication (2FA).
-        %p
-          Each time you log in you’ll be required to provide your username and
-          password as usual, plus a randomly-generated code from your phone.
-
-        .form-actions
-          = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
-
-  - if button_based_providers.any?
-    .panel.panel-default
-      .panel-heading
+%hr
+- if button_based_providers.any?
+  .row.prepend-top-default
+    .col-lg-3.profile-settings-sidebar
+      %h4.prepend-top-0
+        Social sign-in
+      %p
+        Activate signin with one of the following services
+    .col-lg-9
+      %label.label-light
         Connected Accounts
-      .panel-body
-        .oauth-buttons.append-bottom-10
-          %p Click on icon to activate signin with one of the following services
-          - button_based_providers.each do |provider|
-            .btn-group
-              = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true"
-
-              - if auth_active?(provider)
-                = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
-                  = icon('close')
-
-  - if current_user.can_change_username?
-    .panel.panel-warning.update-username
-      .panel-heading
-        Change Username
-      .panel-body
-        = form_for @user, url: update_username_profile_path,  method: :put, remote: true do |f|
-          %p
-            Changing your username will change path to all personal projects!
-          %div
-            .input-group
-              .input-group-addon
-                = "#{root_url}u/"
-              = f.text_field :username, required: true, class: 'form-control'
-            &nbsp;
-          .loading-gif.hide
-            %p
-              = icon('spinner spin')
-              Saving new username
-          .form-actions
-            = f.submit 'Save username', class: "btn btn-warning"
+      %p Click on icon to activate signin with one of the following services
+      - button_based_providers.each do |provider|
+        .provider-btn-group
+          .provider-btn-image
+            = provider_image_tag(provider)
+          - if auth_active?(provider)
+            = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
+              Disconnect
+          - else
+            = link_to user_omniauth_authorize_path(provider), method: :post, class: "provider-btn #{'not-active' if !auth_active?(provider)}", "data-no-turbolink" => "true" do
+              Connect
+  %hr
+- if current_user.can_change_username?
+  .row.prepend-top-default
+    .col-lg-3.profile-settings-sidebar
+      %h4.prepend-top-0.change-username-title
+        Change username
+      %p
+        Changing your username will change path to all personal projects!
+    .col-lg-9
+      = form_for @user, url: update_username_profile_path, method: :put, remote: true, html: {class: "update-username"} do |f|
+        .form-group
+          = f.label :username, "Path", class: "label-light"
+          .input-group
+            .input-group-addon
+              = "#{root_url}u/"
+            = f.text_field :username, required: true, class: 'form-control'
+        .help-block
+          Current path:
+          = "#{root_url}u/#{current_user.username}"
+        .prepend-top-default
+          = f.button class: "btn btn-warning", type: "submit" do
+            = icon "spinner spin", class: "hidden loading-username"
+            Update username
+  %hr
 
-  - if signup_enabled?
-    .panel.panel-danger.remove-account
-      .panel-heading
+- if signup_enabled?
+  .row.prepend-top-default
+    .col-lg-3.profile-settings-sidebar
+      %h4.prepend-top-0.remove-account-title
         Remove account
-      .panel-body
-        - if @user.can_be_removed?
-          %p Deleting an account has the following effects:
-          %ul
-            %li All user content like authored issues, snippets, comments will be removed
-            - rp = current_user.personal_projects.count
-            - unless rp.zero?
-              %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
-          .form-actions
-            = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
-        - else
-          - if @user.solo_owned_groups.present?
-            %p
-              Your account is currently an owner in these groups:
-              %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
-            %p
-              You must transfer ownership or delete these groups before you can delete your account.
+    .col-lg-9
+      - if @user.can_be_removed?
+        %p
+          Deleting an account has the following effects:
+        %ul
+          %li All user content like authored issues, snippets, comments will be removed
+          - rp = current_user.personal_projects.count
+          - unless rp.zero?
+            %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
+        = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+      - else
+        - if @user.solo_owned_groups.present?
+          %p
+            Your account is currently an owner in these groups:
+            %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+          %p
+            You must transfer ownership or delete these groups before you can delete your account.
+.append-bottom-default
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index b2830aa08343cf0dfd40260e141b05c3f3a76227..5d342ef58e5b4ec4401153951b65f302223d37db 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -1,41 +1,41 @@
 - page_title 'Two-factor Authentication', 'Account'
 
-%h2.page-title Two-factor Authentication (2FA)
-%p
-  Download the Google Authenticator application from App Store for iOS or Google
-  Play for Android and scan this code.
-
-  More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
-
-%hr
-
-= form_tag profile_two_factor_auth_path, method: :post, class: 'form-horizontal two-factor-new' do |f|
-  - if @error
-    .alert.alert-danger
-      = @error
-  .form-group
-    .col-lg-2.col-lg-offset-2
-      = raw @qr_code
-    .col-lg-7.col-lg-offset-1.manual-instructions
-      %h3 Can't scan the code?
-
-      %p
-        To add the entry manually, provide the following details to the
-        application on your phone.
-
-      %dl
-        %dt Account
-        %dd= current_user.email
-      %dl
-        %dt Key
-        %dd= current_user.otp_secret.scan(/.{4}/).join(' ')
-      %dl
-        %dt Time based
-        %dd Yes
-  .form-group
-    = label_tag :pin_code, nil, class: "control-label"
-    .col-lg-10
-      = text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
-  .form-actions
-    = submit_tag 'Submit', class: 'btn btn-success'
-    = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch,  class: 'btn btn-cancel' if two_factor_skippable?
+.row.prepend-top-default
+  .col-lg-3
+    %h4.prepend-top-0
+      Two-factor Authentication (2FA)
+    %p
+      Increase your account's security by enabling two-factor authentication (2FA).
+  .col-lg-9
+    %p
+      Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
+    %p
+      Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
+      More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
+    .row.append-bottom-10
+      .col-md-3
+        = raw @qr_code
+      .col-md-9
+        .account-well
+          %p.prepend-top-0.append-bottom-0
+            Can't scan the code?
+          %p.prepend-top-0.append-bottom-0
+            To add the entry manually, provide the following details to the application on your phone.
+          %p.prepend-top-0.append-bottom-0
+            Account:
+            = current_user.email
+          %p.prepend-top-0.append-bottom-0
+            Key:
+            = current_user.otp_secret.scan(/.{4}/).join(' ')
+          %p.two-factor-new-manual-content
+            Time based: Yes
+    = form_tag profile_two_factor_auth_path, method: :post do |f|
+      - if @error
+        .alert.alert-danger
+          = @error
+      .form-group
+        = label_tag :pin_code, nil, class: "label-light"
+        = text_field_tag :pin_code, nil, class: "form-control", required: true
+      .prepend-top-default
+        = submit_tag 'Enable two-factor authentication', class: 'btn btn-success'
+        = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch,  class: 'btn btn-cancel' if two_factor_skippable?
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 3c11b97921f5a95ca90ceeea83ab786885f01006..18caddabd3921cbb85c2f83ee168d970a62fe534 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -6,4 +6,4 @@
     - blob = sanitize_svg(blob)
     %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
   - else
-    %img{src: namespace_project_raw_path(@project.namespace, @project, @id)}
+    %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml
index 882a4d0c5e26672dd1f439814e0fce7c5c23dd27..a21ddaf4930291d203345287c86e13eca9fd855d 100644
--- a/app/views/projects/branches/destroy.js.haml
+++ b/app/views/projects/branches/destroy.js.haml
@@ -1 +1 @@
-$('.js-totalbranch-count').html("#{@repository.branches.size}")
+$('.js-totalbranch-count').html("#{@repository.branch_count}")
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 8eec78a557c463c8637c6ac32120bb06dc85509c..be7cc0f256c1424766c978fb6b1d49f1643ddced 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -70,7 +70,7 @@
           .autoscroll-container
             %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
           .clearfix
-      .scroll-controls
+      #js-build-scroll.scroll-controls
         = link_to '#up-build-trace', class: 'btn' do
           %i.fa.fa-angle-up
         = link_to '#down-build-trace', class: 'btn' do
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 14ee2263b7d8a417ed50f2bbaf18a44c39d20704..6a60cfeff76ffea106f4cda782b26a41cbff70a3 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,4 @@
 - unless @project.empty_repo?
   - if can? current_user, :download_code, @project
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', rel: 'nofollow', title: "Download ZIP" do
+    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
       = icon('download')
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index ce60fbdf032128249361b358b92fa6119491abc9..bac9e244d363b819b3318383c7cbb60c469b08f7 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -1,11 +1,14 @@
+- commits, hidden = limited_commits(@commits)
+- commits = Commit.decorate(commits, @project)
+
 %div.panel.panel-default
   .panel-heading
     Commits (#{@commits.count})
-  - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+  - if hidden > 0
     %ul.well-list
-      - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), @project).each do |commit|
+      - commits.each do |commit|
         = render "projects/commits/inline_commit", commit: commit, project: @project
       %li.warning-row.unstyled
-        other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+        #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
   - else
-    %ul.well-list= render Commit.decorate(@commits, @project), project: @project
+    %ul.well-list= render commits, project: @project
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 6c6312280023ede5e730389b5ac775a8b3822be3..a7e3c2478c24a0b6653811f7200b933c2212803b 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,7 +1,9 @@
 - unless defined?(project)
   - project = @project
 
-- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
+- commits, hidden = limited_commits(@commits)
+
+- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
   .row.commits-row
     .col-md-2.hidden-xs.hidden-sm
       %h5.commits-row-date
@@ -13,3 +15,7 @@
       %ul.bordered-list
         = render commits, project: project
   %hr.lists-separator
+
+- if hidden > 0
+  .alert.alert-warning
+    #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 498c5e05b321edeb3e6e51d6a3d04a492dceb76a..7a5b0d993dbd36c958e89536a0590e9ffd15ae88 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -15,9 +15,9 @@
   = nav_link(html_options: {class: branches_tab_class}) do
     = link_to namespace_project_branches_path(@project.namespace, @project) do
       Branches
-      %span.badge.js-totalbranch-count= @repository.branches.size
+      %span.badge.js-totalbranch-count= @repository.branch_count
 
   = nav_link(controller: [:tags, :releases]) do
     = link_to namespace_project_tags_path(@project.namespace, @project) do
       Tags
-      %span.badge.js-totaltags-count= @repository.tags.length
+      %span.badge.js-totaltags-count= @repository.tag_count
diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml
deleted file mode 100644
index 87ac75a350fd7359245eaaf0f463207845078c71..0000000000000000000000000000000000000000
--- a/app/views/projects/go_import.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-!!! 5
-%html
-  %head
-    - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/')
-    %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"}
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index a0511819c9f956467a330b2c25002ccae0402be4..67d016bd871015f2c414e03eea5ea413c19a92f6 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,9 +1,9 @@
-- page_title "Web Hooks"
+- page_title "Webhooks"
 %h3.page-title
-  Web hooks
+  Webhooks
 
 %p.light
-  #{link_to "Web hooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be
+  #{link_to "Webhooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be
   used for binding events when something is happening within the project.
 
 %hr.clearfix
@@ -70,12 +70,12 @@
           = f.check_box :enable_ssl_verification
           %strong Enable SSL verification
   .form-actions
-    = f.submit "Add Web Hook", class: "btn btn-create"
+    = f.submit "Add Webhook", class: "btn btn-create"
 
 -if @hooks.any?
   .panel.panel-default
     .panel-heading
-      Web hooks (#{@hooks.count})
+      Webhooks (#{@hooks.count})
     %ul.well-list
       - @hooks.each do |hook|
         %li
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index eb9c225df2fbe2954a57981a651737e740a597d4..b151393abab19ac634077ce60a48f60755e100a4 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,7 +1,7 @@
 - content_for :note_actions do
   - if can?(current_user, :update_issue, @issue)
-    = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
-    = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+    = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+    = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
 
 #notes
   = render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 640a1962ffc6e941d0d91fe46242a4a7b646ae74..d9868ad1f0a5eb621a3a21146102167e2ae1aad6 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -11,7 +11,7 @@
           - elsif has_any_ci
             = icon('blank fw')
         %span.merge-request-id
-          \!#{merge_request.iid}
+          = merge_request.to_reference
         %span.merge-request-info
           %strong
             = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 617b043780708f7ca45e1672c995b502c1b1173f..0242276cd84176c78e70b08f07b3b67b5aa9d457 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -18,7 +18,7 @@
         %span.hidden-sm.hidden-md.hidden-lg
           = icon('circle-o')
 
-    %a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
+    %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
       = icon('angle-double-left')
 
     .issue-meta
@@ -71,7 +71,7 @@
       .merge-requests
         = render 'merge_requests'
 
-    .content-block
+    .content-block.content-block-small
       = render 'votes/votes_block', votable: @issue
 
     .row
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 1c7de94acfdf426840494390098c1a5c12de96c6..393998f15b9b5f94b98e81360346fec8b948084b 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,8 +1,8 @@
 - content_for :note_actions do
   - if can?(current_user, :update_merge_request, @merge_request)
     - if @merge_request.open?
-      = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+      = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
     - if @merge_request.closed?
-      = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
+      = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
 
 #notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index b262892ac659949fb08ef5fd0a669e12262b1bfc..ee5b9fd95a8824ecd5563188a6637b26255b7a13 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -68,7 +68,7 @@
 
       .tab-content
         #notes.notes.tab-pane.voting_notes
-          .content-block.oneline-block
+          .content-block.content-block-small.oneline-block
             = render 'votes/votes_block', votable: @merge_request
 
           .row
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index d24c12251f3364ef8fc4f14af5797f699886dae6..a75c0d96c5767fbcbb66a34be7657a5e11da7426 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -4,7 +4,7 @@
       = @merge_request.state_human_name
     %span.hidden-sm.hidden-md.hidden-lg
       = icon(@merge_request.state_icon_name)
-  %a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
+  %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
     = icon('angle-double-left')
   .issue-meta
     %strong.identifier
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
deleted file mode 100644
index ca51b8c745da35f6c3e39449a4e05370ef045f2c..0000000000000000000000000000000000000000
--- a/app/views/projects/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
-  %span
-    = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
-  .issue-detail
-    = link_to [@project.namespace.becomes(Namespace), @project, issue] do
-      %span.issue-number ##{issue.iid}
-    - issue.labels.each do |label|
-      = render_colored_label(label)
-    - if issue.assignee
-      = image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
deleted file mode 100644
index 6f8a341e478d7f9a9c2ec71a3c251aeadf5d11d5..0000000000000000000000000000000000000000
--- a/app/views/projects/milestones/_issues.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.panel.panel-default
-  .panel-heading
-    = title
-    .pull-right= issues.size
-  %ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
-    - issues.sort_by(&:position).each do |issue|
-      = render 'issue', issue: issue
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
deleted file mode 100644
index a1033607c5de0fb94ede75943e56b1a09b452e57..0000000000000000000000000000000000000000
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'),  class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
-  %span.str-truncated
-    = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
-      %span.cgray ##{merge_request.iid}
-    = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
-  .pull-right.assignee-icon
-    - if merge_request.assignee
-      = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/_merge_requests.html.haml b/app/views/projects/milestones/_merge_requests.html.haml
deleted file mode 100644
index 9a5a02af21511b10cf0993a2be37c4ca673f1a7f..0000000000000000000000000000000000000000
--- a/app/views/projects/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-.panel.panel-default
-  .panel-heading= title
-  %ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
-    - merge_requests.sort_by(&:position).each do |merge_request|
-      = render 'merge_request', merge_request: merge_request
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 67d95ab0364d349cec2441263ae7f81a8ad7800a..77b566db6b6003fa475edce90af2fb6d0d4fe88b 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,31 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
-  .row
-    .col-sm-6
-      %strong
-        = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
-
-    .col-sm-6
-      .pull-right.light #{milestone.percent_complete}% complete
-  .row
-    .col-sm-6
-      = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
-        = pluralize milestone.issues.count, 'Issue'
-      &middot;
-      = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
-        = pluralize milestone.merge_requests.count, 'Merge Request'
-    .col-sm-6
-      = milestone_progress_bar(milestone)
-
-  .row
-    .col-sm-6
-      = render 'shared/milestone_expired', milestone: milestone
-    .col-sm-6
-      - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
-        = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
-          = icon('pencil-square-o')
-          Edit
-        \
-        = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
-        = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
-          = icon('trash-o')
-          Delete
+= render 'shared/milestones/milestone',
+          milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
+          issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+          merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+          milestone: milestone
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 2cae1ac4e2cd59955dd3e28c031173677ec8f4cf..b4597043a27af1ec6a0fc1cc541e056e4da12886 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -42,102 +42,9 @@
           = preserve do
             = markdown @milestone.description
 
-- if @milestone.issues.any? && @milestone.can_be_closed?
+- if @milestone.complete? && @milestone.active?
   .alert.alert-success.prepend-top-default
     %span All issues for this milestone are closed. You may close milestone now.
 
-.context.prepend-top-default
-  .milestone-summary
-    %h4 Progress
-    %strong= @milestone.issues.count
-    issues:
-    %span.milestone-stat
-      %strong= @milestone.open_items_count
-      open and
-      %strong= @milestone.closed_items_count
-      closed
-    %span.milestone-stat
-      %strong== #{@milestone.percent_complete}%
-      complete
-    %span.milestone-stat
-      %span.remaining-days= milestone_remaining_days(@milestone)
-    %span.pull-right.tab-issues-buttons
-      - if can?(current_user, :create_issue, @project)
-        = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn  btn-grouped", title: "New Issue" do
-          %i.fa.fa-plus
-          New Issue
-      - if can?(current_user, :read_issue, @project)
-        = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
-    %span.pull-right.tab-merge-requests-buttons.hidden
-      - if can?(current_user, :read_merge_request, @project)
-        = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
-
-  = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
-  %li.active
-    = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
-      Issues
-      %span.badge= @issues.count
-  %li
-    = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
-      Merge Requests
-      %span.badge= @merge_requests.count
-  %li
-    = link_to '#tab-participants', 'data-toggle' => 'tab' do
-      Participants
-      %span.badge= @users.count
-  %li
-    = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
-      Labels
-      %span.badge= @labels.count
-
-.tab-content.milestone-content
-  .tab-pane.active#tab-issues
-    .row.prepend-top-default
-      .col-md-4
-        = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
-      .col-md-4
-        = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing')
-      .col-md-4
-        = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
-
-  .tab-pane#tab-merge-requests
-    .row.prepend-top-default
-      .col-md-3
-        = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
-      .col-md-3
-        = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
-      .col-md-3
-        = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
-      .col-md-3
-        .panel.panel-primary
-          .panel-heading Merged
-          %ul.well-list
-            - @merge_requests.merged.each do |merge_request|
-              = render 'merge_request', merge_request: merge_request
-
-  .tab-pane#tab-participants
-    %ul.bordered-list
-      - @users.each do |user|
-        %li
-          = link_to user, title: user.name, class: "darken" do
-            = image_tag avatar_icon(user, 32), class: "avatar s32"
-            %strong= truncate(user.name, lenght: 40)
-            %br
-            %small.cgray= user.username
-
-  .tab-pane#tab-labels
-    %ul.bordered-list.manage-labels-list
-      - @labels.each do |label|
-        %li
-          = render_colored_label(label)
-          - args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title]
-          - options = args.extract_options!
-
-          %span.issues-count
-            = link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do
-              = pluralize label.open_issues_count, 'open issue'
-          %span.issues-count
-            = link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do
-              = pluralize label.closed_issues_count, 'closed issue'
+= render 'shared/milestones/summary', milestone: @milestone, project: @project
+= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index b5f076088c7b800a250fa9a9435a3f81bcc7c5e8..13e624764d9b8b015dae67bf0e69e2e5e8dbec72 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,5 +1,5 @@
 .note-edit-form
-  = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, class: 'js-quick-submit' do |f|
+  = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note js-quick-submit' } do |f|
     = note_target_fields(note)
     = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
       = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 09740d8ea128145d2d147127e0e15ff9103e16e1..f675f092da18be57f525af8e4430c27b2bc27393 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -13,6 +13,7 @@
     .error-alert
 
   .note-form-actions.clearfix
-    = f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
+    = f.submit 'Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
     = yield(:note_actions)
-    %a.btn.btn-nr.btn-cancel.js-close-discussion-note-form Cancel
+    %a.btn.btn-cancel.js-note-discard{role: "button", data: {cancel_text: "Cancel"}}
+      Discard draft
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index ec478a5963d15d75fb75cc6f7caf9b80b891611c..4ef544136a848046bf3b72f17c4f7220e94813f6 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -6,14 +6,21 @@
     - else
       Any
     %b.caret
-  %ul.dropdown-menu
-    %li
-      = link_to search_filter_path(group_id: nil) do
-        Any
-    - current_user.authorized_groups.sort_by(&:name).each do |group|
-      %li
-        = link_to search_filter_path(group_id: group.id, project_id: nil) do
-          = group.name
+  .dropdown-menu.dropdown-select.dropdown-menu-selectable
+    .dropdown-title
+      %span Filter results by group
+      %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+        = icon('times')
+    .dropdown-content
+      %ul
+        %li
+          = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do
+            Any
+        %li.divider
+        - current_user.authorized_groups.sort_by(&:name).each do |group|
+          %li
+            = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do
+              = group.name
 
 .dropdown.inline.prepend-left-10.project-filter
   %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
@@ -23,11 +30,18 @@
     - else
       Any
     %b.caret
-  %ul.dropdown-menu
-    %li
-      = link_to search_filter_path(project_id: nil) do
-        Any
-    - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
-      %li
-        = link_to search_filter_path(project_id: project.id, group_id: nil) do
-          = project.name_with_namespace
+  .dropdown-menu.dropdown-select.dropdown-menu-selectable
+    .dropdown-title
+      %span Filter results by project
+      %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+        = icon('times')
+    .dropdown-content
+      %ul
+        %li
+          = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do
+            Any
+        %li.divider
+        - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
+          %li
+            = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do
+              = project.name_with_namespace
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index e55159d996b0ccaad858b95a827888ed5e223255..c3fbba2ba547362ca651d9df18530401a29bd8ea 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -7,22 +7,77 @@
             class: "check_all_issues left"
       .issues-other-filters
         .filter-item.inline
-          = users_select_tag(:author_id, selected: params[:author_id],
-            placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+          - if params[:author_id]
+            = hidden_field_tag(:author_id, params[:author_id])
+          = dropdown_tag("Author", options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
+            placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } })
 
         .filter-item.inline
-          = users_select_tag(:assignee_id, selected: params[:assignee_id],
-          placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
+          - if params[:assignee_id]
+            = hidden_field_tag(:assignee_id, params[:assignee_id])
+          = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+            placeholder: "Search assignee", data: { any_user: "Any Author", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } })
 
         .filter-item.inline.milestone-filter
-          = select_tag('milestone_title', projects_milestones_options,
-            class: 'select2 trigger-submit', include_blank: true,
-            data: {placeholder: 'Milestone'})
+          - if params[:milestone_title]
+            = hidden_field_tag(:milestone_title, params[:milestone_title])
+          = dropdown_tag("Milestone", options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
+            placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do
+            - if @project
+              %ul.dropdown-footer-list
+                - if can? current_user, :admin_milestone, @project
+                  %li
+                    = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
+                      Create new
+                %li
+                  = link_to namespace_project_milestones_path(@project.namespace, @project) do
+                    - if can? current_user, :admin_milestone, @project
+                      Manage milestones
+                    - else
+                      View milestones
 
         .filter-item.inline.labels-filter
-          = select_tag('label_name', projects_labels_options,
-            class: 'select2 trigger-submit', include_blank: true,
-            data: {placeholder: 'Label'})
+          - if params[:label_name]
+            = hidden_field_tag(:label_name, params[:label_name])
+          .dropdown
+            %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}}
+              %span.dropdown-toggle-text
+                Label
+              = icon('chevron-down')
+            .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+              .dropdown-page-one
+                = dropdown_title("Filter by label")
+                = dropdown_filter("Search labels")
+                = dropdown_content
+                - if @project
+                  = dropdown_footer do
+                    %ul.dropdown-footer-list
+                      - if can? current_user, :admin_label, @project
+                        %li
+                          %a.dropdown-toggle-page{href: "#"}
+                            Create new
+                      %li
+                        = link_to namespace_project_labels_path(@project.namespace, @project) do
+                          - if can? current_user, :admin_label, @project
+                            Manage labels
+                          - else
+                            View labels
+              - if can? current_user, :admin_label, @project
+                .dropdown-page-two
+                  = dropdown_title("Create new label", back: true)
+                  = dropdown_content do
+                    %input#new_label_color{type: "hidden"}
+                    %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
+                    .dropdown-label-color-preview.js-dropdown-label-color-preview
+                    .suggest-colors.suggest-colors-dropdown
+                      - suggested_colors.each do |color|
+                        = link_to '#', style: "background-color: #{color}", data: { color: color } do
+                          &nbsp
+                    %button.btn.btn-primary.js-new-label-btn{type: "button"}
+                      Create
+              = dropdown_loading
+              .dropdown-loading
+                = icon('spinner spin')
 
         .pull-right
           = render 'shared/sort_dropdown'
@@ -31,11 +86,18 @@
       .issues_bulk_update.hide
         = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post  do
           .filter-item.inline
-            = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
+            = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
+              %ul
+                %li
+                  %a{href: "#", data: {id: "reopen"}} Open
+                %li
+                  %a{href: "#", data: {id: "close"}} Closed
           .filter-item.inline
-            = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
+            = dropdown_tag("Assignee", options: { toggle_class: "js-user-search", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+              placeholder: "Search authors", data: { first_user: (current_user.username if current_user), current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
           .filter-item.inline
-            = select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
+            = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable",
+              placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :js), use_id: true } })
           = hidden_field_tag 'update[issues_ids]', []
           = hidden_field_tag :state_event, params[:state_event]
           .filter-item.inline
@@ -47,6 +109,9 @@
 
 :javascript
   new UsersSelect();
+  new LabelsSelect();
+  new MilestoneSelect();
+  new IssueStatusSelect();
   $('form.filter-form').on('submit', function (event) {
     event.preventDefault();
     Turbolinks.visit(this.action + '&' + $(this).serialize());
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 36f0637788698b89983e6fee089a84a88062ffbb..9020a1330a33bf7cdfd45e282061c45e2316f35c 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -6,7 +6,7 @@
         of
         = issuables_count(issuable)
       %span.pull-right
-        %a.gutter-toggle{href: '#'}
+        %a.gutter-toggle.js-sidebar-toggle{href: '#'}
           = sidebar_gutter_toggle_icon
       .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
         - if prev_issuable = prev_issuable_for(issuable)
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f7c6fc14adf196b0ea186f01f6312e8800bb3a2c
--- /dev/null
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -0,0 +1,25 @@
+-# @project is present when viewing Project's milestone
+- project = @project || issuable.project
+- assignee = issuable.assignee
+- issuable_type = issuable.class.table_name
+- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
+
+%li{ id: dom_id(issuable, 'sortable'),  class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
+  %span
+    - if show_project_name
+      %strong #{project.name} &middot;
+    - elsif show_full_project_name
+      %strong #{project.name_with_namespace} &middot;
+    = link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
+  %div{class: 'issuable-detail'}
+    = link_to [project.namespace.becomes(Namespace), project, issuable] do
+      %span{ class: 'issuable-number' }>= issuable.to_reference
+
+    - issuable.labels.each do |label|
+      = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
+        - render_colored_label(label)
+
+    - if assignee
+      = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
+                class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+        - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..8619939dde7993c575825c8d22704f13793d4bf3
--- /dev/null
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -0,0 +1,16 @@
+- show_counter = local_assigns.fetch(:show_counter, false)
+- primary = local_assigns.fetch(:primary, false)
+- panel_class = primary ? 'panel-primary' : 'panel-default'
+
+.panel{ class: panel_class }
+  .panel-heading
+    = title
+    - if show_counter
+      .pull-right= issuables.size
+
+  - class_prefix = dom_class(issuables).pluralize
+  %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
+    = render partial: 'shared/milestones/issuable',
+             collection: issuables.sort_by(&:position),
+             as: :issuable,
+             locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a8db7f8a556154a5e3c6757549e641f9cb68ba2d
--- /dev/null
+++ b/app/views/shared/milestones/_issues_tab.html.haml
@@ -0,0 +1,10 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+           show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+  .col-md-4
+    = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
+  .col-md-4
+    = render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
+  .col-md-4
+    = render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ba27bafd1bc0ce870843a609f4c0bb4cb003d10b
--- /dev/null
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -0,0 +1,18 @@
+%ul.bordered-list.manage-labels-list
+  - labels.each do |label|
+    - options = { milestone_title: @milestone.title, label_name: label.title }
+
+    %li
+      %span.label-row
+        = link_to milestones_label_path(options) do
+          - render_colored_label(label)
+        %span.prepend-left-10
+          = markdown(label.description, pipeline: :single_line)
+
+      .pull-right
+        %strong.issues-count
+          = link_to milestones_label_path(options.merge(state: 'opened')) do
+            - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
+        %strong.issues-count
+          = link_to milestones_label_path(options.merge(state: 'closed')) do
+            - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c29d8ee6737c1529953a566f552c77e235737c60
--- /dev/null
+++ b/app/views/shared/milestones/_merge_requests_tab.haml
@@ -0,0 +1,12 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+           show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+  .col-md-3
+    = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
+  .col-md-3
+    = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
+  .col-md-3
+    = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
+  .col-md-3
+    = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f01138af3f09c20d2eaef18d63366c44d733aa9f
--- /dev/null
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -0,0 +1,45 @@
+- dashboard = local_assigns[:dashboard]
+- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
+
+%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
+  .row
+    .col-sm-6
+      %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
+    .col-sm-6
+      .pull-right.light #{milestone.percent_complete}% complete
+  .row
+    .col-sm-6
+      = link_to pluralize(milestone.issues.size, 'Issue'), issues_path
+      &middot;
+      = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
+    .col-sm-6= milestone_progress_bar(milestone)
+  - if milestone.is_a?(GlobalMilestone)
+    .row
+      .col-sm-6
+        .expiration= render('shared/milestone_expired', milestone: milestone)
+        .projects
+          - milestone.milestones.each do |milestone|
+            = link_to milestone_path(milestone) do
+              %span.label.label-gray
+                = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+      - if @group
+        .col-sm-6
+          - if can?(current_user, :admin_milestones, @group)
+            - if milestone.closed?
+              = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+            - else
+              = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+
+  - if @project
+    .row
+      .col-sm-6= render('shared/milestone_expired', milestone: milestone)
+      .col-sm-6
+        - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
+          = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
+            = icon('pencil-square-o')
+            Edit
+          \
+          = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
+          = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
+            = icon('trash-o')
+            Delete
diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..67ae85ac276fd567bc83afbb4adcfff40e129353
--- /dev/null
+++ b/app/views/shared/milestones/_participants_tab.html.haml
@@ -0,0 +1,8 @@
+%ul.bordered-list
+  - users.each do |user|
+    %li
+      = link_to user, title: user.name, class: "darken" do
+        = image_tag avatar_icon(user, 32), class: "avatar s32"
+        %strong= truncate(user.name, lenght: 40)
+        %br
+        %small.cgray= user.username
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..59d4ae29f791f349d13e0bc8903011c5655f3636
--- /dev/null
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -0,0 +1,28 @@
+- project = local_assigns[:project]
+
+.context.prepend-top-default
+  .milestone-summary
+    %h4 Progress
+    %strong= milestone.issues.size
+    issues:
+    %span.milestone-stat
+      %strong= milestone.issues.opened.size
+      open and
+      %strong= milestone.issues.closed.size
+      closed
+    %span.milestone-stat
+      %strong== #{milestone.percent_complete}%
+      complete
+
+    %span.milestone-stat
+      %span.remaining-days= milestone_remaining_days(milestone)
+    %span.pull-right.tab-issues-buttons
+      - if project && can?(current_user, :create_issue, project)
+        = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn  btn-grouped", title: "New Issue" do
+          %i.fa.fa-plus
+          New Issue
+      = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
+    %span.pull-right.tab-merge-requests-buttons.hidden
+      = link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped"
+
+  = milestone_progress_bar(milestone)
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..57d7ee85a3b3ac27289d39c02e5b5ce21a9c3c31
--- /dev/null
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -0,0 +1,30 @@
+%ul.nav-links.no-top.no-bottom
+  %li.active
+    = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
+      Issues
+      %span.badge= milestone.issues.size
+  %li
+    = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
+      Merge Requests
+      %span.badge= milestone.merge_requests.size
+  %li
+    = link_to '#tab-participants', 'data-toggle' => 'tab' do
+      Participants
+      %span.badge= milestone.participants.count
+  %li
+    = link_to '#tab-labels', 'data-toggle' => 'tab' do
+      Labels
+      %span.badge= milestone.labels.count
+
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
+
+.tab-content.milestone-content
+  .tab-pane.active#tab-issues
+    = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+  .tab-pane#tab-merge-requests
+    = render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+  .tab-pane#tab-participants
+    = render 'shared/milestones/participants_tab', users: milestone.participants
+  .tab-pane#tab-labels
+    = render 'shared/milestones/labels_tab', labels: milestone.labels
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4cf1d948b5b952d84f08931b556433b79f0b9e4b
--- /dev/null
+++ b/app/views/shared/milestones/_top.html.haml
@@ -0,0 +1,58 @@
+- page_title milestone.title, "Milestones"
+
+- group = local_assigns[:group]
+
+.detail-page-header
+  .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
+    - if milestone.closed?
+      Closed
+    - elsif milestone.expired?
+      Expired
+    - else
+      Open
+  %span.identifier
+    Milestone #{milestone.title}
+  - if milestone.expires_at
+    %span.creator
+      &middot;
+      = milestone.expires_at
+  - if group
+    .pull-right
+      - if can?(current_user, :admin_milestones, group)
+        - if milestone.active?
+          = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+        - else
+          = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(milestone.title), pipeline: :single_line
+
+- if milestone.complete? && milestone.active?
+  .alert.alert-success.prepend-top-default
+    - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
+    %span All issues for this milestone are closed. #{close_msg}
+
+.table-holder
+  %table.table
+    %thead
+      %tr
+        %th Project
+        %th Open issues
+        %th State
+        %th Due date
+    - milestone.milestones.each do |ms|
+      %tr
+        %td
+          - project_name = group ? ms.project.name : ms.project.name_with_namespace
+          = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
+        %td
+          = ms.issues.opened.count
+        %td
+          - if ms.closed?
+            Closed
+          - else
+            Open
+        %td
+          = ms.expires_at
+
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e7e04621ff4437d59de82b5e4fe27915c2c7e5a5
--- /dev/null
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -0,0 +1,22 @@
+- @sort ||= sort_value_recently_updated
+- archived = params[:archived]
+.dropdown.inline
+  %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+    %span.light
+      = projects_sort_options_hash[@sort]
+    %b.caret
+  %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
+    %li.dropdown-header
+      Sort by
+    - projects_sort_options_hash.each do |value, title|
+      %li
+        = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+          = title
+
+    %li.divider
+    %li
+      = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
+        Hide archived projects
+    %li
+      = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
+        Show archived projects
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 99e48e86e389ae7ef5f8451aa0b4e7aa5ef2a2b5..97cfb76cdb0da0337d2b6e8d79d4439531d18b9b 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -28,6 +28,9 @@
           = project.name
 
     .controls
+      - if project.main_language
+        %span
+          = project.main_language
       - if ci_commit
         %span
           = render_ci_status(ci_commit)
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index e0e41fc4bea4e6ff2ba24b9cabf3efc750d6be69..773ce8ac240f8bad822d8fc6a74f1b133cc2b18a 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -1,5 +1,7 @@
 - unless @snippet.content.empty?
   - if markup?(@snippet.file_name)
+    %textarea.markdown-snippet-copy.blob-content{data: {blob_id: @snippet.id}}
+      = @snippet.data
     .file-content.wiki
       = render_markup(@snippet.file_name, @snippet.data)
   - else
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 176fd29cb57ae3489363b7c40610ea5568e148d9..20d2d5f317bdfd2eed538e681d905b88056b80a2 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,14 +1,17 @@
 .awards.votes-block
   - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
-    .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
+    %button.btn.award-control.js-emoji-btn.has_tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
       = emoji_icon(emoji)
-      .counter
+      %span.award-control-text.js-counter
         = notes.count
 
   - if current_user
-    .awards-controls
-      %a.add-award{"href" => "#"}
-        = icon('smile-o')
+    %div.award-menu-holder.js-award-holder
+      %a.btn.award-control.js-add-award{"href" => "#"}
+        = icon('smile-o', {class: "award-control-icon"})
+        = icon('spinner spin', {class: "award-control-icon award-control-icon-loading"})
+        %span.award-control-text
+          Add
 
 - if current_user
   :javascript
@@ -23,17 +26,3 @@
       noteable_id,
       aliases
     );
-
-    $(".awards").on("click", ".emoji-menu-content li", function(e) {
-      var emoji = $(this).find(".emoji-icon").data("emoji");
-      awards_handler.addAward(emoji);
-    });
-
-    $(".awards").on("click", ".award", function(e) {
-      var emoji = $(this).find(".icon").data("emoji");
-      awards_handler.addAward(emoji);
-    });
-
-    $(".award").tooltip();
-
-    $(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false});
diff --git a/config/application.rb b/config/application.rb
index 7fd75ebe69e89c6df8a4bfb1a46ca4aafcf106bc..2b103c4592db16102d46a86d586b246de753c473 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -4,6 +4,7 @@
 require 'devise'
 I18n.config.enforce_available_locales = false
 Bundler.require(:default, Rails.env)
+require_relative '../lib/gitlab/redis_config'
 
 module Gitlab
   REDIS_CACHE_NAMESPACE = 'cache:gitlab'
@@ -33,7 +34,7 @@ class Application < Rails::Application
     config.encoding = "utf-8"
 
     # Configure sensitive parameters which will be filtered from the log file.
-    config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables)
+    config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url)
 
     # Enable escaping HTML in JSON.
     config.active_support.escape_html_entities_in_json = true
@@ -67,22 +68,7 @@ class Application < Rails::Application
       end
     end
 
-    # Use Redis caching across all environments
-    redis_config_file = Rails.root.join('config', 'resque.yml')
-
-    redis_url_string = if File.exists?(redis_config_file)
-                         YAML.load_file(redis_config_file)[Rails.env]
-                       else
-                         "redis://localhost:6379"
-                       end
-
-    # Redis::Store does not handle Unix sockets well, so let's do it for them
-    redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(redis_url_string)
-    redis_uri = URI.parse(redis_url_string)
-    if redis_uri.scheme == 'unix'
-      redis_config_hash[:path] = redis_uri.path
-    end
-
+    redis_config_hash = Gitlab::RedisConfig.redis_store_options
     redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE
     redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
     config.cache_store = :redis_store, redis_config_hash
diff --git a/config/environments/test.rb b/config/environments/test.rb
index d6842affa6c19ba06694766f7720e6590e50adcc..f96ac6f97530378c206d2cebcc0842f29c797113 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -7,8 +7,6 @@
   # and recreated between test runs. Don't rely on the data there!
   config.cache_classes = false
 
-  config.cache_store = :null_store
-
   # Configure static asset server for tests with Cache-Control for performance
   config.serve_static_files = true
   config.static_cache_control = "public, max-age=3600"
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index d82cfb3ec0c787022f6b61fcab2f5104f0fee8f0..31dceaebcadb5b2afa9fffba38ebe7ee556f7bd3 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -203,11 +203,11 @@
   # If you want to use other strategies, that are not supported by Devise, or
   # change the failure app, you can configure them inside the config.warden block.
   #
-  # config.warden do |manager|
-  #   manager.failure_app   = AnotherApp
-  #   manager.intercept_401 = false
-  #   manager.default_strategies(scope: :user).unshift :some_external_strategy
-  # end
+  config.warden do |manager|
+    manager.failure_app = Gitlab::DeviseFailure
+    # manager.intercept_401 = false
+    # manager.default_strategies(scope: :user).unshift :some_external_strategy
+  end
 
   if Gitlab::LDAP::Config.enabled?
     Gitlab.config.ldap.servers.values.each do |server|
diff --git a/config/initializers/go_get.rb b/config/initializers/go_get.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e7896b4900ba256db3fec5c9a4a75ba962161a9
--- /dev/null
+++ b/config/initializers/go_get.rb
@@ -0,0 +1 @@
+Rails.application.config.middleware.use(Gitlab::Middleware::Go)
diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb
new file mode 100644
index 0000000000000000000000000000000000000000..835f3ec557446aacaed23f2725e9c91323f02d57
--- /dev/null
+++ b/config/initializers/mysql_ignore_postgresql_options.rb
@@ -0,0 +1,49 @@
+# This patches ActiveRecord so indexes created using the MySQL adapter ignore
+# any PostgreSQL specific options (e.g. `using: :gin`).
+#
+# These patches do the following for MySQL:
+#
+# 1. Indexes created using the :opclasses option are ignored (as they serve no
+#    purpose on MySQL).
+# 2. When creating an index with `using: :gin` the `using` option is discarded
+#    as :gin is not a valid value for MySQL.
+# 3. The `:opclasses` option is stripped from add_index_options in case it's
+#    used anywhere other than in the add_index methods.
+
+if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
+  module ActiveRecord
+    module ConnectionAdapters
+      class Mysql2Adapter < AbstractMysqlAdapter
+        alias_method :__gitlab_add_index, :add_index
+        alias_method :__gitlab_add_index_sql, :add_index_sql
+        alias_method :__gitlab_add_index_options, :add_index_options
+
+        def add_index(table_name, column_name, options = {})
+          unless options[:opclasses]
+            __gitlab_add_index(table_name, column_name, options)
+          end
+        end
+
+        def add_index_sql(table_name, column_name, options = {})
+          unless options[:opclasses]
+            __gitlab_add_index_sql(table_name, column_name, options)
+          end
+        end
+
+        def add_index_options(table_name, column_name, options = {})
+          if options[:using] and options[:using] == :gin
+            options = options.dup
+            options.delete(:using)
+          end
+
+          if options[:opclasses]
+            options = options.dup
+            options.delete(:opclasses)
+          end
+
+          __gitlab_add_index_options(table_name, column_name, options)
+        end
+      end
+    end
+  end
+end
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
new file mode 100644
index 0000000000000000000000000000000000000000..820cc89ef574f3e02e5e0e31a99c6b93914e247d
--- /dev/null
+++ b/config/initializers/postgresql_opclasses_support.rb
@@ -0,0 +1,188 @@
+# rubocop:disable all
+
+# These changes add support for PostgreSQL operator classes when creating
+# indexes and dumping/loading schemas. Taken from Rails pull request
+# https://github.com/rails/rails/pull/19090.
+#
+# License:
+#
+# Copyright (c) 2004-2016 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+require 'date'
+require 'set'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+# As the Struct definition is changed in this PR/patch we have to first remove
+# the existing one.
+ActiveRecord::ConnectionAdapters.send(:remove_const, :IndexDefinition)
+
+module ActiveRecord
+  module ConnectionAdapters #:nodoc:
+    # Abstract representation of an index definition on a table. Instances of
+    # this type are typically created and returned by methods in database
+    # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
+    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses) #:nodoc:
+    end
+  end
+end
+
+
+module ActiveRecord
+  module ConnectionAdapters # :nodoc:
+    module SchemaStatements
+      def add_index_options(table_name, column_name, options = {}) #:nodoc:
+        column_names = Array(column_name)
+        index_name   = index_name(table_name, column: column_names)
+
+        options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclasses)
+
+        index_type = options[:unique] ? "UNIQUE" : ""
+        index_type = options[:type].to_s if options.key?(:type)
+        index_name = options[:name].to_s if options.key?(:name)
+        max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+
+        if options.key?(:algorithm)
+          algorithm = index_algorithms.fetch(options[:algorithm]) {
+            raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+          }
+        end
+
+        using = "USING #{options[:using]}" if options[:using].present?
+
+        if supports_partial_index?
+          index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+        end
+
+        if index_name.length > max_index_length
+          raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
+        end
+        if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+          raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
+        end
+        index_columns = quoted_columns_for_index(column_names, options).join(", ")
+
+        [index_name, index_type, index_columns, index_options, algorithm, using]
+      end
+    end
+  end
+end
+
+module ActiveRecord
+  module ConnectionAdapters
+    module PostgreSQL
+      module SchemaStatements
+        # Returns an array of indexes for the given table.
+        def indexes(table_name, name = nil)
+           result = query(<<-SQL, 'SCHEMA')
+             SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+             FROM pg_class t
+             INNER JOIN pg_index d ON t.oid = d.indrelid
+             INNER JOIN pg_class i ON d.indexrelid = i.oid
+             WHERE i.relkind = 'i'
+               AND d.indisprimary = 'f'
+               AND t.relname = '#{table_name}'
+               AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
+            ORDER BY i.relname
+          SQL
+
+          result.map do |row|
+            index_name = row[0]
+            unique = row[1] == 't'
+            indkey = row[2].split(" ")
+            inddef = row[3]
+            oid = row[4]
+
+            columns = Hash[query(<<-SQL, "SCHEMA")]
+            SELECT a.attnum, a.attname
+            FROM pg_attribute a
+            WHERE a.attrelid = #{oid}
+            AND a.attnum IN (#{indkey.join(",")})
+            SQL
+
+            column_names = columns.values_at(*indkey).compact
+
+            unless column_names.empty?
+              # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+              desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+              orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+              where = inddef.scan(/WHERE (.+)$/).flatten[0]
+              using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
+              opclasses = Hash[inddef.scan(/\((.+)\)$/).flatten[0].split(',').map do |column_and_opclass|
+                                 column, opclass = column_and_opclass.split(' ').map(&:strip)
+                                 [column, opclass] if opclass
+                               end.compact]
+
+              IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using, opclasses)
+            end
+          end.compact
+        end
+
+        def add_index(table_name, column_name, options = {}) #:nodoc:
+          index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+          execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}"
+        end
+
+        protected
+
+          def quoted_columns_for_index(column_names, options = {})
+            column_opclasses = options[:opclasses] || {}
+            column_names.map {|name| "#{quote_column_name(name)} #{column_opclasses[name]}"}
+          end
+      end
+    end
+  end
+end
+
+module ActiveRecord
+  class SchemaDumper
+    private
+
+      def indexes(table, stream)
+        if (indexes = @connection.indexes(table)).any?
+          add_index_statements = indexes.map do |index|
+            statement_parts = [
+              "add_index #{remove_prefix_and_suffix(index.table).inspect}",
+              index.columns.inspect,
+              "name: #{index.name.inspect}",
+            ]
+            statement_parts << 'unique: true' if index.unique
+
+            index_lengths = (index.lengths || []).compact
+            statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
+
+            index_orders = index.orders || {}
+            statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
+            statement_parts << "where: #{index.where.inspect}" if index.where
+            statement_parts << "using: #{index.using.inspect}" if index.using
+            statement_parts << "type: #{index.type.inspect}" if index.type
+            statement_parts << "opclasses: #{index.opclasses}" if index.opclasses.present?
+
+            "  #{statement_parts.join(', ')}"
+          end
+
+          stream.puts add_index_statements.sort.join("\n")
+          stream.puts
+        end
+      end
+  end
+end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 0fc725842ba16fd738338faa4443b257362061ee..3da5d46be92a4796d0ad1b9e5dc415dfdb81bf57 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -13,9 +13,12 @@
 if Rails.env.test?
   Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
 else
+  redis_config = Gitlab::RedisConfig.redis_store_options
+  redis_config[:namespace] = 'session:gitlab'
+  
   Gitlab::Application.config.session_store(
     :redis_store, # Using the cookie_store would enable session replay attacks.
-    servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
+    servers: redis_config,
     key: '_gitlab_session',
     secure: Gitlab.config.gitlab.https,
     httponly: true,
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index dcf6ce74d96bbd1dc546a6d0934e5a611d7b8cd7..cc83137745ad220d398fa02b498d27245827b1b2 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,16 +1,9 @@
-# Custom Redis configuration
-config_file = Rails.root.join('config', 'resque.yml')
-
-resque_url = if File.exists?(config_file)
-               YAML.load_file(config_file)[Rails.env]
-             else
-               "redis://localhost:6379"
-             end
+SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab'
 
 Sidekiq.configure_server do |config|
   config.redis = {
-    url: resque_url,
-    namespace: 'resque:gitlab'
+    url: Gitlab::RedisConfig.url,
+    namespace: SIDEKIQ_REDIS_NAMESPACE
   }
 
   config.server_middleware do |chain|
@@ -36,7 +29,7 @@
 
 Sidekiq.configure_client do |config|
   config.redis = {
-    url: resque_url,
-    namespace: 'resque:gitlab'
+    url: Gitlab::RedisConfig.url,
+    namespace: SIDEKIQ_REDIS_NAMESPACE
   }
 end
diff --git a/config/mail_room.yml b/config/mail_room.yml
index f266a70ee0df48be107c8ae90bb7054ec899e56a..aed55f74eab5e5acb1e19cc5e1489dfc559b1644 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -2,6 +2,7 @@
 <%
 require "yaml"
 require "json"
+require_relative "lib/gitlab/redis_config"
 
 rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
 
@@ -17,13 +18,7 @@ if File.exists?(config_file)
   config['mailbox']    = "inbox"  if config['mailbox'].nil?
 
   if config['enabled'] && config['address'] && config['address'].include?('%{key}')
-    redis_config_file = "config/resque.yml"
-    redis_url =
-      if File.exists?(redis_config_file)
-        YAML.load_file(redis_config_file)[rails_env]
-      else
-        "redis://localhost:6379"
-      end
+    redis_url = Gitlab::RedisConfig.new(rails_env).url
     %>
     -
       :host: <%= config['host'].to_json %>
diff --git a/config/routes.rb b/config/routes.rb
index a918b5bd3f0b65a0dc7f728d3ef6bc0cbe4dfe47..869fca03ec42fb3c611846406e7d5168714236b4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -382,7 +382,7 @@
       get :issues
       get :merge_requests
       get :projects
-      get :events
+      get :activity
     end
 
     scope module: :groups do
diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
new file mode 100644
index 0000000000000000000000000000000000000000..003169c13c6e2d2a94eee810cf4c56aa049b6e7e
--- /dev/null
+++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
@@ -0,0 +1,53 @@
+class AddTrigramIndexesForSearching < ActiveRecord::Migration
+  disable_ddl_transaction!
+
+  def up
+    return unless Gitlab::Database.postgresql?
+
+    unless trigrams_enabled?
+      raise 'You must enable the pg_trgm extension. You can do so by running ' \
+        '"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \
+        'done for every GitLab database. For more information see ' \
+        'http://www.postgresql.org/docs/current/static/sql-createextension.html'
+    end
+
+    # trigram indexes are case-insensitive so we can just index the column
+    # instead of indexing lower(column)
+    to_index.each do |table, columns|
+      columns.each do |column|
+        execute "CREATE INDEX CONCURRENTLY index_#{table}_on_#{column}_trigram ON #{table} USING gin(#{column} gin_trgm_ops);"
+      end
+    end
+  end
+
+  def down
+    return unless Gitlab::Database.postgresql?
+
+    to_index.each do |table, columns|
+      columns.each do |column|
+        remove_index table, name: "index_#{table}_on_#{column}_trigram"
+      end
+    end
+  end
+
+  def trigrams_enabled?
+    res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;")
+    row = res.first
+
+    row && row['enabled'] == 't' ? true : false
+  end
+
+  def to_index
+    {
+      ci_runners:     [:token, :description],
+      issues:         [:title, :description],
+      merge_requests: [:title, :description],
+      milestones:     [:title, :description],
+      namespaces:     [:name, :path],
+      notes:          [:note],
+      projects:       [:name, :path, :description],
+      snippets:       [:title, :file_name],
+      users:          [:username, :name, :email]
+    }
+  end
+end
diff --git a/db/migrate/20160229193553_add_main_language_to_repository.rb b/db/migrate/20160229193553_add_main_language_to_repository.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b5446c6a4472c43f5eaf69a83487cc0a95d9edec
--- /dev/null
+++ b/db/migrate/20160229193553_add_main_language_to_repository.rb
@@ -0,0 +1,5 @@
+class AddMainLanguageToRepository < ActiveRecord::Migration
+  def change
+    add_column :projects, :main_language, :string
+  end
+end
diff --git a/db/migrate/20160305220806_remove_expires_at_from_snippets.rb b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc12b5b09e69fb2e893941e4c14c122f9a7501bd
--- /dev/null
+++ b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
@@ -0,0 +1,5 @@
+class RemoveExpiresAtFromSnippets < ActiveRecord::Migration
+  def change
+    remove_column :snippets, :expires_at, :datetime
+  end
+end
diff --git a/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49e787d9a9a598ecb877d5e6e0fb97fadcc0c3e8
--- /dev/null
+++ b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb
@@ -0,0 +1,9 @@
+class DisallowBlankLineCodeOnNote < ActiveRecord::Migration
+  def up
+    execute("UPDATE notes SET line_code = NULL WHERE line_code = ''")
+  end
+
+  def down
+    # noop
+  end
+end
diff --git a/db/migrate/20160309140734_fix_todos.rb b/db/migrate/20160309140734_fix_todos.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ebe0fc82305d87de35e0cc7bfabc1ba74f9695db
--- /dev/null
+++ b/db/migrate/20160309140734_fix_todos.rb
@@ -0,0 +1,16 @@
+class FixTodos < ActiveRecord::Migration
+ def up
+    execute <<-SQL
+      DELETE FROM todos
+      WHERE todos.target_type IN ('Commit', 'ProjectSnippet')
+         OR NOT EXISTS (
+              SELECT *
+              FROM projects
+              WHERE projects.id = todos.project_id
+            )
+    SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 71d9257a31e3fd5e9b880b2248e5cf7c34303eaa..3ac6203632d1bcaa4d5e5b52e316ce6350de25d9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,10 +11,11 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160222153918) do
+ActiveRecord::Schema.define(version: 20160309140734) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
+  enable_extension "pg_trgm"
 
   create_table "abuse_reports", force: :cascade do |t|
     t.integer  "reporter_id"
@@ -258,6 +259,9 @@
     t.string   "architecture"
   end
 
+  add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+  add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
+
   create_table "ci_services", force: :cascade do |t|
     t.string   "type"
     t.string   "title"
@@ -417,11 +421,13 @@
   add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
   add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
   add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
+  add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
   add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
   add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
   add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
   add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
   add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
+  add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
   create_table "keys", force: :cascade do |t|
     t.integer  "user_id"
@@ -543,12 +549,14 @@
   add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
   add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
   add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
+  add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
   add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
   add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
   add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree
   add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
   add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
   add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
+  add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
   create_table "milestones", force: :cascade do |t|
     t.string   "title",       null: false
@@ -562,10 +570,12 @@
   end
 
   add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree
+  add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
   add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
   add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
   add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
   add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree
+  add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
   create_table "namespaces", force: :cascade do |t|
     t.string   "name",                     null: false
@@ -580,8 +590,10 @@
 
   add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
   add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
+  add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
   add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
   add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
+  add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
   add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
 
   create_table "notes", force: :cascade do |t|
@@ -607,6 +619,7 @@
   add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
   add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
   add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
+  add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
   add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
   add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
   add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
@@ -697,6 +710,7 @@
     t.integer  "build_timeout",          default: 3600,     null: false
     t.boolean  "pending_delete",         default: false
     t.boolean  "public_builds",          default: true,     null: false
+    t.string   "main_language"
   end
 
   add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -704,9 +718,12 @@
   add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
   add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
   add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
+  add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
   add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
+  add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
   add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
   add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
+  add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
   add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
   add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
   add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
@@ -777,7 +794,6 @@
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "file_name"
-    t.datetime "expires_at"
     t.string   "type"
     t.integer  "visibility_level", default: 0, null: false
   end
@@ -785,8 +801,9 @@
   add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
   add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
   add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
-  add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
+  add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"}
   add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
+  add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
   add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
   add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
 
@@ -920,9 +937,12 @@
   add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree
   add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
   add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+  add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
   add_index "users", ["name"], name: "index_users_on_name", using: :btree
+  add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
   add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
   add_index "users", ["username"], name: "index_users_on_username", using: :btree
+  add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
 
   create_table "users_star_projects", force: :cascade do |t|
     t.integer  "project_id", null: false
diff --git a/doc/README.md b/doc/README.md
index be6c5f96ea15d8583d8856fe5bf567e8297d29d2..0ca30e4e0f230178d2d15016597036c4097c81fa 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -13,7 +13,7 @@
 - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
 - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
 - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
-- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
+- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
 - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
 
 ## CI User documentation
@@ -54,7 +54,7 @@ be linked with your base image. Below is a list of examples you may use:
 
 ## Administrator documentation
 
-- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
 - [Install](install/README.md) Requirements, directory structures and installation from source.
 - [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components
 - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
@@ -63,7 +63,7 @@ be linked with your base image. Below is a list of examples you may use:
 - [Log system](logs/logs.md) Log system.
 - [Environment Variables](administration/environment_variables.md) to configure GitLab.
 - [Operations](operations/README.md) Keeping GitLab up and running
-- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
 - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
 - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
 - [Update](update/README.md) Update guides to upgrade your installation.
diff --git a/doc/api/commits.md b/doc/api/commits.md
index e4d436b8e52179882656f2f5a9fbc8642be5858f..6341440c58b9017eab77981d9cdc3e8f5d04f45e 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -213,8 +213,7 @@ Example response:
 
 ## Commit status
 
-Since GitLab 8.1, this is the new commit status API. The documentation in
-[ci/api/commits](../ci/api/commits.md) is deprecated.
+Since GitLab 8.1, this is the new commit status API.
 
 ### Get the status of a commit
 
diff --git a/doc/api/notes.md b/doc/api/notes.md
index d4d63e825abe801e09dd17e282a22f6110a41790..85d4f0bafa27a5eab0a0322847536146fe1a44d9 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -145,7 +145,6 @@ Parameters:
     "state": "active",
     "created_at": "2013-09-30T13:46:01Z"
   },
-  "expires_at": null,
   "updated_at": "2013-10-02T07:34:20Z",
   "created_at": "2013-10-02T07:34:20Z"
 }
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index a7acf37b5bcc05b9e81fae2e1849b7d39607e4cf..fb802102e3a56f5190fe2ed54998f5e11aba89fd 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -51,7 +51,6 @@ Parameters:
     "state": "active",
     "created_at": "2012-05-23T08:00:58Z"
   },
-  "expires_at": null,
   "updated_at": "2012-06-28T10:52:04Z",
   "created_at": "2012-06-28T10:52:04Z"
 }
diff --git a/doc/api/users.md b/doc/api/users.md
index b7fc903825ea2faedcc5aa77e47b41cacceb8b53..82c57a2fd43784a2ad95103ae4c3d515e4b89e96 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -151,6 +151,8 @@ Parameters:
   "name": "John Smith",
   "state": "active",
   "created_at": "2012-05-23T08:00:58Z",
+  "confirmed_at": "2012-05-23T08:00:58Z",
+  "last_sign_in_at": "2015-03-23T08:00:58Z",
   "bio": null,
   "skype": "",
   "linkedin": "",
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 2120b5b28509a53a089d4074f1fab363fdf0e909..4abc45bf9bbd24b132e25d26992a55e3ba55a6bb 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -3,6 +3,7 @@
 ### CI User documentation
 
 - [Get started with GitLab CI](quick_start/README.md)
+- [CI examples for various languages](examples/README.md)
 - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
 - [Learn how `.gitlab-ci.yml` works](yaml/README.md)
 - [Configure a Runner, the application that runs your builds](runners/README.md)
@@ -14,24 +15,4 @@
 - [Build artifacts](build_artifacts/README.md)
 - [User permissions](permissions/README.md)
 - [API](api/README.md)
-
-### CI Examples
-
-- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- [Test your PHP applications](examples/php.md)
-- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
-- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
-- [Test Clojure applications](examples/test-clojure-application.md)
-- [Using `dpl` as deployment tool](deployment/README.md)
-- Help your favorite programming language and GitLab by sending a merge request
-  with a guide for that language.
-
-### CI Services
-
-GitLab CI uses the `services` keyword to define what docker containers should
-be linked with your base image. Below is a list of examples you may use:
-
-- [Using MySQL](services/mysql.md)
-- [Using PostgreSQL](services/postgres.md)
-- [Using Redis](services/redis.md)
-- [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
+- [CI services (linked docker containers)](services/README.md)
diff --git a/doc/ci/api/README.md b/doc/ci/api/README.md
index cf9710ede577759608e4bbb28dfa802d4598e283..aea808007fcb21b7b2fb24fec13e8befca877b84 100644
--- a/doc/ci/api/README.md
+++ b/doc/ci/api/README.md
@@ -1,86 +1,22 @@
 # GitLab CI API
 
-## Resources
-
-- [Projects](projects.md)
-- [Runners](runners.md)
-- [Commits](commits.md)
-- [Builds](builds.md)
-
-
-## Authentication
-
-GitLab CI API uses different types of authentication depends on what API you use.
-Each API document has section with information about authentication you need to use.
-
-GitLab CI API has 4 authentication methods:
-
-* GitLab user token & GitLab url
-* GitLab CI project token
-* GitLab CI runners registration token
-* GitLab CI runner token
-
-
-### Authentication #1: GitLab user token & GitLab url
-
-Authentication is done by
-sending the `private-token` of a valid user and the `url` of an
-authorized GitLab instance via a query string along with the API
-request:
-
-    GET http://gitlab.example.com/ci/api/v1/projects?private_token=QVy1PB7sTxfy4pqfZM1U&url=http://demo.gitlab.com/
+## Purpose
 
-If preferred, you may instead send the `private-token` as a header in
-your request:
+Main purpose of GitLab CI API is to provide necessary data and context for
+GitLab CI Runners.
 
-    curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://gitlab.example.com/ci/api/v1/projects?url=http://demo.gitlab.com/"
+For consumer API take a look at this [documentation](../../api/README.md) where
+you will find all relevant information.
 
+## API Prefix
 
-### Authentication #2: GitLab CI project token
+Current CI API prefix is `/ci/api/v1`.
 
-Each project in GitLab CI has it own token. 
-It can be used to get project commits and builds information.
-You can use project token only for certain project.
+You need to prepend this prefix to all examples in this documentation, like:
 
-### Authentication #3: GitLab CI runners registration token
+    GET /ci/api/v1/builds/:id/artifacts
 
-This token is not persisted and is generated on each application start.
-It can be used only for registering new runners in system. You can find it on 
-GitLab CI Runners web page https://gitlab-ci.example.com/admin/runners
-
-### Authentication #4: GitLab CI runner token
-
-Every GitLab CI runner has it own token that allow it to receive and update 
-GitLab CI builds. This token exists of internal purposes and should be used only 
-by runners
-
-## JSON
-
-All API requests are serialized using JSON.  You don't need to specify
-`.json` at the end of API URL.
-
-## Status codes
-
-The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
-
-API request types:
-
-- `GET` requests access one or more resources and return the result as JSON
-- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
-- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
-- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
-
-The following list shows the possible return codes for API requests.
-
-Return values:
+## Resources
 
-- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
-- `201 Created` - The `POST` request was successful and the resource is returned as JSON
-- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
-- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
-- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
-- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
-- `405 Method Not Allowed` - The request is not supported
-- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
-- `422 Unprocessable` - The entity could not be processed
-- `500 Server Error` - While handling the request something went wrong on the server side
+- [Builds](builds.md)
+- [Runners](runners.md)
diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md
index 018ca22dbbd749fe3d2f386fee98f45df7722d90..d100e2611789891d3a89a7c8ff495e056255484d 100644
--- a/doc/ci/api/builds.md
+++ b/doc/ci/api/builds.md
@@ -1,85 +1,73 @@
 # Builds API
 
-This API used by runners to receive and update builds.
+API used by runners to receive and update builds.
 
-__Authentication is done by runner token__
+_**Note:** This API is intended to be used only by Runners as their own
+communication channel. For the consumer API see the
+[Builds API](../../api/builds.md)._
+
+## Authentication
+
+This API uses two types of authentication:
+
+1.   Unique runner's token
+
+     Token assigned to runner after it has been registered.
+
+2.   Using build authorization token
+
+     This is project's CI token that can be found in Continuous Integration
+     project settings.
+
+     Build authorization token can be passed as a parameter or a value of
+     `BUILD-TOKEN` header. This method are interchangeable.
 
 ## Builds
 
 ### Runs oldest pending build by runner
 
-    POST /ci/builds/register
+    POST /ci/api/v1/builds/register
 
 Parameters:
 
-  * `token` (required) - The unique token of runner
-
-Returns:
-
-```json
-{
-  "id": 48584,
-  "ref": "0.1.1",
-  "tag": true,
-  "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
-  "status": "success",
-  "name": "pages",
-  "token": "9dd60b4f1a439d1765357446c1084c",
-  "stage": "test",
-  "project_id": 479,
-  "project_name": "test",
-  "commands": "echo commands",
-  "repo_url": "http://gitlab-ci-token:token@gitlab.example/group/test.git",
-  "before_sha": "0000000000000000000000000000000000000000",
-  "allow_git_fetch": false,
-  "options": {
-    "image": "docker:image",
-    "artifacts": {
-      "paths": [
-        "public"
-      ]
-    },
-    "cache": {
-      "paths": [
-        "vendor"
-      ]
-    }
-  },
-  "timeout": 3600,
-  "variables": [
-    {
-      "key": "CI_BUILD_TAG",
-      "value": "0.1.1",
-      "public": true
-    }
-  ],
-  "depends_on_builds": [
-    {
-      "id": 48584,
-      "ref": "0.1.1",
-      "tag": true,
-      "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
-      "status": "success",
-      "name": "build",
-      "token": "9dd60b4f1a439d1765357446c1084c",
-      "stage": "build",
-      "project_id": 479,
-      "project_name": "test",
-      "artifacts_file": {
-        "filename": "artifacts.zip",
-        "size": 0
-      }
-    }
-  ]
-}
-```
+  * `token` (required) - Unique runner token
+
 
 ### Update details of an existing build
 
-    PUT /ci/builds/:id
+    PUT /ci/api/v1/builds/:id
 
 Parameters:
 
   * `id` (required) - The ID of a project
+  * `token` (required) - Unique runner token
   * `state` (optional) - The state of a build
   * `trace` (optional) - The trace of a build
+
+### Upload artifacts to build
+
+    POST /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+  * `id` (required) - The ID of a build
+  * `token` (required) - The build authorization token
+  * `file` (required) - Artifacts file
+
+### Download the artifacts file from build
+
+    GET /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+  * `id` (required) - The ID of a build
+  * `token` (required) - The build authorization token
+
+### Remove the artifacts file from build
+
+    DELETE /ci/api/v1/builds/:id/artifacts
+
+Parameters:
+
+  * ` id` (required) - The ID of a build
+  * `token` (required) - The build authorization token
diff --git a/doc/ci/api/commits.md b/doc/ci/api/commits.md
deleted file mode 100644
index 871de7abcce18ccd03451140cca3b49c40aa08c5..0000000000000000000000000000000000000000
--- a/doc/ci/api/commits.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Commits API
-
-**DEPRECATED**
-
-Since GitLab 8.1, there is a new commit status API. Please see the [revised
-documentation](../../api/commits.md#commit-status).
-
----
-
-__Authentication is done by GitLab CI project token__
-
-## Commits
-
-### Retrieve all commits per project
-
-Get list of commits per project
-
-    GET /ci/commits
-
-Parameters:
-
-  * `project_id` (required) - The ID of a project
-  * `project_token` (requires) - Project token
-  * `page` (optional)
-  * `per_page` (optional) - items per request (default is 20)
-
-Returns:
-
-```json
-[{
-  "id": 3,
-  "ref": "master",
-  "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
-  "project_id": 2,
-  "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
-  "created_at": "2014-11-05T09:46:35.247Z",
-  "status": "success",
-  "finished_at": "2014-11-05T09:46:44.254Z",
-  "duration": 5.062692165374756,
-  "git_commit_message": "wow\n",
-  "git_author_name": "Administrator",
-  "git_author_email": "admin@example.com",
-  "builds": [{
-    "id": 7,
-    "project_id": 2,
-    "ref": "master",
-    "status": "success",
-    "finished_at": "2014-11-05T09:46:44.254Z",
-    "created_at": "2014-11-05T09:46:35.259Z",
-    "updated_at": "2014-11-05T09:46:44.255Z",
-    "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
-    "started_at": "2014-11-05T09:46:39.192Z",
-    "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
-    "runner_id": 1,
-    "coverage": null,
-    "commit_id": 3
-  }]
-}]
-```
-
-### Create commit
-
-Inform GitLab CI about new commit you want it to build.
-
-__If commit already exists in GitLab CI it will not be created__
-
-
-    POST /ci/commits
-
-Parameters:
-
-  * `project_id` (required) - The ID of a project
-  * `project_token` (requires) - Project token
-  * `data` (required) -  Push data. For example see comment in `lib/api/commits.rb`
-
-Returns:
-
-```json
-{
-  "id": 3,
-  "ref": "master",
-  "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
-  "project_id": 2,
-  "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
-  "created_at": "2014-11-05T09:46:35.247Z",
-  "status": "success",
-  "finished_at": "2014-11-05T09:46:44.254Z",
-  "duration": 5.062692165374756,
-  "git_commit_message": "wow\n",
-  "git_author_name": "Administrator",
-  "git_author_email": "admin@example.com",
-  "builds": [{
-    "id": 7,
-    "project_id": 2,
-    "ref": "master",
-    "status": "success",
-    "finished_at": "2014-11-05T09:46:44.254Z",
-    "created_at": "2014-11-05T09:46:35.259Z",
-    "updated_at": "2014-11-05T09:46:44.255Z",
-    "sha": "65617dfc36761baa1f46a7006f2a88916f7f56cf",
-    "started_at": "2014-11-05T09:46:39.192Z",
-    "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898",
-    "runner_id": 1,
-    "coverage": null,
-    "commit_id": 3
-  }]
-}
-```
diff --git a/doc/ci/api/projects.md b/doc/ci/api/projects.md
deleted file mode 100644
index fe6b1c01352b35216e7d09f63321231e8a3cc5b7..0000000000000000000000000000000000000000
--- a/doc/ci/api/projects.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# Projects API
-
-This API is intended to aid in the setup and configuration of
-projects on GitLab CI.
-
-__Authentication is done by GitLab user token & GitLab url__
-
-## Projects
-
-### List Authorized Projects
-
-Lists all projects that the authenticated user has access to.
-
-```
-GET /ci/projects
-```
-
-Returns:
-
-```json
-[
-  {
-    "id" : 271,
-    "name" : "gitlabhq",
-    "timeout" : 1800,
-    "token" : "iPWx6WM4lhHNedGfBpPJNP",
-    "default_ref" : "master",
-    "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
-    "path" : "gitlab/gitlab-shell",
-    "always_build" : false,
-    "polling_interval" : null,
-    "public" : false,
-    "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
-    "gitlab_id" : 3
-  },
-  {
-    "id" : 272,
-    "name" : "gitlab-ci",
-    "timeout" : 1800,
-    "token" : "iPWx6WM4lhHNedGfBpPJNP",
-    "default_ref" : "master",
-    "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
-    "path" : "gitlab/gitlab-shell",
-    "always_build" : false,
-    "polling_interval" : null,
-    "public" : false,
-    "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
-    "gitlab_id" : 4
-  }
-]
-```
-
-### List Owned Projects
-
-Lists all projects that the authenticated user owns.
-
-```
-GET /ci/projects/owned
-```
-
-Returns:
-
-```json
-[
-  {
-    "id" : 272,
-    "name" : "gitlab-ci",
-    "timeout" : 1800,
-    "token" : "iPWx6WM4lhHNedGfBpPJNP",
-    "default_ref" : "master",
-    "gitlab_url" : "http://demo.gitlabhq.com/gitlab/gitlab-shell",
-    "path" : "gitlab/gitlab-shell",
-    "always_build" : false,
-    "polling_interval" : null,
-    "public" : false,
-    "ssh_url_to_repo" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
-    "gitlab_id" : 4
-  }
-]
-```
-
-### Single Project
-
-Returns information about a single project for which the user is
-authorized.
-
-    GET /ci/projects/:id
-
-Parameters:
-
-  * `id` (required) - The ID of the GitLab CI project
-
-### Create Project
-
-Creates a GitLab CI project using GitLab project details.
-
-    POST /ci/projects
-
-Parameters:
-
-  * `name` (required) - The name of the project
-  * `gitlab_id` (required) - The ID of the project on the GitLab instance
-  * `default_ref` (optional) - The branch to run on (default to `master`)
-
-### Update Project
-
-Updates a GitLab CI project using GitLab project details that the
-authenticated user has access to.
-
-    PUT /ci/projects/:id
-
-Parameters:
-
-  * `name` - The name of the project
-  * `default_ref` - The branch to run on (default to `master`)
-
-### Remove Project
-
-Removes a GitLab CI project that the authenticated user has access to.
-
-    DELETE /ci/projects/:id
-
-Parameters:
-
-  * `id` (required) - The ID of the GitLab CI project
-
-### Link Project to Runner
-
-Links a runner to a project so that it can make builds (only via
-authorized user).
-
-    POST /ci/projects/:id/runners/:runner_id
-
-Parameters:
-
-  * `id` (required) - The ID of the GitLab CI project
-  * `runner_id` (required) - The ID of the GitLab CI runner
-
-### Remove Project from Runner
-
-Removes a runner from a project so that it can not make builds (only
-via authorized user).
-
-    DELETE /ci/projects/:id/runners/:runner_id
-
-Parameters:
-
-  * `id` (required) - The ID of the GitLab CI project
-  * `runner_id` (required) - The ID of the GitLab CI runner
\ No newline at end of file
diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md
index e9033aeacd55b79252ccf4e3438b63932ebec4da..2f01da4bd76c46f45466c5e95a0561f9659998f5 100644
--- a/doc/ci/api/runners.md
+++ b/doc/ci/api/runners.md
@@ -1,81 +1,46 @@
 # Runners API
 
+API used by runners to register and delete themselves.
+
 _**Note:** This API is intended to be used only by Runners as their own
 communication channel. For the consumer API see the
 [new Runners API](../../api/runners.md)._
 
-## Runners
-
-### Retrieve all runners
+## Authentication
 
-__Authentication is done by GitLab user token & GitLab url__
+This API uses two types of authentication:
 
-Used to get information about all runners registered on the GitLab CI
-instance.
+1.   Unique runner's token
 
-    GET /ci/runners
+     Token assigned to runner after it has been registered.
 
-Returns:
+2.   Using runners' registration token
 
-```json
-[
-  {
-    "id" : 85,
-    "token" : "12b68e90394084703135"
-  },
-  {
-    "id" : 86,
-    "token" : "76bf894e969364709864"
-  },
-]
-```
+     This is a token that can be found in project's settings.
+     It can be also found in Admin area &raquo; Runners settings.
 
-### Register a new runner
+     There are two types of tokens you can pass - shared runner registration
+     token or project specific registration token.
 
+## Runners
 
-__Authentication is done with a Shared runner registration token or a project Specific runner registration token__
+### Register a new runner
 
 Used to make GitLab CI aware of available runners.
 
-    POST /ci/runners/register
+    POST /ci/api/v1/runners/register
 
 Parameters:
 
-  * `token` (required) - The registration token. It is 2 types of token you can pass here. 
+  * `token` (required) - Registration token
 
-1. Shared runner registration token
-2. Project specific registration token
-
-Returns:
-
-```json
-{
-  "id" : 85,
-  "token" : "12b68e90394084703135"
-}
-```
 
 ### Delete a runner
 
+Used to remove runner.
 
-__Authentication is done by runner token__
-
-Used to removing runners.
-
-    DELETE /ci/runners/delete
+    DELETE /ci/api/v1/runners/delete
 
 Parameters:
 
-  * `token` (required) - The runner token.
-
-Returns:
-
-```json
-{
-  "id" : 1,
-  "token" : "d14963981a428f70121777e50643d1",
-  "created_at" : "2015-02-26T11:39:39.232Z",
-  "updated_at" : "2015-02-26T11:39:39.232Z",
-  "description" : "awesome runner"
-}
-```
+  * `token` (required) - Unique runner token
diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
index 9bd2f5aff22814dff1a86bb1636c227583e78603..c10f82054e2dae9f0ff9430afcea4aca373b6a66 100644
--- a/doc/ci/enable_or_disable_ci.md
+++ b/doc/ci/enable_or_disable_ci.md
@@ -64,7 +64,7 @@ Save the file and restart GitLab: `sudo service gitlab restart`.
 For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line:
 
 ```
-gitlab-rails['gitlab_default_projects_features_builds'] = false
+gitlab_rails['gitlab_default_projects_features_builds'] = false
 ```
 
 Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 31f29f4a082e92ab05db675b040e4532a644c313..cc059dc437633fd93fff4e4228ea15c6eef8f540 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,13 +1,15 @@
-## Build script examples
+# CI Examples
 
+- [Testing a PHP application](php.md)
 - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
 - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
 - [Test a Clojure application](test-clojure-application.md)
+- [Using `dpl` as deployment tool](deployment/README.md)
+- Help your favorite programming language and GitLab by sending a merge request
+  with a guide for that language.
 
-## Languages
+## Outside the documentation
 
-This is a list of languages you can test with GitLab CI. Each section has
-comprehensive documentation and comes with a test repository hosted on
-GitLab.com.
-
-- [Testing PHP](php.md)
+- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
+- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 624d9899c79239ca7af90470e13f4b7e5e30454f..9aba4326e116096a42ead0f4eaaa060711cf46d0 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -223,20 +223,13 @@ You can access a builds badge image using following link:
 http://example.gitlab.com/namespace/project/badges/branch/build.svg
 ```
 
+Awesome! You started using CI in GitLab!
+
 ## Examples
 
 Visit the [examples README][examples] to see a list of examples using GitLab
 CI with various languages.
 
-## Next steps
-
-Awesome! You started using CI in GitLab!
-
-Next you can look into doing more with the CI. Many people are using GitLab
-to package, containerize, test and deploy software.
-
-Visit our various languages examples at <https://gitlab.com/groups/gitlab-examples>.
-
 [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
 [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
 [examples]: ../examples/README.md
diff --git a/doc/development/README.md b/doc/development/README.md
index b9a0d81e5ba9c3ae30d6d0f50183caefd8afae54..1b281809afcb95e33ef4a91bf704008e6b3d38f3 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -1,7 +1,6 @@
 # Development
 
 - [Architecture](architecture.md) of GitLab
-- [Benchmarking](benchmarking.md)
 - [CI setup](ci_setup.md) for testing GitLab
 - [Gotchas](gotchas.md) to avoid
 - [How to dump production data to staging](db_dump.md)
@@ -9,4 +8,5 @@
 - [Rake tasks](rake_tasks.md) for development
 - [Shell commands](shell_commands.md) in the GitLab codebase
 - [Sidekiq debugging](sidekiq_debugging.md)
+- [SQL guidelines](sql.md) for SQL guidelines
 - [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
diff --git a/doc/development/benchmarking.md b/doc/development/benchmarking.md
deleted file mode 100644
index 88e18ee95f9b43dcf2d796ac475bd6f79c66940c..0000000000000000000000000000000000000000
--- a/doc/development/benchmarking.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Benchmarking
-
-GitLab CE comes with a set of benchmarks that are executed for every build. This
-makes it easier to measure performance of certain components over time.
-
-Benchmarks are written as RSpec tests using a few extra helpers. To write a
-benchmark, first tag the top-level `describe`:
-
-```ruby
-describe MaruTheCat, benchmark: true do
-
-end
-```
-
-This ensures the benchmark is executed separately from other test collections.
-It also exposes the various RSpec matchers used for writing benchmarks to the
-test group.
-
-Next, lets write the actual benchmark:
-
-```ruby
-describe MaruTheCat, benchmark: true do
-  let(:maru) { MaruTheChat.new }
-
-  describe '#jump_in_box' do
-    benchmark_subject { maru.jump_in_box }
-
-    it { is_expected.to iterate_per_second(9000) }
-  end
-end
-```
-
-Here `benchmark_subject` is a small wrapper around RSpec's `subject` method that
-makes it easier to specify the subject of a benchmark. Using RSpec's regular
-`subject` would require us to write the following instead:
-
-```ruby
-subject { -> { maru.jump_in_box } }
-```
-
-The `iterate_per_second` matcher defines the amount of times per second a
-subject should be executed. The higher the amount of iterations the better.
-
-By default the allowed standard deviation is a maximum of 30%. This can be
-adjusted by chaining the `with_maximum_stddev` on the `iterate_per_second`
-matcher:
-
-```ruby
-it { is_expected.to iterate_per_second(9000).with_maximum_stddev(50) }
-```
-
-This can be useful if the code in question depends on external resources of
-which the performance can vary a lot (e.g. physical HDDs, network calls, etc).
-However, in most cases 30% should be enough so only change this when really
-needed.
-
-## Benchmarks Location
-
-Benchmarks should be stored in `spec/benchmarks` and should follow the regular
-Rails specs structure. That is, model benchmarks go in `spec/benchmark/models`,
-benchmarks for code in the `lib` directory go in `spec/benchmarks/lib`, etc.
-
-## Underlying Technology
-
-The benchmark setup uses [benchmark-ips][benchmark-ips] which takes care of the
-heavy lifting such as warming up code, calculating iterations, standard
-deviation, etc.
-
-[benchmark-ips]: https://github.com/evanphx/benchmark-ips
diff --git a/doc/development/sql.md b/doc/development/sql.md
new file mode 100644
index 0000000000000000000000000000000000000000..23fd7604957cafed7716b88d61e55df4ba08299a
--- /dev/null
+++ b/doc/development/sql.md
@@ -0,0 +1,219 @@
+# SQL Query Guidelines
+
+This document describes various guidelines to follow when writing SQL queries,
+either using ActiveRecord/Arel or raw SQL queries.
+
+## Using LIKE Statements
+
+The most common way to search for data is using the `LIKE` statement. For
+example, to get all issues with a title starting with "WIP:" you'd write the
+following query:
+
+```sql
+SELECT *
+FROM issues
+WHERE title LIKE 'WIP:%';
+```
+
+On PostgreSQL the `LIKE` statement is case-sensitive. On MySQL this depends on
+the case-sensitivity of the collation, which is usually case-insensitive. To
+perform a case-insensitive `LIKE` on PostgreSQL you have to use `ILIKE` instead.
+This statement in turn isn't supported on MySQL.
+
+To work around this problem you should write `LIKE` queries using Arel instead
+of raw SQL fragments as Arel automatically uses `ILIKE` on PostgreSQL and `LIKE`
+on MySQL. This means that instead of this:
+
+```ruby
+Issue.where('title LIKE ?', 'WIP:%')
+```
+
+You'd write this instead:
+
+```ruby
+Issue.where(Issue.arel_table[:title].matches('WIP:%'))
+```
+
+Here `matches` generates the correct `LIKE` / `ILIKE` statement depending on the
+database being used.
+
+If you need to chain multiple `OR` conditions you can also do this using Arel:
+
+```ruby
+table = Issue.arel_table
+
+Issue.where(table[:title].matches('WIP:%').or(table[:foo].matches('WIP:%')))
+```
+
+For PostgreSQL this produces:
+
+```sql
+SELECT *
+FROM issues
+WHERE (title ILIKE 'WIP:%' OR foo ILIKE 'WIP:%')
+```
+
+In turn for MySQL this produces:
+
+```sql
+SELECT *
+FROM issues
+WHERE (title LIKE 'WIP:%' OR foo LIKE 'WIP:%')
+```
+
+## LIKE & Indexes
+
+Neither PostgreSQL nor MySQL use any indexes when using `LIKE` / `ILIKE` with a
+wildcard at the start. For example, this will not use any indexes:
+
+```sql
+SELECT *
+FROM issues
+WHERE title ILIKE '%WIP:%';
+```
+
+Because the value for `ILIKE` starts with a wildcard the database is not able to
+use an index as it doesn't know where to start scanning the indexes.
+
+MySQL provides no known solution to this problem. Luckily PostgreSQL _does_
+provide a solution: trigram GIN indexes. These indexes can be created as
+follows:
+
+```sql
+CREATE INDEX [CONCURRENTLY] index_name_here
+ON table_name
+USING GIN(column_name gin_trgm_ops);
+```
+
+The key here is the `GIN(column_name gin_trgm_ops)` part. This creates a [GIN
+index][gin-index] with the operator class set to `gin_trgm_ops`. These indexes
+_can_ be used by `ILIKE` / `LIKE` and can lead to greatly improved performance.
+One downside of these indexes is that they can easily get quite large (depending
+on the amount of data indexed).
+
+To keep naming of these indexes consistent please use the following naming
+pattern:
+
+    index_TABLE_on_COLUMN_trigram
+
+For example, a GIN/trigram index for `issues.title` would be called
+`index_issues_on_title_trigram`.
+
+Due to these indexes taking quite some time to be built they should be built
+concurrently. This can be done by using `CREATE INDEX CONCURRENTLY` instead of
+just `CREATE INDEX`. Concurrent indexes can _not_ be created inside a
+transaction. Transactions for migrations can be disabled using the following
+pattern:
+
+```ruby
+class MigrationName < ActiveRecord::Migration
+  disable_ddl_transaction!
+end
+```
+
+For example:
+
+```ruby
+class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
+  disable_ddl_transaction!
+
+  def up
+    return unless Gitlab::Database.postgresql?
+
+    execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));'
+    execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));'
+  end
+
+  def down
+    return unless Gitlab::Database.postgresql?
+
+    remove_index :users, :index_on_users_lower_username
+    remove_index :users, :index_on_users_lower_email
+  end
+end
+```
+
+## Plucking IDs
+
+This can't be stressed enough: **never** use ActiveRecord's `pluck` to pluck a
+set of values into memory only to use them as an argument for another query. For
+example, this will make the database **very** sad:
+
+```ruby
+projects = Project.all.pluck(:id)
+
+MergeRequest.where(source_project_id: projects)
+```
+
+Instead you can just use sub-queries which perform far better:
+
+```ruby
+MergeRequest.where(source_project_id: Project.all.select(:id))
+```
+
+The _only_ time you should use `pluck` is when you actually need to operate on
+the values in Ruby itself (e.g. write them to a file). In almost all other cases
+you should ask yourself "Can I not just use a sub-query?".
+
+## Use UNIONs
+
+UNIONs aren't very commonly used in most Rails applications but they're very
+powerful and useful. In most applications queries tend to use a lot of JOINs to
+get related data or data based on certain criteria, but JOIN performance can
+quickly deteriorate as the data involved grows.
+
+For example, if you want to get a list of projects where the name contains a
+value _or_ the name of the namespace contains a value most people would write
+the following query:
+
+```sql
+SELECT *
+FROM projects
+JOIN namespaces ON namespaces.id = projects.namespace_id
+WHERE projects.name ILIKE '%gitlab%'
+OR namespaces.name ILIKE '%gitlab%';
+```
+
+Using a large database this query can easily take around 800 milliseconds to
+run. Using a UNION we'd write the following instead:
+
+```sql
+SELECT projects.*
+FROM projects
+WHERE projects.name ILIKE '%gitlab%'
+
+UNION
+
+SELECT projects.*
+FROM projects
+JOIN namespaces ON namespaces.id = projects.namespace_id
+WHERE namespaces.name ILIKE '%gitlab%';
+```
+
+This query in turn only takes around 15 milliseconds to complete while returning
+the exact same records.
+
+This doesn't mean you should start using UNIONs everywhere, but it's something
+to keep in mind when using lots of JOINs in a query and filtering out records
+based on the joined data.
+
+GitLab comes with a `Gitlab::SQL::Union` class that can be used to build a UNION
+of multiple `ActiveRecord::Relation` objects. You can use this class as
+follows:
+
+```ruby
+union = Gitlab::SQL::Union.new([projects, more_projects, ...])
+
+Project.from("(#{union.to_sql}) projects")
+```
+
+## Ordering by Creation Date
+
+When ordering records based on the time they were created you can simply order
+by the `id` column instead of ordering by `created_at`. Because IDs are always
+unique and incremented in the order that rows are created this will produce the
+exact same results. This also means there's no need to add an index on
+`created_at` to ensure consistent performance as `id` is already indexed by
+default.
+
+[gin-index]: http://www.postgresql.org/docs/current/static/gin.html
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index 0f2665a3bf7162a4cf4b216296a9f6e6041f676e..15051dd76f97abaf65dd5c083ee63cb7e40872cf 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
 
 **Note: Custom git hooks must be configured on the filesystem of the GitLab
 server. Only GitLab server administrators will be able to complete these tasks.
-Please explore webhooks as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
+Please explore [webhooks](doc/web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
 
 Git natively supports hooks that are executed on different actions.
 Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index c1787a7c6a8c55749174434efc9c7b6aa25c2e4f..0fd54be58b09f4ec09e931183c1433efd27f384a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -467,12 +467,15 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
 
 ### Initial Login
 
-Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
+Visit YOUR_SERVER in your web browser for your first GitLab login.
 
-    root
-    5iveL!fe
+If you didn't [provide a root password during setup](#initialize-database-and-activate-advanced-features),
+you'll be redirected to a password reset screen to provide the password for the
+initial administrator account. Enter your desired password and you'll be
+redirected back to the login screen.
 
-**Important Note:** On login you'll be prompted to change the password.
+The default account's username is **root**. Provide the password you created
+earlier and login. After login you can change the username if you wish.
 
 **Enjoy!**
 
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 8df142c531b1566e3c2345b07559f3344cd83fad..d59b7f0e84dcc778623ea25f950da8571e44fe4f 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -97,6 +97,17 @@ To change the Unicorn workers when you have the Omnibus package please see [the
 
 If you want to run the database separately expect a size of about 1 MB per user.
 
+### PostgreSQL Requirements
+
+Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every
+GitLab database. This extension can be enabled (using a PostgreSQL super user)
+by running the following query for every database:
+
+    CREATE EXTENSION pg_trgm;
+
+On some systems you may need to install an additional package (e.g.
+`postgresql-contrib`) for this extension to become available.
+
 ## Redis and Sidekiq
 
 Redis stores all user sessions and the background task queue.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 281eea8363d4146f745fae5839429e3f6e0b9f37..7c8f785a61f1c26f0f5ce11e56e613b8c341f513 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -39,3 +39,34 @@ please see the [project_services directory][projects-code].
 [jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
 [Project Service]: ../project_services/project_services.md
 [projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
+
+## SSL certificate errors
+
+When trying to integrate GitLab with services that are using self-signed certificates,
+it is very likely that SSL certificate errors will occur on different parts of the
+application, most likely Sidekiq. There are 2 approaches you can take to solve this:
+
+1. Add the root certificate to the trusted chain of the OS.
+1. If using Omnibus, you can add the certificate to GitLab's trusted certificates.
+
+**OS main trusted chain**
+
+This [resource](http://kb.kerio.com/product/kerio-connect/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
+has all the information you need to add a certificate to the main trusted chain.
+
+This [answer](http://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu)
+at SuperUser also has relevant information.
+
+**Omnibus Trusted Chain**
+
+It is enough to concatenate the certificate to the main trusted certificate:
+
+```bash
+cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem
+```
+
+After that restart GitLab with:
+
+```bash
+sudo gitlab-ctl restart
+```
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index f256477196b50f4af0b4c3215e86e5a877facaf4..cf1f98492ea83f85d8e477dcf0d516edb8391a36 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -204,3 +204,25 @@ When setting `method: ssl`, the underlying authentication method used by
 `omniauth-ldap` is `simple_tls`.  This method establishes TLS encryption with 
 the LDAP server before any LDAP-protocol data is exchanged but no validation of
 the LDAP server's SSL certificate is performed.
+
+## Troubleshooting
+
+### Invalid credentials when logging in
+
+Make sure the user you are binding with has enough permissions to read the user's
+tree and traverse it.
+
+Also make sure that the `user_filter` is not blocking otherwise valid users.
+
+To make sure that the LDAP settings are correct and GitLab can see your users,
+execute the following command:
+
+
+```bash
+# For Omnibus installations
+sudo gitlab-rake gitlab:ldap:check
+
+# For installations from source
+sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
+```
+
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index c84113556cd72308ff354654078eff52c0314411..1c3dc707f6d7992a33000543cbe2c8f6688dc3c2 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -131,14 +131,70 @@ On the sign in page there should now be a SAML button below the regular sign in
 Click the icon to begin the authentication process. If everything goes well the user
 will be returned to GitLab and will be signed in.
 
+## Customization
+
+### `attribute_statements`
+
+>**Note:**
+This setting is only available on GitLab 8.6 and above.
+This setting should only be used to map attributes that are part of the
+OmniAuth info hash schema.
+
+`attribute_statements` is used to map Attribute Names in a SAMLResponse to entries
+in the OmniAuth [info hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later).
+
+For example, if your SAMLResponse contains an Attribute called 'EmailAddress',
+specify `{ email: ['EmailAddress'] }` to map the Attribute to the
+corresponding key in the info hash.  URI-named Attributes are also supported, e.g.
+`{ email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] }`.
+
+This setting allows you tell GitLab where to look for certain attributes required
+to create an account. Like mentioned above, if your IdP sends the user's email
+address as `EmailAddress` instead of `email`, let GitLab know by setting it on
+your configuration:
+
+```yaml
+args: {
+        assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+        idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+        idp_sso_target_url: 'https://login.example.com/idp',
+        issuer: 'https://gitlab.example.com',
+        name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
+        attribute_statements: { email: ['EmailAddress'] }
+}
+```
+
+### `allowed_clock_drift`
+
+The clock of the Identity Provider may drift slightly ahead of your system clocks.
+To allow for a small amount of clock drift you can use `allowed_clock_drift` within
+your settings. Its value must be given in a number (and/or fraction) of seconds.
+The value given is added to the current time at which the response is validated.
+
+```yaml
+args: {
+        assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+        idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+        idp_sso_target_url: 'https://login.example.com/idp',
+        issuer: 'https://gitlab.example.com',
+        name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
+        attribute_statements: { email: ['EmailAddress'] },
+        allowed_clock_drift: 1 # for one second clock drift
+}
+```
+
 ## Troubleshooting
 
+### 500 error after login
+
 If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page,
 this likely indicates that GitLab could not get the email address for the SAML user.
 
 Make sure the IdP provides a claim containing the user's email address, using claim name
 `email` or `mail`.
 
+### Redirect back to login screen with no evident error
+
 If after signing in into your SAML server you are redirected back to the sign in page and
 no error is displayed, check your `production.log` file. It will most likely contain the
 message `Can't verify CSRF token authenticity`. This means that there is an error during
@@ -147,4 +203,36 @@ the SAML request, but this error never reaches GitLab due to the CSRF check.
 To bypass this you can add `skip_before_action :verify_authenticity_token` to the
 `omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
 where it can then be seen in the usual logs, or as a flash message in the login
-screen.
\ No newline at end of file
+screen.
+
+### Invalid audience
+
+This error means that the IdP doesn't recognize GitLab as a valid sender and
+receiver of SAML requests. Make sure to add the GitLab callback URL to the approved
+audiences of the IdP server.
+
+### Missing claims
+
+The IdP server needs to pass certain information in order for GitLab to either
+create an account, or match the login information to an existing account. `email`
+is the minimum amount of information that needs to be passed. If the IdP server
+is not providing this information, all SAML requests will fail.
+
+Make sure this information is provided.
+
+### Key validation error, Digest mismatch or Fingerprint mismatch
+
+These errors all come from a similar place, the SAML certificate. SAML requests
+need to be validated using a fingerprint, a certificate or a validator.
+
+For this you need take the following into account:
+
+- If no certificate is provided in the settings, a fingerprint or fingerprint
+  validator needs to be provided and the response from the server must contain
+  a certificate (`<ds:KeyInfo><ds:X509Data><ds:X509Certificate>`)
+- If a certificate is provided in the settings, it is no longer necessary for
+  the request to contain one. In this case the fingerprint or fingerprint
+  validators are optional
+
+Make sure that one of the above described scenarios is valid, or the requests will
+fail with one of the mentioned errors.
\ No newline at end of file
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index ecbe0d3e8873beed79b21381dcc853260fc7ffa7..f6ba80f46d5d67041ab863815f0ce929a964fdc2 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -2,19 +2,11 @@
 
 ## On Slack
 
-To enable Slack integration you must create an Incoming WebHooks integration on Slack;
+To enable Slack integration you must create an Incoming WebHooks integration on Slack:
 
 1.  [Sign in to Slack](https://slack.com/signin)
 
-1.  Select **Apps & Custom Integrations** from the dropdown next to your team name.
-
-1.  Click the **Configure** link (right-upper corner).
-
-1.  Select the **Custom integrations** tab.
-
-1.  Click the **Incoming WebHooks** row.
-
-1.  Click the **Add configuration** button.
+1.  Visit [Incoming WebHooks](https://my.slack.com/services/new/incoming-webhook/)
 
 1.  Choose the channel name you want to send notifications to.
 
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index cbf57db56846e2da3206570e38e69b79674f4311..e6eb1cf38197ed630fbc8700d8f890598617e4aa 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -29,6 +29,8 @@
 
 ## GitLab Flavored Markdown (GFM)
 
+_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._
+
 For GitLab we developed something we call "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality.
 
 You can use GFM in
@@ -88,8 +90,8 @@ GFM will autolink almost any URL you copy and paste into your text.
 
 ## Code and Syntax Highlighting
 
-_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
-list of supported languages visit the rouge website._
+_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the Rouge website._
 
 Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
 
@@ -591,3 +593,4 @@ By including colons in the header row, you can align the text within that column
 - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
 
 [rouge]: http://rouge.jneen.net/ "Rouge website"
+[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
index 7c12557a32170aee78e40e8475e9415374856793..27170c1eb1947bc9bba12a1861c5fccb5aaa4c9f 100644
--- a/doc/project_services/jira.md
+++ b/doc/project_services/jira.md
@@ -219,3 +219,16 @@ You can see from the above image that there are four references to GitLab:
 [JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
 [jira-ce]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2146 "MR - Backport JIRA service"
 [8_3_post]: https://about.gitlab.com/2015/12/22/gitlab-8-3-released/ "GitLab 8.3 release post"
+
+## Troubleshooting
+
+### GitLab is unable to comment on a ticket
+
+Make sure that the user you set up for GitLab to communicate with JIRA has the
+correct access permission to post comments on a ticket and to also transition the
+ticket, if you'd like GitLab to also take care of closing them.
+
+### GitLab is unable to close a ticket
+
+Make sure the the `Transition ID` you set within the JIRA settings matches the
+one your project needs to close a ticket.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index cc8a22cd003269b388c25adfe007ff1f8ab2a3d8..6be954ad68ba65ec530da46f4c270dda9b2fb9c7 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -6,6 +6,6 @@
 - [Features](features.md)
 - [Maintenance](maintenance.md) and self-checks
 - [User management](user_management.md)
-- [Web hooks](web_hooks.md)
+- [Webhooks](web_hooks.md)
 - [Import](import.md) of git repositories in bulk
 - [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index 5a8b94af9b4bdd034bba760ec6ef53371f9e9c85..2ebf7c48f4e969eed4a72632ea533dbebaeaf842 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -1,41 +1,41 @@
-# Web hooks
+# Webhooks
 
-## Add a web hook for **ALL** projects:
+## Add a webhook for **ALL** projects:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
     # source installations
     bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
 
-## Add a web hook for projects in a given **NAMESPACE**:
+## Add a webhook for projects in a given **NAMESPACE**:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme
     # source installations
     bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
 
-## Remove a web hook from **ALL** projects using:
+## Remove a webhook from **ALL** projects using:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
     # source installations
     bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
 
-## Remove a web hook from projects in a given **NAMESPACE**:
+## Remove a webhook from projects in a given **NAMESPACE**:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
     # source installations
     bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
 
-## List **ALL** web hooks:
+## List **ALL** webhooks:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:list
     # source installations
     bundle exec rake gitlab:web_hook:list RAILS_ENV=production
 
-## List the web hooks from projects in a given **NAMESPACE**:
+## List the webhooks from projects in a given **NAMESPACE**:
 
     # omnibus-gitlab
     sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/
diff --git a/doc/security/README.md b/doc/security/README.md
index be1abb88c3db6d19a12efb0378107bcb614bdb7e..4cd0fdd409443e6889a8232fc28cdd7603c61393 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -2,7 +2,7 @@
 
 - [Password length limits](password_length_limits.md)
 - [Rack attack](rack_attack.md)
-- [Web Hooks and insecure internal web services](webhooks.md)
+- [Webhooks and insecure internal web services](webhooks.md)
 - [Information exclusivity](information_exclusivity.md)
 - [Reset your root password](reset_root_password.md)
 - [User File Uploads](user_file_uploads.md)
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 1e9d33e87c314a3b9353a623b4806114b47c5939..bb46aebf4b5a41f83b3351e74a3d5d08c28e0035 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -1,13 +1,13 @@
-# Web Hooks and insecure internal web services
+# Webhooks and insecure internal web services
 
-If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Web Hooks.
+If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Webhooks.
 
-With [Web Hooks](../web_hooks/web_hooks.md), you and your project masters and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way. 
+With [Webhooks](../web_hooks/web_hooks.md), you and your project masters and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way. 
 
-Things get hairy, however, when a Web Hook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the web hook is triggered and the POST request is sent.
+Things get hairy, however, when a Webhook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the webhook is triggered and the POST request is sent.
 
-Because Web Hook requests are made by the GitLab server itself, these have complete access to everything running on the server (http://localhost:123) or within the server's local network (http://192.168.1.12:345), even if these services are otherwise protected and inaccessible from the outside world.
+Because Webhook requests are made by the GitLab server itself, these have complete access to everything running on the server (http://localhost:123) or within the server's local network (http://192.168.1.12:345), even if these services are otherwise protected and inaccessible from the outside world.
 
-If a web service does not require authentication, Web Hooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like "http://localhost:123/some-resource/delete". 
+If a web service does not require authentication, Webhooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like "http://localhost:123/some-resource/delete". 
 
 To prevent this type of exploitation from happening, make sure that you are aware of every web service GitLab could potentially have access to, and that all of these are set up to require authentication for every potentially destructive command. Enabling authentication but leaving a default password is not enough.
\ No newline at end of file
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
index 408a17ac348a28fc06ebf4f9fb874efb1a76307c..0a9cb5683e7a8e314d51bd6c6012d9cd5f5f42c5 100644
--- a/doc/update/8.4-to-8.5.md
+++ b/doc/update/8.4-to-8.5.md
@@ -64,6 +64,9 @@ sudo -u git -H bundle install --without postgres development test --deployment
 # PostgreSQL installations (note: the line below states '--without mysql')
 sudo -u git -H bundle install --without mysql development test --deployment
 
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
 # Run database migrations
 sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
 
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index a10e62877ba89de5a1228006fff608162115d1d0..f446ed0a35b9fa7e6ee51dbdf8aa7029fff233fe 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -62,7 +62,13 @@ sudo -u git -H bundle install --without development test mysql --deployment
 # MySQL
 sudo -u git -H bundle install --without development test postgres --deployment
 
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# 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
 ```
 
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index fd0327686b170dd62a6f07c3f88cab732f04eb6c..5fa39ef1b0a77e6eee04c1f78357ae25b6af53ef 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -4,7 +4,7 @@
 
 Although deprecated, if someone wants to make this script into a gem or otherwise improve it merge requests are welcome.
 
-*Make sure you view this [upgrade guide from the 'master' branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
+*Make sure you view this [upgrade guide from the 'master' branch](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md) for the most up to date instructions.*
 
 GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
 
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index b82306bd1dabb45689dcc9e6328f0036f20c0388..87049427139594b586f81b6eb3ca714c4ad15673 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -1,4 +1,4 @@
-# Web hooks
+# Webhooks
 
 _**Note:**
 Starting from GitLab 8.5:_
@@ -7,11 +7,11 @@ Starting from GitLab 8.5:_
 - _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_
 - _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_
 
-Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created.
+Project webhooks allow you to trigger an URL if new code is pushed or a new issue is created.
 
-You can configure web hooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the web hook URL.
+You can configure webhooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the webhook URL.
 
-Web hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
+Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
 
 ## SSL Verification
 
@@ -19,7 +19,7 @@ By default, the SSL certificate of the webhook endpoint is verified based on
 an internal list of Certificate Authorities,
 which means the certificate cannot be self-signed.
 
-You can turn this off in the web hook settings in your GitLab projects.
+You can turn this off in the webhook settings in your GitLab projects.
 
 ![SSL Verification](ssl.png)
 
@@ -582,7 +582,6 @@ X-Gitlab-Event: Note Hook
     "created_at": "2015-04-09 02:40:38 UTC",
     "updated_at": "2015-04-09 02:40:38 UTC",
     "file_name": "test.rb",
-    "expires_at": null,
     "type": "ProjectSnippet",
     "visibility_level": 0
   }
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 5076b2697a355010a8e2350643627f05d6bf415a..36cb9da2380489dc31325699e1fdafadf0bc9b5e 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -9,7 +9,8 @@ Documentation on how to use Git LFS are under [Managing large binary files with
 
 ## Configuration
 
-Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
+Git LFS objects can be large in size. By default, they are stored on the server
+GitLab is installed on.
 
 There are two configuration options to help GitLab server administrators:
 
@@ -37,5 +38,8 @@ In `config/gitlab.yml`:
 
 ## Known limitations
 
-* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
+* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
+  is not supported
 * Currently, removing LFS objects from GitLab Git LFS storage is not supported
+* LFS authentications via SSH is not supported for the time being
+* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index b59e92cb31798cbe20963082300f17ed1d7c19cc..ba91685a20b835eefc0de954f4958a2bacef595b 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -1,17 +1,21 @@
 # Git LFS
 
-Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git.
-The general recommendation is to not have Git repositories larger than 1GB to preserve performance.
+Managing large files such as audio, video and graphics files has always been one
+of the shortcomings of Git. The general recommendation is to not have Git repositories
+larger than 1GB to preserve performance.
 
-GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain
-environments it is not always convenient to use different commands to differentiate between the large files and regular ones.
+GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+(EE only), however in certain environments it is not always convenient to use
+different commands to differentiate between the large files and regular ones.
 
-Git LFS makes this simpler for the end user by removing the requirement to learn new commands.
+Git LFS makes this simpler for the end user by removing the requirement to
+learn new commands.
 
 ## How it works
 
-Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests.
-Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file.
+Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
+to authorize client requests. Once the request is authorized, Git LFS client receives
+instructions from where to fetch or where to push the large file.
 
 ## GitLab server configuration
 
@@ -24,15 +28,19 @@ Documentation for GitLab instance administrators is under [LFS administration do
 
 ## Known limitations
 
-* Git LFS v1 original API is not supported since it was deprecated early in LFS development
+* Git LFS v1 original API is not supported since it was deprecated early in LFS
+  development
 * When SSH is set as a remote, Git LFS objects still go through HTTPS
-* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting)
+* Any Git LFS request will ask for HTTPS credentials to be provided so good Git
+  credentials store is recommended
+* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
+  to add the URL to Git config manually (see #troubleshooting)
 
 ## Using Git LFS
 
-Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS:
-For example, if you want to upload a very large file and check it into your Git repository:
+Lets take a look at the workflow when you need to check large files into your Git
+repository with Git LFS. For example, if you want to upload a very large file and
+check it into your Git repository:
 
 ```bash
 git clone git@gitlab.example.com:group/project.git
@@ -40,7 +48,8 @@ git lfs init                          # initialize the Git LFS project project
 git lfs track "*.iso"                 # select the file extensions that you want to treat as large files
 ```
 
-Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension:
+Once a certain file extension is marked for tracking as a LFS object you can use
+Git as usual without having to redo the command to track a file with the same extension:
 
 ```bash
 cp ~/tmp/debian.iso ./                # copy a large file into the current directory
@@ -49,13 +58,17 @@ git commit -am "Added Debian iso"     # commit the file meta data
 git push origin master                # sync the git repo and large file to the GitLab server
 ```
 
-Cloning the repository works the same as before. Git automatically detects the LFS-tracked files and clones them via HTTP. If you performed the git clone command with a SSH URL, you have to enter your GitLab credentials for HTTP authentication.
+Cloning the repository works the same as before. Git automatically detects the
+LFS-tracked files and clones them via HTTP. If you performed the git clone
+command with a SSH URL, you have to enter your GitLab credentials for HTTP
+authentication.
 
 ```bash
 git clone git@gitlab.example.com:group/project.git
 ```
 
-If you already cloned the repository and you want to get the latest LFS object that are on the remote repository, eg. from branch `master`:
+If you already cloned the repository and you want to get the latest LFS object
+that are on the remote repository, eg. from branch `master`:
 
 ```bash
 git lfs fetch master
@@ -73,8 +86,8 @@ Check if you have permissions to push to the project or fetch from the project.
 
 * Project is not allowed to access the LFS object
 
-LFS object you are trying to push to the project or fetch from the project is not available to the project anymore.
-Probably the object was removed from the server.
+LFS object you are trying to push to the project or fetch from the project is not
+available to the project anymore. Probably the object was removed from the server.
 
 * Local git repository is using deprecated LFS API
 
@@ -89,16 +102,26 @@ git lfs logs last
 
 If the status `error 501` is shown, it is because:
 
-* Git LFS support is not enabled on the GitLab server. Check with your GitLab administrator why Git LFS is not enabled on the server. See [LFS administration documentation](lfs_administration.md) for instructions on how to enable LFS support.
+* Git LFS support is not enabled on the GitLab server. Check with your GitLab
+  administrator why Git LFS is not enabled on the server. See
+  [LFS administration documentation](lfs_administration.md) for instructions
+  on how to enable LFS support.
 
-* Git LFS client version is not supported by GitLab server. Check your Git LFS version with `git lfs version`. Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try to update your Git LFS client. Only version 1.0.1 and newer are supported.
+* Git LFS client version is not supported by GitLab server. Check your Git LFS
+  version with `git lfs version`. Check the Git config of the project for traces
+  of deprecated API with `git lfs -l`. If `batch = false` is set in the config,
+  remove the line and try to update your Git LFS client. Only version 1.0.1 and
+  newer are supported.
 
 ### getsockopt: connection refused
 
-If you push a LFS object to a project and you receive an error similar to: `Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
-the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP.
+If you push a LFS object to a project and you receive an error similar to:
+`Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
+the LFS client is trying to reach GitLab through HTTPS. However, your GitLab
+instance is being served on HTTP.
 
-This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config.
+This behaviour is caused by Git LFS using HTTPS connections by default when a
+`lfsurl` is not set in the Git config.
 
 To prevent this from happening, set the lfs url in project Git config:
 
@@ -109,18 +132,24 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/o
 
 ### Credentials are always required when pushing an object
 
-Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required.
+Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
+the LFS object on every push for every object, user HTTPS credentials are required.
 
-By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
+By default, Git has support for remembering the credentials for each repository
+you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
 
-For example, you can tell Git to remember the password for a period of time in which you expect to push the objects:
+For example, you can tell Git to remember the password for a period of time in
+which you expect to push the objects:
 
 ```bash
 git config --global credential.helper 'cache --timeout=3600'
 ```
 
-This will remember the credentials for an hour after which Git operations will require re-authentication.
+This will remember the credentials for an hour after which Git operations will
+require re-authentication.
 
-If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
+If you are using OS X you can use `osxkeychain` to store and encrypt your credentials.
+For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
 
-More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
\ No newline at end of file
+More details about various methods of storing the user credentials can be found
+on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
\ No newline at end of file
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
index 69b3a7764419da1d477afb7019c2a55b62bae350..bed9282f1c658208006a31b93e337d6b67ea4b56 100644
--- a/features/dashboard/archived_projects.feature
+++ b/features/dashboard/archived_projects.feature
@@ -10,3 +10,8 @@ Feature: Dashboard Archived Projects
   Scenario: I should see non-archived projects on dashboard
     Then I should see "Shop" project link
     And I should not see "Forum" project link
+
+  Scenario: I toggle show of archived projects on dashboard
+    When I click "Show archived projects" link
+    Then I should see "Shop" project link
+    And I should see "Forum" project link
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 7df6b6f09bab674b3c04d4c715c626c9530a4c2b..092e18d1b8697f0ad720aa9626e1ef14541c4366 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -140,4 +140,4 @@ Feature: Explore Projects
     When I visit the explore starred projects
     Then I should see project "Community"
     And I should see project "Internal"
-    And I should see project "Archive"
+    And I should not see project "Archive"
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
index 62ea66a783cc16fda8ca60e9209fff781ce63999..d6c05df9840ed4f68ccb3488fe0ef9c5dcc98a7e 100644
--- a/features/group/milestones.feature
+++ b/features/group/milestones.feature
@@ -28,3 +28,20 @@ Feature: Group Milestones
     And I fill milestone name
     When I press create mileston button
     Then milestone in each project should be created
+
+  Scenario: I should see Issues listed with labels
+    Given Group has projects with milestones
+    When I visit group "Owned" page
+    And I click on group milestones
+    And I click on one group milestone
+    Then I should see the "bug" label
+    And I should see the "feature" label
+    And I should see the project name in the Issue row
+
+  Scenario: I should see the Labels tab
+    Given Group has projects with milestones
+    When I visit group "Owned" page
+    And I click on group milestones
+    And I click on one group milestone
+    And I click on the "Labels" tab
+    Then I should see the list of labels
diff --git a/features/groups.feature b/features/groups.feature
index a60c3860b8348d23ad7a8cb2423fae1e6ae27585..419a5d3963d6314b0c969653d85d4f3b4d4382ba 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -15,6 +15,10 @@ Feature: Groups
   Scenario: I should see group "Owned" dashboard list
     When I visit group "Owned" page
     Then I should see group "Owned" projects list
+
+  @javascript
+  Scenario: I should see group "Owned" activity feed
+    When I visit group "Owned" activity page
     And I should see projects activity feed
 
   Scenario: I should see group "Owned" issues list
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 89af58dcef351c175a1e3ed527ec48b34360a910..ff21c7d1b83d3a73c143aed2b49b99171b242200 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -58,14 +58,6 @@ Feature: Project Issues
     Then I should see comment "XML attached"
     And I should see an error alert section within the comment form
 
-  @javascript
-  Scenario: Visiting Issues after leaving a comment
-    Given I visit issue page "Release 0.4"
-    And I leave a comment like "XML attached"
-    And I visit project "Shop" issues page
-    And I sort the list by "Last updated"
-    Then I should see "Release 0.4" at the top
-
   @javascript
   Scenario: Visiting Issues after being sorted the list
     Given I visit project "Shop" issues page
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index a69089f00c4cb2dd43de84056dc1ba202c3e3a85..f8d9fe1854dbbe3872e9e3718911d5ee2e8cb6dd 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -86,15 +86,6 @@ Feature: Project Merge Requests
     And I leave a comment like "XML attached"
     Then I should see comment "XML attached"
 
-  @javascript
-  Scenario: Visiting Merge Requests after leaving a comment
-    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
-    And I visit merge request page "Bug NS-04"
-    And I leave a comment like "XML attached"
-    And I visit project "Shop" merge requests page
-    And I sort the list by "Last updated"
-    Then I should see "Bug NS-04" at the top
-
   @javascript
   Scenario: Visiting Merge Requests after being sorted the list
     Given I visit project "Shop" merge requests page
@@ -128,16 +119,6 @@ Feature: Project Merge Requests
     And I sort the list by "Least popular"
     Then The list should be sorted by "Least popular"
 
-  @javascript
-  Scenario: Visiting Merge Requests after commenting on diffs
-    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
-    And I visit merge request page "Bug NS-05"
-    And I click on the Changes tab
-    And I leave a comment like "Line is wrong" on diff
-    And I visit project "Shop" merge requests page
-    And I sort the list by "Last updated"
-    Then I should see "Bug NS-05" at the top
-
   @javascript
   Scenario: I comment on a merge request diff
     Given project "Shop" have "Bug NS-05" open merge request with diffs inside
diff --git a/features/steps/dashboard/archived_projects.rb b/features/steps/dashboard/archived_projects.rb
index 36e092f50c6014eede84f52957cc83157161602d..6510f8d9b3267bbc2fd1103b333826a13741532e 100644
--- a/features/steps/dashboard/archived_projects.rb
+++ b/features/steps/dashboard/archived_projects.rb
@@ -19,4 +19,8 @@ class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
   step 'I should see "Forum" project link' do
     expect(page).to have_link "Forum"
   end
+
+  step 'I click "Show archived projects" link' do
+    click_link "Show archived projects"
+  end
 end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index cbe54e2dc792aa2d5aff88cbb47443a612ba3cae..d723300f4854a54813ae909f81f83dfd5269cdd7 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -36,13 +36,22 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
   end
 
   step 'I click "Authored by me" link' do
-    select2(current_user.id, from: "#author_id")
-    select2(nil, from: "#assignee_id")
+    execute_script('$("#assignee_id").val("")')
+    execute_script('$(".js-user-search").first().click()')
+    sleep 1
+    execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()")
+    sleep 1
   end
 
   step 'I click "All" link' do
-    select2(nil, from: "#author_id")
-    select2(nil, from: "#assignee_id")
+    execute_script('$(".js-user-search").first().click()')
+    sleep 1
+    execute_script('$(".js-user-search").first().parent().find("li a").first().click()')
+    sleep 1
+    execute_script('$(".js-user-search").eq(1).click()')
+    sleep 1
+    execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()')
+    sleep 1
   end
 
   def should_see(issue)
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 28c8c6b6015147ca49946968163ea9d6dcda01c5..7fc0e444e8683faf1e876729259e552cf6e20aa0 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -40,13 +40,22 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I click "Authored by me" link' do
-    select2(current_user.id, from: "#author_id")
-    select2(nil, from: "#assignee_id")
+    execute_script('$("#assignee_id").val("")')
+    execute_script('$(".js-user-search").first().click()')
+    sleep 0.5
+    execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()")
+    sleep 2
   end
 
   step 'I click "All" link' do
-    select2(nil, from: "#author_id")
-    select2(nil, from: "#assignee_id")
+    execute_script('$(".js-user-search").first().click()')
+    sleep 0.5
+    execute_script('$(".js-user-search").first().parent().find("li a").first().click()')
+    sleep 2
+    execute_script('$(".js-user-search").eq(1).click()')
+    sleep 0.5
+    execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()')
+    sleep 2
   end
 
   def should_see(merge_request)
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 2363ad797faac566a71a84db644ccbec80ff56b4..a167d25983777be8309af1c035edf1646d00d693 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -24,6 +24,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
   end
 
   step 'I click on one group milestone' do
+    milestones = Milestone.where(title: 'GL-113')
+    @global_milestone = GlobalMilestone.new('GL-113', milestones)
+
     click_link 'GL-113'
   end
 
@@ -33,7 +36,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
 
   step 'I should see group milestone with all issues and MRs assigned to that milestone' do
     expect(page).to have_content('Milestone GL-113')
-    expect(page).to have_content('Progress: 0 closed – 3 open')
+    expect(page).to have_content('3 issues: 3 open and 0 closed')
     issue = Milestone.find_by(name: 'GL-113').issues.first
     expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
   end
@@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
     end
   end
 
+  step 'I should see the "bug" label' do
+    page.within('#tab-issues') do
+      expect(page).to have_content 'bug'
+    end
+  end
+
+  step 'I should see the "feature" label' do
+    page.within('#tab-issues') do
+      expect(page).to have_content 'bug'
+    end
+  end
+
+  step 'I should see the project name in the Issue row' do
+    page.within('#tab-issues') do
+      @global_milestone.projects.each do |project|
+        expect(page).to have_content project.name
+      end
+    end
+  end
+
+  step 'I click on the "Labels" tab' do
+    page.within('.nav-links') do
+      page.find(:xpath, "//a[@href='#tab-labels']").click
+    end
+  end
+
+  step 'I should see the list of labels' do
+    page.within('#tab-labels') do
+      expect(page).to have_content 'bug'
+      expect(page).to have_content 'feature'
+    end
+  end
+
   private
 
   def group_milestone
@@ -68,6 +104,10 @@ def group_milestone
     %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
       project = create :project, path: path, group: group
       milestone = create :milestone, title: "Version 7.2", project: project
+
+      create(:label, project: project, title: 'bug')
+      create(:label, project: project, title: 'feature')
+
       create :issue,
         project: project,
         assignee: current_user,
@@ -80,11 +120,14 @@ def group_milestone
         due_date: '2114-08-20',
         description: 'Lorem Ipsum is simply dummy text'
 
-      create :issue,
+      issue = create :issue,
         project: project,
         assignee: current_user,
         author: current_user,
         milestone: milestone
+
+      issue.labels << project.labels.find_by(title: 'bug')
+      issue.labels << project.labels.find_by(title: 'feature')
     end
   end
 end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 0c60328583a74c67fbbd02c2543d4c1099f98f87..d9436e9e21a5ac1353d4c3cfd21976a6b4514044 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -99,9 +99,9 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   end
 
   step 'I reset my token' do
-    page.within '.update-token' do
+    page.within '.private-token' do
       @old_token = @user.private_token
-      click_button "Reset"
+      click_button "Reset private token"
     end
   end
 
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 9e96fa5ba4945d4a23d28a9a87a955160146adca..19d81453d8cd7f67cf32bf7847e969eec83f9287 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -26,7 +26,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
   end
 
   step 'I click the "Hooks" tab' do
-    click_link('Web Hooks')
+    click_link('Webhooks')
   end
 
   step 'I click the "Deploy Keys" tab' do
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
   end
 
   step 'the active sub nav should be Hooks' do
-    ensure_active_sub_nav('Web Hooks')
+    ensure_active_sub_nav('Webhooks')
   end
 
   step 'the active sub nav should be Deploy Keys' do
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index be4db770948cc3391592c4a79251930be02f943a..4994df589a7e4354e507b16534a649bdd5bc9de0 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -25,14 +25,14 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
   step 'I submit new hook' do
     @url = FFaker::Internet.uri("http")
     fill_in "hook_url", with: @url
-    expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
+    expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1)
   end
 
   step 'I submit new hook with SSL verification enabled' do
     @url = FFaker::Internet.uri("http")
     fill_in "hook_url", with: @url
     check "hook_enable_ssl_verification"
-    expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
+    expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1)
   end
 
   step 'I should see newly created hook' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 277c63914d12faee79b6e918c2142e4301873440..135e1d016ae06e565ed04c579431d0499d9fbf4c 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -10,7 +10,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
 
   step 'I click the thumbsup award Emoji' do
     page.within '.awards' do
-      thumbsup = page.find('.award .emoji-1F44D')
+      thumbsup = page.first('.award-control')
       thumbsup.click
       thumbsup.hover
       sleep 0.3
@@ -18,23 +18,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
   end
 
   step 'I click to emoji-picker' do
-    page.within '.awards-controls' do
-      page.find('.add-award').click
+    page.within '.awards' do
+      page.find('.js-add-award').click
     end
   end
 
   step 'I click to emoji in the picker' do
     page.within '.emoji-menu-content' do
-      page.first('.emoji-icon').click
+      page.first('.js-emoji-btn').click
     end
   end
 
   step 'I can remove it by clicking to icon' do
     page.within '.awards' do
       expect do
-        page.find('.award.active').click
+        page.find('.js-emoji-btn.active').click
         sleep 0.3
-      end.to change{ page.all(".award").size }.from(3).to(2)
+      end.to change{ page.all(".award-control.js-emoji-btn").size }.from(3).to(2)
     end
   end
 
@@ -49,23 +49,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
     sleep 0.2
 
     page.within '.awards' do
-      expect(page).to have_selector '.award'
-      expect(page.find('.award.active .counter')).to have_content '1'
-      expect(page.find('.award.active')['data-original-title']).to eq('me')
+      expect(page).to have_selector '.js-emoji-btn'
+      expect(page.find('.js-emoji-btn.active .js-counter')).to have_content '1'
+      expect(page.find('.js-emoji-btn.active')['data-original-title']).to eq('me')
     end
   end
 
   step 'I have no awards added' do
     page.within '.awards' do
-      expect(page).to have_selector '.award'
-      expect(page.all('.award').size).to eq(2)
+      expect(page).to have_selector '.award-control.js-emoji-btn'
+      expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
 
       # Check tooltip data
-      page.all('.award').each do |element|
+      page.all('.award-control.js-emoji-btn').each do |element|
         expect(element['title']).to eq("")
       end
 
-      page.all('.award .counter').each do |element|
+      page.all('.award-control .js-counter').each do |element|
         expect(element).to have_content '0'
       end
     end
@@ -79,7 +79,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
   step 'I leave comment with a single emoji' do
     page.within('.js-main-target-form') do
       fill_in 'note[note]', with: ':smile:'
-      click_button 'Add Comment'
+      click_button 'Comment'
     end
   end
 
diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index 50bb32429b965345a1ddf62f010fd77c086e884b..6d50501a722d05a7daa33493ef7782ae752ff654 100644
--- a/features/steps/project/issues/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -29,7 +29,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
   end
 
   step 'I click link "bug"' do
-    select2('bug', from: "#label_name")
+    page.find('.js-label-select').click
+    sleep 0.5
+    execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
+    sleep 2
   end
 
   step 'I click link "feature"' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 565bf088b416b9b457f8453d5c1e656902e95ced..8c31fa890b262fd3939c1a15d1de13bb41f41d4c 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -27,7 +27,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   end
 
   step 'I click link "Closed"' do
-    click_link "Closed"
+    find('.issues-state-filters a', text: "Closed").click
   end
 
   step 'I click button "Unsubscribe"' do
@@ -63,14 +63,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   end
 
   step 'I click "author" dropdown' do
-    first('#s2id_author_id').click
+    page.find('.js-author-search').click
+    sleep 1
   end
 
   step 'I see current user as the first user' do
-    expect(page).to have_selector('.user-result', visible: true, count: 3)
-    users = page.all('.user-name')
+    expect(page).to have_selector('.dropdown-content', visible: true)
+    users = page.all('.dropdown-menu-author .dropdown-content li a')
     expect(users[0].text).to eq 'Any Author'
-    expect(users[1].text).to eq current_user.name
+    expect(users[1].text).to eq "#{current_user.name} #{current_user.to_reference}"
   end
 
   step 'I submit new issue "500 error on profile"' do
@@ -267,7 +268,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   step 'I leave a comment with code block' do
     page.within(".js-main-target-form") do
       fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
-      click_button "Add Comment"
+      click_button "Comment"
       sleep 0.05
     end
   end
@@ -355,10 +356,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     end
   end
 
-  step 'I should see "Release 0.4" at the top' do
-    expect(page.find('ul.content-list.issues-list li.issue:first-child')).to have_content("Release 0.4")
-  end
-
   def filter_issue(text)
     fill_in 'issue_search', with: text
   end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index e2eda511497f4d6e4bb7974e41f94b8046d04768..4faa0f4707ce85dbb3ab4c5c6bcfd7414cf01bea 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
   end
 
   step 'I should see 3 issues' do
-    expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
+    expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
   end
 
   step 'I click link to remove milestone' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 8bf423cc64b28e1c3fc107eeafef3fb3368aafd2..df4259b9ddf2d79a03c8038fb0fc6e02be94aebd 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -419,7 +419,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
 
     page.within(".js-discussion-note-form") do
       fill_in "note_note", with: "Line is correct"
-      click_button "Add Comment"
+      click_button "Comment"
     end
 
     page.within ".files [id^=diff]:nth-child(2) .note-body > .note-text" do
@@ -432,7 +432,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
 
     page.within(".js-discussion-note-form") do
       fill_in "note_note", with: "Line is wrong on here"
-      click_button "Add Comment"
+      click_button "Comment"
     end
   end
 
@@ -517,14 +517,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     end
   end
 
-  step 'I should see "Bug NS-05" at the top' do
-    expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
-  end
-
-  step 'I should see "Bug NS-04" at the top' do
-    expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-04")
-  end
-
   def merge_request
     @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
   end
@@ -536,7 +528,7 @@ def init_diff_note
   def leave_comment(message)
     page.within(".js-discussion-note-form", visible: true) do
       fill_in "note_note", with: message
-      click_button "Add Comment"
+      click_button "Comment"
     end
     page.within(".notes_holder", visible: true) do
       expect(page).to have_content message
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 504654f90ddd12a28980281778bcae256015bd23..786a0cad97571599525bea9761ad02d21ad8d3ce 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -77,7 +77,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
   step 'I leave a comment like "Good snippet!"' do
     page.within('.js-main-target-form') do
       fill_in "note_note", with: "Good snippet!"
-      click_button "Add Comment"
+      click_button "Comment"
     end
   end
 
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 51b15791674c36b0b5d3511b75a01c65cb3ea293..243469b8e7d55f0eb237a70e0581604ed85e65aa 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -361,7 +361,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I can see the new rendered SVG image' do
-    expect(find('.file-content')).to have_css('img')
+    expect(page).to have_css('.file-content img')
   end
 
   private
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 06e69441894af6513fb00f07359a446ef8705ce7..906b66a4a6328e1854a4977098faf343b6ac1b20 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -93,14 +93,14 @@ module SharedDiffNote
 
       page.within("form[id$='#{sample_commit.line_code}']") do
         fill_in 'note[note]', with: ':smile:'
-        click_button('Add Comment')
+        click_button('Comment')
       end
     end
   end
 
   step 'I submit the diff comment' do
     page.within(diff_file_selector) do
-      click_button("Add Comment")
+      click_button("Comment")
     end
   end
 
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index ae10c6069a93e16b21aa057ae83f78c68d6a43b8..e59bfbea9982dce78ac03a3cfdef1b2bce21a7cf 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -182,7 +182,7 @@ def leave_reference_comment(issuable:, from_project_name:)
 
     page.within('.js-main-target-form') do
       fill_in 'note[note]', with: "##{issuable.to_reference(project)}"
-      click_button 'Add Comment'
+      click_button 'Comment'
     end
   end
 
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index eb6df61b8e6b43d6b905ca7d95608fdf12c70119..fb0462d6e0420d3c79646773817cf378f63eb8b0 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -17,7 +17,7 @@ module SharedNote
   step 'I leave a comment like "XML attached"' do
     page.within(".js-main-target-form") do
       fill_in "note[note]", with: "XML attached"
-      click_button "Add Comment"
+      click_button "Comment"
     end
   end
 
@@ -30,7 +30,7 @@ module SharedNote
 
   step 'I submit the comment' do
     page.within(".js-main-target-form") do
-      click_button "Add Comment"
+      click_button "Comment"
     end
   end
 
@@ -115,7 +115,7 @@ module SharedNote
   step 'I leave a comment with a header containing "Comment with a header"' do
     page.within(".js-main-target-form") do
       fill_in "note[note]", with: "# Comment with a header"
-      click_button "Add Comment"
+      click_button "Comment"
       sleep 0.05
     end
   end
@@ -144,11 +144,4 @@ module SharedNote
       expect(page).to have_content("+1 Awesome!")
     end
   end
-
-  step 'I sort the list by "Last updated"' do
-    find('button.dropdown-toggle.btn').click
-    page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
-      click_link "Last updated"
-    end
-  end
 end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index da9d1503ebcf116b2ffa0a41ef560317da834ebc..2bd8ea745e47abadca0472a99eb3e09533838a94 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -27,6 +27,10 @@ module SharedPaths
     visit group_path(Group.find_by(name: "Owned"))
   end
 
+  step 'I visit group "Owned" activity page' do
+    visit activity_group_path(Group.find_by(name: "Owned"))
+  end
+
   step 'I visit group "Owned" issues page' do
     visit issues_group_path(Group.find_by(name: "Owned"))
   end
diff --git a/features/support/env.rb b/features/support/env.rb
index 62c80b9c94884b949f8df13f3250d2a2f39376ea..357d164d87f73d0af44146f10f4d6c327222feab 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -14,6 +14,7 @@
 
 require_relative 'capybara'
 require_relative 'db_cleaner'
+require_relative 'rerun'
 
 %w(select2_helper test_env repo_helpers).each do |f|
   require Rails.root.join('spec', 'support', f)
diff --git a/features/support/rerun.rb b/features/support/rerun.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b176c5be895e397ddacb4c085488c9dee9ef5bc
--- /dev/null
+++ b/features/support/rerun.rb
@@ -0,0 +1,14 @@
+# The spinach-rerun-reporter doesn't define the on_undefined_step
+# See it here: https://github.com/javierav/spinach-rerun-reporter/blob/master/lib/spinach/reporter/rerun.rb
+module Spinach
+  class Reporter
+    class Rerun
+      def on_undefined_step(step_data, failure, step_definitions = nil)
+        super step_data, failure, step_definitions
+
+        # save feature file and scenario line
+        @rerun << "#{current_feature.filename}:#{current_scenario.line}"
+      end
+    end
+  end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b021db8fa5b9f4547b90fae346625ddbbe523f4e..5b5b8bd044b89ca173503ad318839746881bafee 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -23,6 +23,8 @@ class Identity < Grape::Entity
     end
 
     class UserFull < User
+      expose :last_sign_in_at
+      expose :confirmed_at
       expose :email
       expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
       expose :identities, using: Entities::Identity
@@ -141,7 +143,7 @@ class RepoCommitDetail < RepoCommit
     class ProjectSnippet < Grape::Entity
       expose :id, :title, :file_name
       expose :author, using: Entities::UserBasic
-      expose :expires_at, :updated_at, :created_at
+      expose :updated_at, :created_at
     end
 
     class ProjectEntity < Grape::Entity
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 1a3f662811a0a12467cd8cffe520b38bf64a842d..28e074cd2893130f5aaff46bad6218afe609949b 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -10,7 +10,7 @@ class ValidationError < StandardError;end
     attr_reader :before_script, :image, :services, :variables, :path, :cache
 
     def initialize(config, path = nil)
-      @config = YAML.safe_load(config, [Symbol])
+      @config = YAML.safe_load(config, [Symbol], [], true)
       @path = path
 
       unless @config.is_a? Hash
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 3f483847efaa14f6adf98502cf88bc6c92a8799f..46e51a4bf6d9868bc16dc2c439da3cf981c7b799 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -76,7 +76,7 @@ def import_issues
           project.issues.create!(
             description: body,
             title: issue["title"],
-            state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened',
+            state: %w(resolved invalid duplicate wontfix closed).include?(issue["status"]) ? 'closed' : 'opened',
             author_id: gl_user_id(project, reporter)
           )
         end
diff --git a/lib/gitlab/devise_failure.rb b/lib/gitlab/devise_failure.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a78fde9d7829136ba49633de4cb56a0bcabf65b7
--- /dev/null
+++ b/lib/gitlab/devise_failure.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  class DeviseFailure < Devise::FailureApp
+    protected
+
+    # Override `Devise::FailureApp#request_format` to handle a special case
+    #
+    # This tells Devise to handle an unauthenticated `.zip` request as an HTML
+    # request (i.e., redirect to sign in).
+    #
+    # Otherwise, Devise would respond with a 401 Unauthorized with
+    # `Content-Type: application/zip` and a response body in plaintext, and the
+    # browser would freak out.
+    #
+    # See https://gitlab.com/gitlab-org/gitlab-ce/issues/12944
+    def request_format
+      if request.format == :zip
+        Mime::Type.lookup_by_extension(:html).ref
+      else
+        super
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2ef50286b1ddf6d3217eb0a714200a1823595d65
--- /dev/null
+++ b/lib/gitlab/exclusive_lease.rb
@@ -0,0 +1,41 @@
+module Gitlab
+  # This class implements an 'exclusive lease'. We call it a 'lease'
+  # because it has a set expiry time. We call it 'exclusive' because only
+  # one caller may obtain a lease for a given key at a time. The
+  # implementation is intended to work across GitLab processes and across
+  # servers. It is a 'cheap' alternative to using SQL queries and updates:
+  # you do not need to change the SQL schema to start using
+  # ExclusiveLease.
+  #
+  # It is important to choose the timeout wisely. If the timeout is very
+  # high (1 hour) then the throughput of your operation gets very low (at
+  # most once an hour). If the timeout is lower than how long your
+  # operation may take then you cannot count on exclusivity. For example,
+  # if the timeout is 10 seconds and you do an operation which may take 20
+  # seconds then two overlapping operations may hold a lease for the same
+  # key at the same time.
+  #
+  class ExclusiveLease
+    def initialize(key, timeout:)
+      @key, @timeout = key, timeout
+    end
+
+    # Try to obtain the lease. Return true on success,
+    # false if the lease is already taken.
+    def try_obtain
+      # Performing a single SET is atomic
+      !!redis.set(redis_key, '1', nx: true, ex: @timeout)
+    end
+
+    private
+
+    def redis
+      # Maybe someday we want to use a connection pool...
+      @redis ||= Redis.new(url: Gitlab::RedisConfig.url)
+    end
+
+    def redis_key
+      "gitlab:exclusive_lease:#{@key}"
+    end
+  end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index e2a85f2982593dbfb118d619dc3a320109887168..172c5441e36fade9cbc61b78cb59437538d9fe10 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -45,10 +45,13 @@ def import_pull_requests
                                                     direction: :asc).each do |raw_data|
           pull_request = PullRequestFormatter.new(project, raw_data)
 
-          if !pull_request.cross_project? && pull_request.valid?
-            merge_request = MergeRequest.create!(pull_request.attributes)
-            import_comments(pull_request.number, merge_request)
-            import_comments_on_diff(pull_request.number, merge_request)
+          if pull_request.valid?
+            merge_request = MergeRequest.new(pull_request.attributes)
+
+            if merge_request.save
+              import_comments(pull_request.number, merge_request)
+              import_comments_on_diff(pull_request.number, merge_request)
+            end
           end
         end
 
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index f96fed0f5cfb1359418a64bae9ce86d04e1a5b81..4e507b090e8d3b94ed1237098e862c089a6f019e 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -17,16 +17,12 @@ def attributes
         }
       end
 
-      def cross_project?
-        source_repo.id != target_repo.id
-      end
-
       def number
         raw_data.number
       end
 
       def valid?
-        source_branch.present? && target_branch.present?
+        !cross_project? && source_branch.present? && target_branch.present?
       end
 
       private
@@ -53,6 +49,10 @@ def body
         raw_data.body || ""
       end
 
+      def cross_project?
+        source_repo.present? && target_repo.present? && source_repo.id != target_repo.id
+      end
+
       def description
         formatter.author_line(author) + body
       end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
new file mode 100644
index 0000000000000000000000000000000000000000..50b0dd32380325268496a65674056c676d30f4a8
--- /dev/null
+++ b/lib/gitlab/middleware/go.rb
@@ -0,0 +1,50 @@
+# A dumb middleware that returns a Go HTML document if the go-get=1 query string
+# is used irrespective if the namespace/project exists
+module Gitlab
+  module Middleware
+    class Go
+      def initialize(app)
+        @app = app
+      end
+
+      def call(env)
+        request = Rack::Request.new(env)
+
+        if go_request?(request)
+          render_go_doc(request)
+        else
+          @app.call(env)
+        end
+      end
+
+      private
+
+      def render_go_doc(request)
+        body = go_body(request)
+        response = Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' })
+        response.finish
+      end
+
+      def go_request?(request)
+        request["go-get"].to_i == 1 && request.env["PATH_INFO"].present?
+      end
+
+      def go_body(request)
+        base_url = Gitlab.config.gitlab.url
+        # Go subpackages may be in the form of namespace/project/path1/path2/../pathN
+        # We can just ignore the paths and leave the namespace/project
+        path_info = request.env["PATH_INFO"]
+        path_info.sub!(/^\//, '')
+        project_path = path_info.split('/').first(2).join('/')
+        request_url = URI.join(base_url, project_path)
+        domain_path = strip_url(request_url.to_s)
+
+        "<!DOCTYPE html><html><head><meta content='#{domain_path} git #{request_url}.git' name='go-import'></head></html>\n";
+      end
+
+      def strip_url(url)
+        url.gsub(/\Ahttps?:\/\//, '')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 70de6a74e767a928fde2549f9582f22135642bdf..0607a8b95927940941509ff216ad98dd3fd839b0 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -2,8 +2,8 @@ module Gitlab
   class ProjectSearchResults < SearchResults
     attr_reader :project, :repository_ref
 
-    def initialize(project_id, query, repository_ref = nil)
-      @project = Project.find(project_id)
+    def initialize(project, query, repository_ref = nil)
+      @project = project
       @repository_ref = if repository_ref.present?
                           repository_ref
                         else
@@ -73,7 +73,7 @@ def wiki_blobs
     end
 
     def notes
-      Note.where(project_id: limit_project_ids).user.search(query).order('updated_at DESC')
+      project.notes.user.search(query).order('updated_at DESC')
     end
 
     def commits
@@ -84,8 +84,8 @@ def commits
       end
     end
 
-    def limit_project_ids
-      [project.id]
+    def project_ids_relation
+      project
     end
   end
 end
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index da1c15fef6193acc4e46a9fb14d70804947e2c4b..97d1edab9c13510ad7641196245e0a6d64c64098 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -63,7 +63,7 @@ def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
       end
 
       # This method provide a sample data generated with
-      # existing project and commits to test web hooks
+      # existing project and commits to test webhooks
       def build_sample(project, user)
         commits = project.repository.commits(project.default_branch, nil, 3)
         ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
diff --git a/lib/gitlab/redis_config.rb b/lib/gitlab/redis_config.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4949c6db5392b88b5fa1c5708957d7bf72251896
--- /dev/null
+++ b/lib/gitlab/redis_config.rb
@@ -0,0 +1,30 @@
+module Gitlab
+  class RedisConfig
+    attr_reader :url
+
+    def self.url
+      new.url
+    end
+    
+    def self.redis_store_options
+      url = new.url
+      redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(url)
+      # Redis::Store does not handle Unix sockets well, so let's do it for them
+      redis_uri = URI.parse(url)
+      if redis_uri.scheme == 'unix'
+        redis_config_hash[:path] = redis_uri.path
+      end
+      redis_config_hash
+    end
+
+    def initialize(rails_env=nil)
+      rails_env ||= Rails.env
+      config_file = File.expand_path('../../../config/resque.yml', __FILE__)
+  
+      @url = "redis://localhost:6379"
+      if File.exists?(config_file)
+        @url =YAML.load_file(config_file)[rails_env]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 2ab2d4af797d5b09451315cb2a38c753cd4d9390..f13528a2eea549b2a929ea501edc1d6cc7da6747 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -2,12 +2,12 @@ module Gitlab
   class SearchResults
     attr_reader :query
 
-    # Limit search results by passed project ids
+    # Limit search results by passed projects
     # It allows us to search only for projects user has access to
-    attr_reader :limit_project_ids
+    attr_reader :limit_projects
 
-    def initialize(limit_project_ids, query)
-      @limit_project_ids = limit_project_ids || Project.all
+    def initialize(limit_projects, query)
+      @limit_projects = limit_projects || Project.all
       @query = Shellwords.shellescape(query) if query.present?
     end
 
@@ -27,7 +27,8 @@ def objects(scope, page = nil)
     end
 
     def total_count
-      @total_count ||= projects_count + issues_count + merge_requests_count + milestones_count
+      @total_count ||= projects_count + issues_count + merge_requests_count +
+        milestones_count
     end
 
     def projects_count
@@ -53,27 +54,29 @@ def empty?
     private
 
     def projects
-      Project.where(id: limit_project_ids).search(query)
+      limit_projects.search(query)
     end
 
     def issues
-      issues = Issue.where(project_id: limit_project_ids)
+      issues = Issue.where(project_id: project_ids_relation)
+
       if query =~ /#(\d+)\z/
         issues = issues.where(iid: $1)
       else
         issues = issues.full_search(query)
       end
+
       issues.order('updated_at DESC')
     end
 
     def milestones
-      milestones = Milestone.where(project_id: limit_project_ids)
+      milestones = Milestone.where(project_id: project_ids_relation)
       milestones = milestones.search(query)
       milestones.order('updated_at DESC')
     end
 
     def merge_requests
-      merge_requests = MergeRequest.in_projects(limit_project_ids)
+      merge_requests = MergeRequest.in_projects(project_ids_relation)
       if query =~ /[#!](\d+)\z/
         merge_requests = merge_requests.where(iid: $1)
       else
@@ -89,5 +92,9 @@ def default_scope
     def per_page
       20
     end
+
+    def project_ids_relation
+      limit_projects.select(:id).reorder(nil)
+    end
   end
 end
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index addda95be2ba676f88fb05ffb516606aeca9d29a..e0e74ff8359f637faf72211766d5f88cb7268a8d 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -2,10 +2,10 @@ module Gitlab
   class SnippetSearchResults < SearchResults
     include SnippetsHelper
 
-    attr_reader :limit_snippet_ids
+    attr_reader :limit_snippets
 
-    def initialize(limit_snippet_ids, query)
-      @limit_snippet_ids = limit_snippet_ids
+    def initialize(limit_snippets, query)
+      @limit_snippets = limit_snippets
       @query = query
     end
 
@@ -35,11 +35,11 @@ def snippet_blobs_count
     private
 
     def snippet_titles
-      Snippet.where(id: limit_snippet_ids).search(query).order('updated_at DESC')
+      limit_snippets.search(query).order('updated_at DESC')
     end
 
     def snippet_blobs
-      Snippet.where(id: limit_snippet_ids).search_code(query).order('updated_at DESC')
+      limit_snippets.search_code(query).order('updated_at DESC')
     end
 
     def default_scope
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 4885baf95265ff1a9d97b22c966c522452c25cf5..d1b42c1f9b97a0c45bd0bfde190b129e6ec1aa9f 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -3,7 +3,7 @@ module UserAccess
     def self.allowed?(user)
       return false if user.blocked?
 
-      if user.requires_ldap_check?
+      if user.requires_ldap_check? && user.try_obtain_ldap_lease
         return false unless Gitlab::LDAP::Access.allowed?(user)
       end
 
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index f221afcf73a21b5bb1c802653bd29afa5cf7625a..51e746ef923637b9a11a8273c55fc465459eab05 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -4,16 +4,16 @@ namespace :cache do
 
   desc "GitLab | Clear redis cache"
   task :clear => :environment do
-    redis_store = Rails.cache.instance_variable_get(:@data)
+    redis = Redis.new(url: Gitlab::RedisConfig.url)
     cursor = REDIS_SCAN_START_STOP
     loop do
-      cursor, keys = redis_store.scan(
+      cursor, keys = redis.scan(
         cursor,
         match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", 
         count: CLEAR_BATCH_SIZE
       )
 
-      redis_store.del(*keys) if keys.any?
+      redis.del(*keys) if keys.any?
 
       break if cursor == REDIS_SCAN_START_STOP
     end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
new file mode 100644
index 0000000000000000000000000000000000000000..ebe301c1fc7ca2aeea67a6e85d66fefe5d450101
--- /dev/null
+++ b/lib/tasks/gemojione.rake
@@ -0,0 +1,121 @@
+# This task will generate a standard and Retina sprite of all of the current
+# Gemojione Emojis, with the accompanying SCSS map.
+#
+# It will not appear in `rake -T` output, and the dependent gems are not
+# included in the Gemfile by default, because this task will only be needed
+# occasionally, such as when new Emojis are added to Gemojione.
+
+begin
+  require 'sprite_factory'
+  require 'rmagick'
+rescue LoadError
+  # noop
+end
+
+namespace :gemojione do
+  task sprite: :environment do
+    check_requirements!
+
+    SIZE   = 20
+    RETINA = SIZE * 2
+
+    Dir.mktmpdir do |tmpdir|
+      # Copy the Gemojione assets to the temporary folder for resizing
+      FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+
+      Dir.chdir(tmpdir) do
+        Dir["**/*.png"].each do |png|
+          resize!(File.join(tmpdir, png), SIZE)
+        end
+      end
+
+      style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss))
+
+      # Combine the resized assets into a packed sprite and re-generate the SCSS
+      SpriteFactory.cssurl = "image-url('$IMAGE')"
+      SpriteFactory.run!(File.join(tmpdir, 'images'), {
+        output_style: style_path,
+        output_image: "app/assets/images/emoji.png",
+        selector:     '.emoji-',
+        style:        :scss,
+        nocomments:   true,
+        pngcrush:     true,
+        layout:       :packed
+      })
+
+      # SpriteFactory's SCSS is a bit too verbose for our purposes here, so
+      # let's simplify it
+      system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path}))
+      system(%Q(sed -i '' "s/ no-repeat//" #{style_path}))
+
+      # Append a generic rule that applies to all Emojis
+      File.open(style_path, 'a') do |f|
+        f.puts
+        f.puts <<-CSS.strip_heredoc
+        .emoji-icon {
+          background-image: image-url('emoji.png');
+          background-repeat: no-repeat;
+          height: #{SIZE}px;
+          width: #{SIZE}px;
+
+          @media only screen and (-webkit-min-device-pixel-ratio: 2),
+                 only screen and (min--moz-device-pixel-ratio: 2),
+                 only screen and (-o-min-device-pixel-ratio: 2/1),
+                 only screen and (min-device-pixel-ratio: 2),
+                 only screen and (min-resolution: 192dpi),
+                 only screen and (min-resolution: 2dppx) {
+            background-image: image-url('emoji@2x.png');
+            background-size: 840px 820px;
+          }
+        }
+        CSS
+      end
+    end
+
+    # Now do it again but for Retina
+    Dir.mktmpdir do |tmpdir|
+      # Copy the Gemojione assets to the temporary folder for resizing
+      FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+
+      Dir.chdir(tmpdir) do
+        Dir["**/*.png"].each do |png|
+          resize!(File.join(tmpdir, png), RETINA)
+        end
+      end
+
+      # Combine the resized assets into a packed sprite and re-generate the SCSS
+      SpriteFactory.run!(File.join(tmpdir, 'images'), {
+        output_image: "app/assets/images/emoji@2x.png",
+        style:        false,
+        nocomments:   true,
+        pngcrush:     true,
+        layout:       :packed
+      })
+    end
+  end
+
+  def check_requirements!
+    return if defined?(SpriteFactory) && defined?(Magick)
+
+    puts <<-MSG.strip_heredoc
+      This task is disabled by default and should only be run when the Gemojione
+      gem is updated with new Emojis.
+
+      To enable this task, *temporarily* add the following lines to Gemfile and
+      re-bundle:
+
+      gem 'sprite-factory'
+      gem 'rmagick'
+    MSG
+
+    exit 1
+  end
+
+  def resize!(image_path, size)
+    # Resize the image in-place, save it, and free the object
+    image = Magick::Image.read(image_path).first
+    image.resize!(size, size)
+    image.write(image_path) { self.quality = 100 }
+    image.destroy!
+  end
+end
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index 76e443e55ee2564198f1f1658b503c75aec21764..cc0f668474eda09b889908580466d14e86fbb59e 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -1,13 +1,13 @@
 namespace :gitlab do
   namespace :web_hook do
-    desc "GitLab | Adds a web hook to the projects"
+    desc "GitLab | Adds a webhook to the projects"
     task :add => :environment do
       web_hook_url = ENV['URL']
       namespace_path = ENV['NAMESPACE']
 
       projects = find_projects(namespace_path)
 
-      puts "Adding web hook '#{web_hook_url}' to:"
+      puts "Adding webhook '#{web_hook_url}' to:"
       projects.find_each(batch_size: 1000) do |project|
         print "- #{project.name} ... "
         web_hook = project.hooks.new(url: web_hook_url)
@@ -20,7 +20,7 @@ namespace :gitlab do
       end
     end
 
-    desc "GitLab | Remove a web hook from the projects"
+    desc "GitLab | Remove a webhook from the projects"
     task :rm => :environment do
       web_hook_url = ENV['URL']
       namespace_path = ENV['NAMESPACE']
@@ -28,12 +28,12 @@ namespace :gitlab do
       projects = find_projects(namespace_path)
       projects_ids = projects.pluck(:id)
 
-      puts "Removing web hooks with the url '#{web_hook_url}' ... "
+      puts "Removing webhooks with the url '#{web_hook_url}' ... "
       count = WebHook.where(url: web_hook_url, project_id: projects_ids, type: 'ProjectHook').delete_all
-      puts "#{count} web hooks were removed."
+      puts "#{count} webhooks were removed."
     end
 
-    desc "GitLab | List web hooks"
+    desc "GitLab | List webhooks"
     task :list => :environment do
       namespace_path = ENV['NAMESPACE']
 
@@ -43,7 +43,7 @@ namespace :gitlab do
         puts "#{hook.project.name.truncate(20).ljust(20)} -> #{hook.url}"
       end
 
-      puts "\n#{web_hooks.size} web hooks found."
+      puts "\n#{web_hooks.size} webhooks found."
     end
   end
 
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 0985ef3a669e5d86ccf42ed5dacb104377008704..2cf7a25a0fde15226c7e2ce3e0cb8e7a9ea23b5d 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -46,20 +46,11 @@ namespace :spec do
     run_commands(cmds)
   end
 
-  desc 'GitLab | Rspec | Run benchmark specs'
-  task :benchmark do
-    cmds = [
-      %W(rake gitlab:setup),
-      %W(rspec spec --tag @benchmark)
-    ]
-    run_commands(cmds)
-  end
-
   desc 'GitLab | Rspec | Run other specs'
   task :other do
     cmds = [
       %W(rake gitlab:setup),
-      %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services --tag ~@benchmark)
+      %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
     ]
     run_commands(cmds)
   end
@@ -69,7 +60,7 @@ desc "GitLab | Run specs"
 task :spec do
   cmds = [
     %W(rake gitlab:setup),
-    %W(rspec spec --tag ~@benchmark),
+    %W(rspec spec),
   ]
   run_commands(cmds)
 end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 3acfc6e207505317c3c494960d2bfdd78dce41d4..01d23b89bb7e5fe892688d73aec17dff8436573b 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -4,53 +4,59 @@ namespace :spinach do
   namespace :project do
     desc "GitLab | Spinach | Run project commits, issues and merge requests spinach features"
     task :half do
-      cmds = [
-        %W(rake gitlab:setup),
-        %W(spinach --tags @project_commits,@project_issues,@project_merge_requests),
-      ]
-      run_commands(cmds)
+      run_spinach_tests('@project_commits,@project_issues,@project_merge_requests')
     end
 
     desc "GitLab | Spinach | Run remaining project spinach features"
     task :rest do
-      cmds = [
-        %W(rake gitlab:setup),
-        %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests),
-      ]
-      run_commands(cmds)
+      run_spinach_tests('~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests')
     end
   end
 
   desc "GitLab | Spinach | Run project spinach features"
   task :project do
-    cmds = [
-      %W(rake gitlab:setup),
-      %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
-    ]
-    run_commands(cmds)
+    run_spinach_tests('~@admin,~@dashboard,~@profile,~@public,~@snippets')
   end
 
   desc "GitLab | Spinach | Run other spinach features"
   task :other do
-    cmds = [
-      %W(rake gitlab:setup),
-      %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
-    ]
-    run_commands(cmds)
+    run_spinach_tests('@admin,@dashboard,@profile,@public,@snippets')
+  end
+
+  desc "GitLab | Spinach | Run other spinach features"
+  task :builds do
+    run_spinach_tests('@builds')
   end
 end
 
 desc "GitLab | Run spinach"
 task :spinach do
-  cmds = [
-    %W(rake gitlab:setup),
-    %W(spinach),
-  ]
-  run_commands(cmds)
+  run_spinach_tests(nil)
+end
+
+def run_command(cmd)
+  system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
 end
 
-def run_commands(cmds)
-  cmds.each do |cmd|
-    system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
+def run_spinach_command(args)
+  run_command(%w(spinach -r rerun) + args)
+end
+
+def run_spinach_tests(tags)
+  #run_command(%w(rake gitlab:setup)) or raise('gitlab:setup failed!')
+
+  success = run_spinach_command(%W(--tags #{tags}))
+  3.times do |_|
+    break if success
+    break unless File.exists?('tmp/spinach-rerun.txt')
+
+    tests = File.foreach('tmp/spinach-rerun.txt').map(&:chomp)
+    puts ''
+    puts "Spinach tests for #{tags}: Retrying tests... #{tests}".red
+    puts ''
+    sleep(3)
+    success = run_spinach_command(tests)
   end
+
+  raise("spinach tests for #{tags} failed!") unless success
 end
diff --git a/public/logo.svg b/public/logo.svg
index c09785cb96f49f6f59f1d62012016b477e75eae6..fc4553137f73dfbf2f13a0c0f4ea878742032243 100644
--- a/public/logo.svg
+++ b/public/logo.svg
@@ -1,26 +1,9 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
-    <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
-    <title>Slice 1</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
-        <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
-            <g id="Page-1" sketch:type="MSShapeGroup">
-                <g id="Fill-1-+-Group-24">
-                    <g id="Group-24">
-                        <g id="Group">
-                            <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
-                            <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
-                            <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
-                            <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
-                            <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
-                            <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
-                            <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
-                        </g>
-                    </g>
-                </g>
-            </g>
-        </g>
-    </g>
-</svg>
\ No newline at end of file
+<svg width="210" height="210" viewBox="0 0 210 210" xmlns="http://www.w3.org/2000/svg">
+  <path d="M105.0614 203.655l38.64-118.921h-77.28l38.64 118.921z" fill="#e24329"/>
+  <path d="M105.0614 203.6548l-38.64-118.921h-54.153l92.793 118.921z" fill="#fc6d26"/>
+  <path d="M12.2685 84.7341l-11.742 36.139c-1.071 3.296.102 6.907 2.906 8.944l101.629 73.838-92.793-118.921z" fill="#fca326"/>
+  <path d="M12.2685 84.7342h54.153l-23.273-71.625c-1.197-3.686-6.411-3.685-7.608 0l-23.272 71.625z" fill="#e24329"/>
+  <path d="M105.0614 203.6548l38.64-118.921h54.153l-92.793 118.921z" fill="#fc6d26"/>
+  <path d="M197.8544 84.7341l11.742 36.139c1.071 3.296-.102 6.907-2.906 8.944l-101.629 73.838 92.793-118.921z" fill="#fca326"/>
+  <path d="M197.8544 84.7342h-54.153l23.273-71.625c1.197-3.686 6.411-3.685 7.608 0l23.272 71.625z" fill="#e24329"/>
+</svg>
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index b6f076a90c3b3745d68156951c75ce93bf1055ef..4a7ee7dbb64ddc642b3a71578f3b83bc093ac4f3 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,16 +1,30 @@
 #!/bin/bash
 
+retry() {
+    for i in $(seq 1 3); do
+        if eval "$@"; then
+            return 0
+        fi
+        sleep 3s
+        echo "Retrying..."
+    done
+    return 1
+}
+
 if [ -f /.dockerinit ]; then
     mkdir -p vendor
-    if [ ! -e vendor/phantomjs_1.9.8-0jessie_amd64.deb ]; then
+
+    # Install phantomjs package
+    pushd vendor
+    if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
         wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
-        mv phantomjs_1.9.8-0jessie_amd64.deb vendor/
     fi
-    dpkg -i vendor/phantomjs_1.9.8-0jessie_amd64.deb
+    dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+    popd
 
-    apt-get update -qq
-    apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
-        libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
+    # Try to install packages
+    retry 'apt-get update -yqqq; apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
+      libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip'
 
     cp config/database.yml.mysql config/database.yml
     sed -i 's/username:.*/username: root/g' config/database.yml
@@ -20,7 +34,7 @@ if [ -f /.dockerinit ]; then
     cp config/resque.yml.example config/resque.yml
     sed -i 's/localhost/redis/g' config/resque.yml
 
-    export FLAGS=(--path vendor)
+    export FLAGS=(--path vendor --retry 3)
 else
     export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
     cp config/database.yml.mysql config/database.yml
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb
deleted file mode 100644
index b57a33004a48ca6afa01dfa114eaac3e2dc46eca..0000000000000000000000000000000000000000
--- a/spec/benchmarks/finders/issues_finder_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'spec_helper'
-
-describe IssuesFinder, benchmark: true do
-  describe '#execute' do
-    let(:user) { create(:user) }
-    let(:project) { create(:project, :public) }
-
-    let(:label1) { create(:label, project: project, title: 'A') }
-    let(:label2) { create(:label, project: project, title: 'B') }
-
-    before do
-      10.times do |n|
-        issue = create(:issue, author: user, project: project)
-
-        if n > 4
-          create(:label_link, label: label1, target: issue)
-          create(:label_link, label: label2, target: issue)
-        end
-      end
-    end
-
-    describe 'retrieving issues without labels' do
-      let(:finder) do
-        IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
-                               state: 'opened')
-      end
-
-      benchmark_subject { finder.execute }
-
-      it { is_expected.to iterate_per_second(2000) }
-    end
-
-    describe 'retrieving issues with labels' do
-      let(:finder) do
-        IssuesFinder.new(user, scope: 'all', label_name: label1.title,
-                               state: 'opened')
-      end
-
-      benchmark_subject { finder.execute }
-
-      it { is_expected.to iterate_per_second(1000) }
-    end
-
-    describe 'retrieving issues for a single project' do
-      let(:finder) do
-        IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
-                               state: 'opened', project_id: project.id)
-      end
-
-      benchmark_subject { finder.execute }
-
-      it { is_expected.to iterate_per_second(2000) }
-    end
-  end
-end
diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb
deleted file mode 100644
index 551ce21840d6869e8314300f4aa4f2b41a8f7c05..0000000000000000000000000000000000000000
--- a/spec/benchmarks/finders/trending_projects_finder_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe TrendingProjectsFinder, benchmark: true do
-  describe '#execute' do
-    let(:finder) { described_class.new }
-    let(:user)   { create(:user) }
-
-    # to_a is used to force actually running the query (instead of just building
-    # it).
-    benchmark_subject { finder.execute(user).non_archived.to_a }
-
-    it { is_expected.to iterate_per_second(500) }
-  end
-end
diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
deleted file mode 100644
index 3855763b200b8ffafd01adc0f7d324f117c3b8a9..0000000000000000000000000000000000000000
--- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::ReferenceFilter, benchmark: true do
-  let(:input) do
-    html = <<-EOF
-<p>Hello @alice and @bob, how are you doing today?</p>
-<p>This is simple @dummy text to see how the @ReferenceFilter class performs
-when @processing HTML.</p>
-    EOF
-
-    Nokogiri::HTML.fragment(html)
-  end
-
-  let(:project) { create(:empty_project) }
-
-  let(:filter) { described_class.new(input, project: project) }
-
-  describe '#replace_text_nodes_matching' do
-    let(:iterations) { 6000 }
-
-    describe 'with identical input and output HTML' do
-      benchmark_subject do
-        filter.replace_text_nodes_matching(User.reference_pattern) do |content|
-          content
-        end
-      end
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-
-    describe 'with different input and output HTML' do
-      benchmark_subject do
-        filter.replace_text_nodes_matching(User.reference_pattern) do |content|
-          '@eve'
-        end
-      end
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-  end
-end
diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb
deleted file mode 100644
index a94afc4c40d899a89b0813e5ccfef31799ec7eee..0000000000000000000000000000000000000000
--- a/spec/benchmarks/models/milestone_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Milestone, benchmark: true do
-  describe '#sort_issues' do
-    let(:milestone) { create(:milestone) }
-
-    let(:issue1) { create(:issue, milestone: milestone) }
-    let(:issue2) { create(:issue, milestone: milestone) }
-    let(:issue3) { create(:issue, milestone: milestone) }
-
-    let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
-
-    benchmark_subject { milestone.sort_issues(issue_ids) }
-
-    it { is_expected.to iterate_per_second(500) }
-  end
-end
diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb
deleted file mode 100644
index cee0949edc53476ebe3a48d41d68b8bb2b9afd50..0000000000000000000000000000000000000000
--- a/spec/benchmarks/models/project_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Project, benchmark: true do
-  describe '.trending' do
-    let(:group)    { create(:group) }
-    let(:project1) { create(:empty_project, :public, group: group) }
-    let(:project2) { create(:empty_project, :public, group: group) }
-
-    let(:iterations) { 500 }
-
-    before do
-      2.times do
-        create(:note_on_commit, project: project1)
-      end
-
-      create(:note_on_commit, project: project2)
-    end
-
-    describe 'without an explicit start date' do
-      benchmark_subject { described_class.trending.to_a }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-
-    describe 'with an explicit start date' do
-      let(:date) { 1.month.ago }
-
-      benchmark_subject { described_class.trending(date).to_a }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-  end
-
-  describe '.find_with_namespace' do
-    let(:group)   { create(:group, name: 'sisinmaru') }
-    let(:project) { create(:project, name: 'maru', namespace: group) }
-
-    describe 'using a capitalized namespace' do
-      benchmark_subject { described_class.find_with_namespace('sisinmaru/MARU') }
-
-      it { is_expected.to iterate_per_second(600) }
-    end
-
-    describe 'using a lowercased namespace' do
-      benchmark_subject { described_class.find_with_namespace('sisinmaru/maru') }
-
-      it { is_expected.to iterate_per_second(600) }
-    end
-  end
-end
diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
deleted file mode 100644
index 8b039ef731738b6f6afb8873b29ecded195f5c48..0000000000000000000000000000000000000000
--- a/spec/benchmarks/models/project_team_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe ProjectTeam, benchmark: true do
-  describe '#max_member_access' do
-    let(:group)   { create(:group) }
-    let(:project) { create(:empty_project, group: group) }
-    let(:user)    { create(:user) }
-
-    before do
-      project.team << [user, :master]
-
-      5.times do
-        project.team << [create(:user), :reporter]
-
-        project.group.add_user(create(:user), :reporter)
-      end
-    end
-
-    benchmark_subject { project.team.max_member_access(user.id) }
-
-    it { is_expected.to iterate_per_second(35000) }
-  end
-end
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb
deleted file mode 100644
index 1be7a8d3ed9dfb591f8915245191565f64788ffb..0000000000000000000000000000000000000000
--- a/spec/benchmarks/models/user_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'spec_helper'
-
-describe User, benchmark: true do
-  describe '.all' do
-    before do
-      10.times { create(:user) }
-    end
-
-    benchmark_subject { User.all.to_a }
-
-    it { is_expected.to iterate_per_second(500) }
-  end
-
-  describe '.by_login' do
-    before do
-      %w{Alice Bob Eve}.each do |name|
-        create(:user,
-               email: "#{name}@gitlab.com",
-               username: name,
-               name: name)
-      end
-    end
-
-    # The iteration count is based on the query taking little over 1 ms when
-    # using PostgreSQL.
-    let(:iterations) { 900 }
-
-    describe 'using a capitalized username' do
-      benchmark_subject { User.by_login('Alice') }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-
-    describe 'using a lowercase username' do
-      benchmark_subject { User.by_login('alice') }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-
-    describe 'using a capitalized Email address' do
-      benchmark_subject { User.by_login('Alice@gitlab.com') }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-
-    describe 'using a lowercase Email address' do
-      benchmark_subject { User.by_login('alice@gitlab.com') }
-
-      it { is_expected.to iterate_per_second(iterations) }
-    end
-  end
-
-  describe '.find_by_any_email' do
-    let(:user) { create(:user) }
-
-    describe 'using a user with only a single Email address' do
-      let(:email) { user.email }
-
-      benchmark_subject { User.find_by_any_email(email) }
-
-      it { is_expected.to iterate_per_second(1000) }
-    end
-
-    describe 'using a user with multiple Email addresses' do
-      let(:email) { user.emails.first.email }
-
-      benchmark_subject { User.find_by_any_email(email) }
-
-      before do
-        10.times do
-          user.emails.create(email: FFaker::Internet.email)
-        end
-      end
-
-      it { is_expected.to iterate_per_second(1000) }
-    end
-  end
-end
diff --git a/spec/benchmarks/services/projects/create_service_spec.rb b/spec/benchmarks/services/projects/create_service_spec.rb
deleted file mode 100644
index 25ed48c34fdb5c8464f0eefbc50e92dd41095540..0000000000000000000000000000000000000000
--- a/spec/benchmarks/services/projects/create_service_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CreateService, benchmark: true do
-  describe '#execute' do
-    let(:user) { create(:user, :admin) }
-
-    let(:group) do
-      group = create(:group)
-
-      create(:group_member, group: group, user: user)
-
-      group
-    end
-
-    benchmark_subject do
-      name    = SecureRandom.hex
-      service = described_class.new(user,
-                                    name:             name,
-                                    path:             name,
-                                    namespace_id:     group.id,
-                                    visibility_level: Gitlab::VisibilityLevel::PUBLIC)
-
-      service.execute
-    end
-
-    it { is_expected.to iterate_per_second(0.5) }
-  end
-end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 883bbaedd4e28941eafc7b6f3a1cba9b3c98a936..70ed8f3a62e1d2addb47766fe0f606cbabdcdd9c 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -2,7 +2,7 @@
 
 describe Projects::ForksController do
   let(:user) { create(:user) }
-  let(:project) { create(:project, visibility_level: Project::PUBLIC) }
+  let(:project) { create(:project, :public) }
   let(:forked_project) { Projects::ForkService.new(project, user).execute }
   let(:group) { create(:group, owner: forked_project.creator) }
 
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 09ec4f18f9d2394dc2276e6fe1d603b121d12c1c..0ddbec9eac21cee9bba8ff16f8e90cc310a04f2e 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -2,30 +2,41 @@
 
 describe Projects::RepositoriesController do
   let(:project) { create(:project) }
-  let(:user)    { create(:user) }
 
   describe "GET archive" do
-    before do
-      sign_in(user)
-      project.team << [user, :developer]
-    end
-
-    it "uses Gitlab::Workhorse" do
-      expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
+    context 'as a guest' do
+      it 'responds with redirect in correct format' do
+        get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
 
-      get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+        expect(response.content_type).to start_with 'text/html'
+        expect(response).to be_redirect
+      end
     end
 
-    context "when the service raises an error" do
+    context 'as a user' do
+      let(:user) { create(:user) }
 
       before do
-        allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
+        project.team << [user, :developer]
+        sign_in(user)
       end
+      it "uses Gitlab::Workhorse" do
+        expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
 
-      it "renders Not Found" do
         get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+      end
+
+      context "when the service raises an error" do
+
+        before do
+          allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
+        end
+
+        it "renders Not Found" do
+          get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
 
-        expect(response.status).to eq(404)
+          expect(response.status).to eq(404)
+        end
       end
     end
   end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 6eee4dfe229c2b894a680d1a04c94a69a8699c37..1893e946f5cd970b685cbefd774d5d196936cdcd 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -9,19 +9,6 @@
 
   describe "GET show" do
 
-    context "when requested by `go get`" do
-      render_views
-
-      it "renders the go-import meta tag" do
-        get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project"
-
-        expect(response.body).to include("name='go-import'")
-
-        content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git"
-        expect(response.body).to include("content='#{content}'")
-      end
-    end
-
     context "rendering default project view" do
       render_views
 
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
index b493a6968ff9b25faf535b347d833fd5046aab44..0f13b2c10209c72dcfb7903acddbca801057fddf 100644
--- a/spec/factories/personal_snippets.rb
+++ b/spec/factories/personal_snippets.rb
@@ -1,15 +1,4 @@
 FactoryGirl.define do
   factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
-    trait :public do
-      visibility_level PersonalSnippet::PUBLIC
-    end
-
-    trait :internal do
-      visibility_level PersonalSnippet::INTERNAL
-    end
-
-    trait :private do
-      visibility_level PersonalSnippet::PRIVATE
-    end
   end
 end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
index 154442bd3dbceb6cef1af31b3f8b31fa137858d4..d681a2c8483d55ae3aa53def3c11c8dd5cd1a566 100644
--- a/spec/factories/project_snippets.rb
+++ b/spec/factories/project_snippets.rb
@@ -1,9 +1,5 @@
 FactoryGirl.define do
-  factory :project_snippet do
+  factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
     project
-    author
-    title
-    content
-    file_name
   end
 end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index b9127b3d75e9e0e6c7ac42c1a22c7a1fea7029bb..365f12a0c952a5ba2ab6757aaa21847c9466a445 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -12,5 +12,17 @@
     title
     content
     file_name
+
+    trait :public do
+      visibility_level Snippet::PUBLIC
+    end
+
+    trait :internal do
+      visibility_level Snippet::INTERNAL
+    end
+
+    trait :private do
+      visibility_level Snippet::PRIVATE
+    end
   end
 end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 591866b40d46521c012c2a0e9f8b3c131a6126a4..f6e33f651c47d22655e6ea6f9d01088909c263b0 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
 require 'rails_helper'
 
 feature 'Issue filtering by Milestone', feature: true do
-  include Select2Helper
-
   let(:project)   { create(:project, :public) }
   let(:milestone) { create(:milestone, project: project) }
 
@@ -31,6 +29,9 @@ def visit_issues(project)
   end
 
   def filter_by_milestone(title)
-    select2(title, from: '#milestone_title')
+    find(".js-milestone-select").click
+    sleep 0.5
+    find(".milestone-filter a", text: title).click
+    sleep 1
   end
 end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index f70214e11223ae0928d39360f67c1b5c84bc1873..1b2fd1bab1086bd22b9e7e6c2d25364477cc37e3 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
 require 'rails_helper'
 
 feature 'Merge Request filtering by Milestone', feature: true do
-  include Select2Helper
-
   let(:project)   { create(:project, :public) }
   let(:milestone) { create(:milestone, project: project) }
 
@@ -31,6 +29,9 @@ def visit_merge_requests(project)
   end
 
   def filter_by_milestone(title)
-    select2(title, from: '#milestone_title')
+    find(".js-milestone-select").click
+    sleep 0.5
+    find(".milestone-filter a", text: title).click
+    sleep 1
   end
 end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 1a360cd1ebc821a14b5e562c61bf58dc25bbbff6..d9a8058efd9453ed2001b610d03043d09a723a9f 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -22,7 +22,7 @@
       it 'should be valid' do
         is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
         expect(find('.js-main-target-form input[type=submit]').value).
-          to eq('Add Comment')
+          to eq('Comment')
         page.within('.js-main-target-form') do
           expect(page).not_to have_link('Cancel')
         end
@@ -49,7 +49,7 @@
         page.within('.js-main-target-form') do
           fill_in 'note[note]', with: 'This is awsome!'
           find('.js-md-preview-button').click
-          click_button 'Add Comment'
+          click_button 'Comment'
         end
       end
 
@@ -202,7 +202,7 @@
         before do
           page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
             fill_in 'note[note]', with: 'Another comment on line 10'
-            click_button('Add Comment')
+            click_button('Comment')
           end
         end
 
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 1b4ffc2d7176e1fa2ef6d896c467c747c00f1323..7fdc5e5d7aab0ba5c322363d18cc18db69acd167 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -5,15 +5,14 @@
   let(:user1) { create :user }
   let(:group) { create :group }
 
-  let(:project1) { create(:empty_project, :public,   group: group) }
-  let(:project2) { create(:empty_project, :private,  group: group) }
-  
+  let(:project1) { create(:empty_project, :public,  group: group) }
+  let(:project2) { create(:empty_project, :private, group: group) }
 
   context ':all filter' do
     before do
-      @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
-      @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
-      @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
+      @snippet1 = create(:personal_snippet, :private)
+      @snippet2 = create(:personal_snippet, :internal)
+      @snippet3 = create(:personal_snippet, :public)
     end
 
     it "returns all private and internal snippets" do
@@ -31,9 +30,9 @@
 
   context ':by_user filter' do
     before do
-      @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
-      @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
-      @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
+      @snippet1 = create(:personal_snippet, :private,  author: user)
+      @snippet2 = create(:personal_snippet, :internal, author: user)
+      @snippet3 = create(:personal_snippet, :public,   author: user)
     end
 
     it "returns all public and internal snippets" do
@@ -75,9 +74,9 @@
 
   context 'by_project filter' do
     before do
-      @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
-      @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
-      @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
+      @snippet1 = create(:project_snippet, :private,  project: project1)
+      @snippet2 = create(:project_snippet, :internal, project: project1)
+      @snippet3 = create(:project_snippet, :public,   project: project1)
     end
 
     it "returns public snippets for unauthorized user" do
@@ -93,7 +92,7 @@
     end
 
     it "returns all snippets for project members" do
-      project1.team << [user, :developer] 
+      project1.team << [user, :developer]
       snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
       expect(snippets).to include(@snippet1, @snippet2, @snippet3)
     end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index aafc24397a9af0ee35c12acd942d0a20090ccd6d..cd7596a763d9930c37afc812700b391fdd554017 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -58,7 +58,7 @@
 
   describe "skip_level?" do
     describe "forks" do
-      let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+      let(:project) { create(:project, :internal) }
       let(:fork_project) { create(:forked_project_with_submodules) }
 
       before do
@@ -74,7 +74,7 @@
     end
 
     describe "non-forked project" do
-      let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+      let(:project) { create(:project, :internal) }
 
       it "skips levels" do
         expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
@@ -84,7 +84,7 @@
     end
 
     describe "Snippet" do
-      let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+      let(:snippet) { create(:snippet, :internal) }
 
       it "skips levels" do
         expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index f3394910c5b34d63e0a2fd6dd4b552826dbeac65..1e98280d045cac8e13b37a254d219f3a33287716 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -427,6 +427,45 @@ module Ci
       end
     end
 
+    describe "YAML Alias/Anchor" do
+      it "is correctly supported for jobs" do
+        config = <<EOT
+job1: &JOBTMPL
+  script: execute-script-for-job
+
+job2: *JOBTMPL
+EOT
+
+        config_processor = GitlabCiYamlProcessor.new(config)
+
+        expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(2)
+        expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+          except: nil,
+          stage: "test",
+          stage_idx: 1,
+          name: :job1,
+          only: nil,
+          commands: "\nexecute-script-for-job",
+          tag_list: [],
+          options: {},
+          when: "on_success",
+          allow_failure: false
+        })
+        expect(config_processor.builds_for_stage_and_ref("test", "master").second).to eq({
+          except: nil,
+          stage: "test",
+          stage_idx: 1,
+          name: :job2,
+          only: nil,
+          commands: "\nexecute-script-for-job",
+          tag_list: [],
+          options: {},
+          when: "on_success",
+          allow_failure: false
+        })
+      end
+    end
+
     describe "Error handling" do
       it "fails to parse YAML" do
         expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c413132abe56a3dc4246b2cf728d043ec75c0f58
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::Importer, lib: true do
+  before do
+    Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+  end
+
+  let(:statuses) do
+    [
+      "open",
+      "resolved",
+      "on hold",
+      "invalid",
+      "duplicate",
+      "wontfix",
+      "closed"  # undocumented status
+    ]
+  end
+  let(:sample_issues_statuses) do
+    issues = []
+
+    statuses.map.with_index do |status, index|
+      issues << {
+        local_id: index,
+        status: status,
+        title: "Issue #{index}",
+        content: "Some content to issue #{index}"
+      }
+    end
+
+    issues
+  end
+
+  let(:project_identifier) { 'namespace/repo' }
+  let(:data) do
+    {
+      bb_session: {
+        bitbucket_access_token: "123456",
+        bitbucket_access_token_secret: "secret"
+      }
+    }
+  end
+  let(:project) do
+    create(
+      :project,
+      import_source: project_identifier,
+      import_data: ProjectImportData.new(data: data)
+    )
+  end
+  let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
+  let(:issues_statuses_sample_data) do
+    {
+      count: sample_issues_statuses.count,
+      issues: sample_issues_statuses
+    }
+  end
+
+  context 'issues statuses' do
+    before do
+      stub_request(
+        :get,
+        "https://bitbucket.org/api/1.0/repositories/#{project_identifier}"
+      ).to_return(status: 200, body: { has_issues: true }.to_json)
+
+      stub_request(
+        :get,
+        "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues?limit=50&sort=utc_created_on&start=0"
+      ).to_return(status: 200, body: issues_statuses_sample_data.to_json)
+
+      sample_issues_statuses.each_with_index do |issue, index|
+        stub_request(
+          :get,
+          "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues/#{issue[:local_id]}/comments"
+        ).to_return(
+          status: 200,
+          body: [{ author_info: { username: "username" }, utc_created_on: index }].to_json
+        )
+      end
+    end
+
+    it 'map statuses to open or closed' do
+      importer.execute
+
+      expect(project.issues.where(state: "closed").size).to eq(5)
+      expect(project.issues.where(state: "opened").size).to eq(2)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fbdb7ea34ac531311240eb53ea0c7c1c833dd607
--- /dev/null
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::ExclusiveLease do
+  it 'cannot obtain twice before the lease has expired' do
+    lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+    expect(lease.try_obtain).to eq(true)
+    expect(lease.try_obtain).to eq(false)
+  end
+
+  it 'can obtain after the lease has expired' do
+    timeout = 1
+    lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
+    lease.try_obtain # start the lease
+    sleep(2 * timeout) # lease should have expired now
+    expect(lease.try_obtain).to eq(true)
+  end
+
+  def unique_key
+    SecureRandom.hex(10)
+  end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 6cebcb5009ad99728a9eafa199402e215eaf4977..e49dcb42342f54089a48d66e91cc4a8f7566afff 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -127,34 +127,6 @@
     end
   end
 
-  describe '#cross_project?' do
-    context 'when source, and target repositories are the same' do
-      let(:raw_data) { OpenStruct.new(base_data) }
-
-      it 'returns false' do
-        expect(pull_request.cross_project?).to eq false
-      end
-    end
-
-    context 'when source repo is a fork' do
-      let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
-      let(:raw_data) { OpenStruct.new(base_data) }
-
-      it 'returns true' do
-        expect(pull_request.cross_project?).to eq true
-      end
-    end
-
-    context 'when target repo is a fork' do
-      let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
-      let(:raw_data) { OpenStruct.new(base_data) }
-
-      it 'returns true' do
-        expect(pull_request.cross_project?).to eq true
-      end
-    end
-  end
-
   describe '#number' do
     let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
 
@@ -166,24 +138,44 @@
   describe '#valid?' do
     let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') }
 
-    context 'when source and target branches exists' do
-      let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
+    context 'when source, and target repositories are the same' do
+      context 'and source and target branches exists' do
+        let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
 
-      it 'returns true' do
-        expect(pull_request.valid?).to eq true
+        it 'returns true' do
+          expect(pull_request.valid?).to eq true
+        end
+      end
+
+      context 'and source branch doesn not exists' do
+        let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+
+        it 'returns false' do
+          expect(pull_request.valid?).to eq false
+        end
+      end
+
+      context 'and target branch doesn not exists' do
+        let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+
+        it 'returns false' do
+          expect(pull_request.valid?).to eq false
+        end
       end
     end
 
-    context 'when source branch doesn not exists' do
-      let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+    context 'when source repo is a fork' do
+      let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
+      let(:raw_data) { OpenStruct.new(base_data) }
 
       it 'returns false' do
         expect(pull_request.valid?).to eq false
       end
     end
 
-    context 'when target branch doesn not exists' do
-      let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+    context 'when target repo is a fork' do
+      let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
+      let(:raw_data) { OpenStruct.new(base_data) }
 
       it 'returns false' do
         expect(pull_request.valid?).to eq false
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..117a15264da24b53589d87291b847539d849c7f7
--- /dev/null
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::Go, lib: true do
+  let(:app) { double(:app) }
+  let(:middleware) { described_class.new(app) }
+
+  describe '#call' do
+    describe 'when go-get=0' do
+      it 'skips go-import generation' do
+        env = { 'rack.input' => '',
+                'QUERY_STRING' => 'go-get=0' }
+        expect(app).to receive(:call).with(env).and_return('no-go')
+        middleware.call(env)
+      end
+    end
+
+    describe 'when go-get=1' do
+      it 'returns a document' do
+        env = { 'rack.input' => '',
+                'QUERY_STRING' => 'go-get=1',
+                'PATH_INFO' => '/group/project/path' }
+        resp = middleware.call(env)
+        expect(resp[0]).to eq(200)
+        expect(resp[1]['Content-Type']).to eq('text/html')
+        expected_body = "<!DOCTYPE html><html><head><meta content='localhost/group/project git http://localhost/group/project.git' name='go-import'></head></html>\n"
+        expect(resp[2].body).to eq([expected_body])
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index efc2e5f4ef1fda1ee96566c9d2c7a12daf2471d3..09adbc07dcbbdb1c7f5bc58a5ca9526d3430d472 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -5,7 +5,7 @@
   let(:query) { 'hello world' }
 
   describe 'initialize with empty ref' do
-    let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, '') }
+    let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') }
 
     it { expect(results.project).to eq(project) }
     it { expect(results.repository_ref).to be_nil }
@@ -14,7 +14,7 @@
 
   describe 'initialize with ref' do
     let(:ref) { 'refs/heads/test' }
-    let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, ref) }
+    let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) }
 
     it { expect(results.project).to eq(project) }
     it { expect(results.repository_ref).to eq(ref) }
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb18f41785824b1c5e37e9d1ae38f214db8ed333
--- /dev/null
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::SearchResults do
+  let!(:project) { create(:project, name: 'foo') }
+  let!(:issue) { create(:issue, project: project, title: 'foo') }
+
+  let!(:merge_request) do
+    create(:merge_request, source_project: project, title: 'foo')
+  end
+
+  let!(:milestone) { create(:milestone, project: project, title: 'foo') }
+  let(:results) { described_class.new(Project.all, 'foo') }
+
+  describe '#total_count' do
+    it 'returns the total amount of search hits' do
+      expect(results.total_count).to eq(4)
+    end
+  end
+
+  describe '#projects_count' do
+    it 'returns the total amount of projects' do
+      expect(results.projects_count).to eq(1)
+    end
+  end
+
+  describe '#issues_count' do
+    it 'returns the total amount of issues' do
+      expect(results.issues_count).to eq(1)
+    end
+  end
+
+  describe '#merge_requests_count' do
+    it 'returns the total amount of merge requests' do
+      expect(results.merge_requests_count).to eq(1)
+    end
+  end
+
+  describe '#milestones_count' do
+    it 'returns the total amount of milestones' do
+      expect(results.milestones_count).to eq(1)
+    end
+  end
+
+  describe '#empty?' do
+    it 'returns true when there are no search results' do
+      allow(results).to receive(:total_count).and_return(0)
+
+      expect(results.empty?).to eq(true)
+    end
+
+    it 'returns false when there are search results' do
+      expect(results.empty?).to eq(false)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e86b9ef6a63497de1e6b31b5825a2b3bc188a762
--- /dev/null
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::SnippetSearchResults do
+  let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
+
+  let(:results) { described_class.new(Snippet.all, 'foo') }
+
+  describe '#total_count' do
+    it 'returns the total amount of search hits' do
+      expect(results.total_count).to eq(2)
+    end
+  end
+
+  describe '#snippet_titles_count' do
+    it 'returns the amount of matched snippet titles' do
+      expect(results.snippet_titles_count).to eq(1)
+    end
+  end
+
+  describe '#snippet_blobs_count' do
+    it 'returns the amount of matched snippet blobs' do
+      expect(results.snippet_blobs_count).to eq(1)
+    end
+  end
+end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index e891838672ea8a89af09248d10c3ef009e1bf942..25e9e5eca48a2491d77242e4177601ce8bad1b8c 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -132,4 +132,32 @@
       expect(runner.belongs_to_one_project?).to be_truthy
     end
   end
+
+  describe '#search' do
+    let(:runner) { create(:ci_runner, token: '123abc') }
+
+    it 'returns runners with a matching token' do
+      expect(described_class.search(runner.token)).to eq([runner])
+    end
+
+    it 'returns runners with a partially matching token' do
+      expect(described_class.search(runner.token[0..2])).to eq([runner])
+    end
+
+    it 'returns runners with a matching token regardless of the casing' do
+      expect(described_class.search(runner.token.upcase)).to eq([runner])
+    end
+
+    it 'returns runners with a matching description' do
+      expect(described_class.search(runner.description)).to eq([runner])
+    end
+
+    it 'returns runners with a partially matching description' do
+      expect(described_class.search(runner.description[0..2])).to eq([runner])
+    end
+
+    it 'returns runners with a matching description regardless of the casing' do
+      expect(described_class.search(runner.description.upcase)).to eq([runner])
+    end
+  end
 end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 600089802b2a4bed2e4eb4e8c49551845b2dfa93..aff384c294919a2b2439391f7b9f27c520f88ab9 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -32,9 +32,54 @@
   describe ".search" do
     let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
 
-    it "matches by title" do
+    it 'returns notes with a matching title' do
+      expect(described_class.search(searchable_issue.title)).
+        to eq([searchable_issue])
+    end
+
+    it 'returns notes with a partially matching title' do
       expect(described_class.search('able')).to eq([searchable_issue])
     end
+
+    it 'returns notes with a matching title regardless of the casing' do
+      expect(described_class.search(searchable_issue.title.upcase)).
+        to eq([searchable_issue])
+    end
+  end
+
+  describe ".full_search" do
+    let!(:searchable_issue) do
+      create(:issue, title: "Searchable issue", description: 'kittens')
+    end
+
+    it 'returns notes with a matching title' do
+      expect(described_class.full_search(searchable_issue.title)).
+        to eq([searchable_issue])
+    end
+
+    it 'returns notes with a partially matching title' do
+      expect(described_class.full_search('able')).to eq([searchable_issue])
+    end
+
+    it 'returns notes with a matching title regardless of the casing' do
+      expect(described_class.full_search(searchable_issue.title.upcase)).
+        to eq([searchable_issue])
+    end
+
+    it 'returns notes with a matching description' do
+      expect(described_class.full_search(searchable_issue.description)).
+        to eq([searchable_issue])
+    end
+
+    it 'returns notes with a partially matching description' do
+      expect(described_class.full_search(searchable_issue.description)).
+        to eq([searchable_issue])
+    end
+
+    it 'returns notes with a matching description regardless of the casing' do
+      expect(described_class.full_search(searchable_issue.description.upcase)).
+        to eq([searchable_issue])
+    end
   end
 
   describe "#today?" do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 3c995053eecfd350c20b6b6d985b387923928b07..c9245fc953549415b1b973971b8e938cc0046453 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -103,4 +103,30 @@
       expect(group.avatar_type).to eq(["only images allowed"])
     end
   end
+
+  describe '.search' do
+    it 'returns groups with a matching name' do
+      expect(described_class.search(group.name)).to eq([group])
+    end
+
+    it 'returns groups with a partially matching name' do
+      expect(described_class.search(group.name[0..2])).to eq([group])
+    end
+
+    it 'returns groups with a matching name regardless of the casing' do
+      expect(described_class.search(group.name.upcase)).to eq([group])
+    end
+
+    it 'returns groups with a matching path' do
+      expect(described_class.search(group.path)).to eq([group])
+    end
+
+    it 'returns groups with a partially matching path' do
+      expect(described_class.search(group.path[0..2])).to eq([group])
+    end
+
+    it 'returns groups with a matching path regardless of the casing' do
+      expect(described_class.search(group.path.upcase)).to eq([group])
+    end
+  end
 end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 1455661485bf5a993a0a9f5c89d2e67fa9ea8545..f800f415bd2d94a37e76218d925d67f182a8b313 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -31,7 +31,7 @@
       WebMock.stub_request(:post, @service_hook.url)
     end
 
-    it "POSTs to the web hook URL" do
+    it "POSTs to the webhook URL" do
       @service_hook.execute(@data)
       expect(WebMock).to have_requested(:post, @service_hook.url).with(
         headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 6ea99952a8f4021d06308ce9bc10c14b922eabea..04bc2dcfb16a80c9b8ebc062262b6c9aebdf465f 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -52,7 +52,7 @@
       WebMock.stub_request(:post, @project_hook.url)
     end
 
-    it "POSTs to the web hook URL" do
+    it "POSTs to the webhook URL" do
       @project_hook.execute(@data, 'push_hooks')
       expect(WebMock).to have_requested(:post, @project_hook.url).with(
         headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 52271c7c8c6d1fde914f43e5417f4bf94fcbbfa2..7f44ca2f7dbe0eeb31a8729dd2edbeb985b39f5b 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -105,6 +105,31 @@
     end
   end
 
+  describe '#referenced_merge_requests' do
+    it 'returns the referenced merge requests' do
+      project = create(:project, :public)
+
+      mr1 = create(:merge_request,
+                   source_project: project,
+                   source_branch:  'master',
+                   target_branch:  'feature')
+
+      mr2 = create(:merge_request,
+                   source_project: project,
+                   source_branch:  'feature',
+                   target_branch:  'master')
+
+      issue = create(:issue, description: mr1.to_reference, project: project)
+
+      create(:note_on_issue,
+             noteable:   issue,
+             note:       mr2.to_reference,
+             project_id: project.id)
+
+      expect(issue.referenced_merge_requests).to eq([mr1, mr2])
+    end
+  end
+
   it_behaves_like 'an editable mentionable' do
     subject { create(:issue) }
 
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 59c40922abb63cb0d55d2ed2772c874e311417d5..8bf68013fd2668519203cf29839db612cec822dc 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -80,6 +80,12 @@
     it { is_expected.to respond_to(:merge_when_build_succeeds) }
   end
 
+  describe '.in_projects' do
+    it 'returns the merge requests for a set of projects' do
+      expect(described_class.in_projects(Project.all)).to eq([subject])
+    end
+  end
+
   describe '#to_reference' do
     it 'returns a String reference to the object' do
       expect(subject.to_reference).to eq "!#{subject.iid}"
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 1b1380ce4e2c3e31996ad00d678837a633531de5..de1757bf67a5f36f98bd9c51553fa0d170978494 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -60,7 +60,7 @@
     end
 
     it "should recover from dividing by zero" do
-      expect(milestone.issues).to receive(:count).and_return(0)
+      expect(milestone.issues).to receive(:size).and_return(0)
       expect(milestone.percent_complete).to eq(0)
     end
   end
@@ -114,7 +114,6 @@
     end
 
     it { expect(milestone.closed_items_count).to eq(1) }
-    it { expect(milestone.open_items_count).to eq(2) }
     it { expect(milestone.total_items_count).to eq(3) }
     it { expect(milestone.is_empty?).to be_falsey }
   end
@@ -182,4 +181,34 @@
       expect(issue4.position).to eq(42)
     end
   end
+
+  describe '.search' do
+    let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
+
+    it 'returns milestones with a matching title' do
+      expect(described_class.search(milestone.title)).to eq([milestone])
+    end
+
+    it 'returns milestones with a partially matching title' do
+      expect(described_class.search(milestone.title[0..2])).to eq([milestone])
+    end
+
+    it 'returns milestones with a matching title regardless of the casing' do
+      expect(described_class.search(milestone.title.upcase)).to eq([milestone])
+    end
+
+    it 'returns milestones with a matching description' do
+      expect(described_class.search(milestone.description)).to eq([milestone])
+    end
+
+    it 'returns milestones with a partially matching description' do
+      expect(described_class.search(milestone.description[0..2])).
+        to eq([milestone])
+    end
+
+    it 'returns milestones with a matching description regardless of the casing' do
+      expect(described_class.search(milestone.description.upcase)).
+        to eq([milestone])
+    end
+  end
 end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e0b3290e41661e3b9c313358a227fa0d71586369..3c3a580942a97dbf029c6f9c38b4aaa30bfb1ada 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -41,13 +41,32 @@
     it { expect(namespace.human_name).to eq(namespace.owner_name) }
   end
 
-  describe :search do
-    before do
-      @namespace = create :namespace
+  describe '.search' do
+    let(:namespace) { create(:namespace) }
+
+    it 'returns namespaces with a matching name' do
+      expect(described_class.search(namespace.name)).to eq([namespace])
+    end
+
+    it 'returns namespaces with a partially matching name' do
+      expect(described_class.search(namespace.name[0..2])).to eq([namespace])
+    end
+
+    it 'returns namespaces with a matching name regardless of the casing' do
+      expect(described_class.search(namespace.name.upcase)).to eq([namespace])
+    end
+
+    it 'returns namespaces with a matching path' do
+      expect(described_class.search(namespace.path)).to eq([namespace])
     end
 
-    it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) }
-    it { expect(Namespace.search('unknown')).to eq([]) }
+    it 'returns namespaces with a partially matching path' do
+      expect(described_class.search(namespace.path[0..2])).to eq([namespace])
+    end
+
+    it 'returns namespaces with a matching path regardless of the casing' do
+      expect(described_class.search(namespace.path.upcase)).to eq([namespace])
+    end
   end
 
   describe :move_dir do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 583937ca748bae12c4032ecfe60f89084a67bd45..b854de1d3d5254035eb5593fb496cf2723ddfc2a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -24,7 +24,7 @@
 describe Note, models: true do
   describe 'associations' do
     it { is_expected.to belong_to(:project) }
-    it { is_expected.to belong_to(:noteable) }
+    it { is_expected.to belong_to(:noteable).touch(true) }
     it { is_expected.to belong_to(:author).class_name('User') }
 
     it { is_expected.to have_many(:todos).dependent(:destroy) }
@@ -140,10 +140,16 @@
     end
   end
 
-  describe :search do
-    let!(:note) { create(:note, note: "WoW") }
+  describe '.search' do
+    let(:note) { create(:note, note: 'WoW') }
 
-    it { expect(Note.search('wow')).to include(note) }
+    it 'returns notes with matching content' do
+      expect(described_class.search(note.note)).to eq([note])
+    end
+
+    it 'returns notes with matching content regardless of the casing' do
+      expect(described_class.search('WOW')).to eq([note])
+    end
   end
 
   describe :grouped_awards do
@@ -220,4 +226,12 @@
       expect(note.is_award?).to be_falsy
     end
   end
+
+  describe 'clear_blank_line_code!' do
+    it 'clears a blank line code before validation' do
+      note = build(:note, line_code: ' ')
+
+      expect { note.valid? }.to change(note, :line_code).to(nil)
+    end
+  end
 end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index cc92eb0bd9f2cd098eefb8710b4ae9ef32e56244..e0feb606f78188734e6660e1e2fbc492c0a94edc 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -10,7 +10,6 @@
 #  created_at       :datetime
 #  updated_at       :datetime
 #  file_name        :string(255)
-#  expires_at       :datetime
 #  type             :string(255)
 #  visibility_level :integer          default(0), not null
 #
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f9842d23afa63f0e9bc48f315a11a1d99762a912..59c5ffa6b9c3e026923fbba79a94a204e3a1fe77 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -68,6 +68,7 @@
     it { is_expected.to have_many(:runners) }
     it { is_expected.to have_many(:variables) }
     it { is_expected.to have_many(:triggers) }
+    it { is_expected.to have_many(:todos).dependent(:destroy) }
   end
 
   describe 'modules' do
@@ -561,7 +562,7 @@
   end
 
   describe '#visibility_level_allowed?' do
-    let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+    let(:project) { create(:project, :internal) }
 
     context 'when checking on non-forked project' do
       it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
@@ -581,7 +582,58 @@
       it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
       it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
     end
+  end
+
+  describe '.search' do
+    let(:project) { create(:project, description: 'kitten mittens') }
 
+    it 'returns projects with a matching name' do
+      expect(described_class.search(project.name)).to eq([project])
+    end
+
+    it 'returns projects with a partially matching name' do
+      expect(described_class.search(project.name[0..2])).to eq([project])
+    end
+
+    it 'returns projects with a matching name regardless of the casing' do
+      expect(described_class.search(project.name.upcase)).to eq([project])
+    end
+
+    it 'returns projects with a matching description' do
+      expect(described_class.search(project.description)).to eq([project])
+    end
+
+    it 'returns projects with a partially matching description' do
+      expect(described_class.search('kitten')).to eq([project])
+    end
+
+    it 'returns projects with a matching description regardless of the casing' do
+      expect(described_class.search('KITTEN')).to eq([project])
+    end
+
+    it 'returns projects with a matching path' do
+      expect(described_class.search(project.path)).to eq([project])
+    end
+
+    it 'returns projects with a partially matching path' do
+      expect(described_class.search(project.path[0..2])).to eq([project])
+    end
+
+    it 'returns projects with a matching path regardless of the casing' do
+      expect(described_class.search(project.path.upcase)).to eq([project])
+    end
+
+    it 'returns projects with a matching namespace name' do
+      expect(described_class.search(project.namespace.name)).to eq([project])
+    end
+
+    it 'returns projects with a partially matching namespace name' do
+      expect(described_class.search(project.namespace.name[0..2])).to eq([project])
+    end
+
+    it 'returns projects with a matching namespace name regardless of the casing' do
+      expect(described_class.search(project.namespace.name.upcase)).to eq([project])
+    end
   end
 
   describe '#rename_repo' do
@@ -646,4 +698,20 @@
       project.expire_caches_before_rename('foo')
     end
   end
+
+  describe '.search_by_title' do
+    let(:project) { create(:project, name: 'kittens') }
+
+    it 'returns projects with a matching name' do
+      expect(described_class.search_by_title(project.name)).to eq([project])
+    end
+
+    it 'returns projects with a partially matching name' do
+      expect(described_class.search_by_title('kitten')).to eq([project])
+    end
+
+    it 'returns projects with a matching name regardless of the casing' do
+      expect(described_class.search_by_title('KITTENS')).to eq([project])
+    end
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1c7d66398cbe204cf5942b9fd2a61e7bf70ebe88..34866be3395536e1d4a84dc8cc8bef9e9dd8cd59 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -148,6 +148,12 @@
 
         expect(branch.name).to eq('new_feature')
       end
+
+      it 'calls the after_create_branch hook' do
+        expect(repository).to receive(:after_create_branch)
+
+        repository.add_branch(user, 'new_feature', 'master')
+      end
     end
 
     context 'when pre hooks failed' do
@@ -405,7 +411,7 @@
     end
   end
 
-  describe '#expire_branch_ache' do
+  describe '#expire_branch_cache' do
     # This method is private but we need it for testing purposes. Sadly there's
     # no other proper way of testing caching operations.
     let(:cache) { repository.send(:cache) }
@@ -556,11 +562,12 @@
     end
   end
 
-  describe '#before_create_tag' do
+  describe '#before_push_tag' do
     it 'flushes the cache' do
       expect(repository).to receive(:expire_cache)
+      expect(repository).to receive(:expire_tag_count_cache)
 
-      repository.before_create_tag
+      repository.before_push_tag
     end
   end
 
@@ -595,4 +602,89 @@
       repository.after_remove_branch
     end
   end
+
+  describe "#main_language" do
+    it 'shows the main language of the project' do
+      expect(repository.main_language).to eq("Ruby")
+    end
+
+    it 'returns nil when the repository is empty' do
+      allow(repository).to receive(:empty?).and_return(true)
+
+      expect(repository.main_language).to be_nil
+    end
+  end
+
+  describe '#before_remove_tag' do
+    it 'flushes the tag cache' do
+      expect(repository).to receive(:expire_tag_count_cache)
+
+      repository.before_remove_tag
+    end
+  end
+
+  describe '#branch_count' do
+    it 'returns the number of branches' do
+      expect(repository.branch_count).to be_an_instance_of(Fixnum)
+    end
+  end
+
+  describe '#tag_count' do
+    it 'returns the number of tags' do
+      expect(repository.tag_count).to be_an_instance_of(Fixnum)
+    end
+  end
+
+  describe '#expire_branch_count_cache' do
+    let(:cache) { repository.send(:cache) }
+
+    it 'expires the cache' do
+      expect(cache).to receive(:expire).with(:branch_count)
+
+      repository.expire_branch_count_cache
+    end
+  end
+
+  describe '#expire_tag_count_cache' do
+    let(:cache) { repository.send(:cache) }
+
+    it 'expires the cache' do
+      expect(cache).to receive(:expire).with(:tag_count)
+
+      repository.expire_tag_count_cache
+    end
+  end
+
+  describe '#add_tag' do
+    it 'adds a tag' do
+      expect(repository).to receive(:before_push_tag)
+
+      expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
+        with(repository.path_with_namespace, '8.5', 'master', 'foo')
+
+      repository.add_tag('8.5', 'master', 'foo')
+    end
+  end
+
+  describe '#rm_branch' do
+    let(:user) { create(:user) }
+
+    it 'removes a branch' do
+      expect(repository).to receive(:before_remove_branch)
+      expect(repository).to receive(:after_remove_branch)
+
+      repository.rm_branch(user, 'feature')
+    end
+  end
+
+  describe '#rm_tag' do
+    it 'removes a tag' do
+      expect(repository).to receive(:before_remove_tag)
+
+      expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+        with(repository.path_with_namespace, '8.5')
+
+      repository.rm_tag('8.5')
+    end
+  end
 end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index eb2dbbdc5a4eafba13290efd26e219556ca55d03..5077ac7b62bd6b39a182f3a25cb72645dd719eb6 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -10,7 +10,6 @@
 #  created_at       :datetime
 #  updated_at       :datetime
 #  file_name        :string(255)
-#  expires_at       :datetime
 #  type             :string(255)
 #  visibility_level :integer          default(0), not null
 #
@@ -60,4 +59,48 @@
       expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}"
     end
   end
+
+  describe '.search' do
+    let(:snippet) { create(:snippet) }
+
+    it 'returns snippets with a matching title' do
+      expect(described_class.search(snippet.title)).to eq([snippet])
+    end
+
+    it 'returns snippets with a partially matching title' do
+      expect(described_class.search(snippet.title[0..2])).to eq([snippet])
+    end
+
+    it 'returns snippets with a matching title regardless of the casing' do
+      expect(described_class.search(snippet.title.upcase)).to eq([snippet])
+    end
+
+    it 'returns snippets with a matching file name' do
+      expect(described_class.search(snippet.file_name)).to eq([snippet])
+    end
+
+    it 'returns snippets with a partially matching file name' do
+      expect(described_class.search(snippet.file_name[0..2])).to eq([snippet])
+    end
+
+    it 'returns snippets with a matching file name regardless of the casing' do
+      expect(described_class.search(snippet.file_name.upcase)).to eq([snippet])
+    end
+  end
+
+  describe '#search_code' do
+    let(:snippet) { create(:snippet, content: 'class Foo; end') }
+
+    it 'returns snippets with matching content' do
+      expect(described_class.search_code(snippet.content)).to eq([snippet])
+    end
+
+    it 'returns snippets with partially matching content' do
+      expect(described_class.search_code('class')).to eq([snippet])
+    end
+
+    it 'returns snippets with matching content regardless of the casing' do
+      expect(described_class.search_code('FOO')).to eq([snippet])
+    end
+  end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 412101ac9f9b6eb550c5a43868f9739ea44d3e97..909b6796591528b46ca6dc3c2a682970e6abe150 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -463,17 +463,43 @@
     end
   end
 
-  describe 'search' do
-    let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
-    let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
-
-    it "should be case insensitive" do
-      expect(User.search(user1.username.upcase).to_a).to eq([user1])
-      expect(User.search(user1.username.downcase).to_a).to eq([user1])
-      expect(User.search(user2.username.upcase).to_a).to eq([user2])
-      expect(User.search(user2.username.downcase).to_a).to eq([user2])
-      expect(User.search(user1.username.downcase).to_a.size).to eq(2)
-      expect(User.search(user2.username.downcase).to_a.size).to eq(1)
+  describe '.search' do
+    let(:user) { create(:user) }
+
+    it 'returns users with a matching name' do
+      expect(described_class.search(user.name)).to eq([user])
+    end
+
+    it 'returns users with a partially matching name' do
+      expect(described_class.search(user.name[0..2])).to eq([user])
+    end
+
+    it 'returns users with a matching name regardless of the casing' do
+      expect(described_class.search(user.name.upcase)).to eq([user])
+    end
+
+    it 'returns users with a matching Email' do
+      expect(described_class.search(user.email)).to eq([user])
+    end
+
+    it 'returns users with a partially matching Email' do
+      expect(described_class.search(user.email[0..2])).to eq([user])
+    end
+
+    it 'returns users with a matching Email regardless of the casing' do
+      expect(described_class.search(user.email.upcase)).to eq([user])
+    end
+
+    it 'returns users with a matching username' do
+      expect(described_class.search(user.username)).to eq([user])
+    end
+
+    it 'returns users with a partially matching username' do
+      expect(described_class.search(user.username[0..2])).to eq([user])
+    end
+
+    it 'returns users with a matching username regardless of the casing' do
+      expect(described_class.search(user.username.upcase)).to eq([user])
     end
   end
 
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b82c5c7685f4eb56115a9bec3125e35df166de0d..96e8c8c51f86ecd81a12304e20bac518476fa956 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -47,6 +47,8 @@
         expect(json_response.first.keys).to include 'identities'
         expect(json_response.first.keys).to include 'can_create_project'
         expect(json_response.first.keys).to include 'two_factor_enabled'
+        expect(json_response.first.keys).to include 'last_sign_in_at'
+        expect(json_response.first.keys).to include 'confirmed_at'
       end
     end
   end
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5b7ba5218123f87c30e54f343451179cce605add
--- /dev/null
+++ b/spec/services/delete_tag_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DeleteTagService, services: true do
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+  let(:user) { create(:user) }
+  let(:service) { described_class.new(project, user) }
+
+  let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
+
+  describe '#execute' do
+    before do
+      allow(repository).to receive(:find_tag).and_return(tag)
+    end
+
+    it 'removes the tag' do
+      expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+        and_return(true)
+
+      expect(repository).to receive(:before_remove_tag)
+      expect(service).to receive(:success)
+
+      service.execute('8.5')
+    end
+  end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 994585fb32c5899941a076aa8cd0866053293903..a7e2e1b1792014af87e0feb0b47a00cb72c1eeb2 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -155,8 +155,25 @@
     end
   end
 
-  describe "Web Hooks" do
-    context "execute web hooks" do
+  describe "Updates main language" do
+
+    context "before push" do
+      it { expect(project.main_language).to eq(nil) }
+    end
+
+    context "after push" do
+      before do
+        @service = execute_service(project, user, @oldrev, @newrev, @ref)
+      end
+
+      it { expect(@service.update_main_language).to eq(true) }
+      it { expect(project.main_language).to eq("Ruby") }
+    end
+  end
+
+
+  describe "Webhooks" do
+    context "execute webhooks" do
       it "when pushing a branch for the first time" do
         expect(project).to receive(:execute_hooks)
         expect(project.default_branch).to eq("master")
@@ -254,22 +271,24 @@
 
       allow(project.repository).to receive(:commits_between).
         and_return([closing_commit])
+
+      project.team << [commit_author, :master]
     end
 
     context "to default branches" do
       it "closes issues" do
-        execute_service(project, user, @oldrev, @newrev, @ref )
+        execute_service(project, commit_author, @oldrev, @newrev, @ref )
         expect(Issue.find(issue.id)).to be_closed
       end
 
       it "adds a note indicating that the issue is now closed" do
         expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
-        execute_service(project, user, @oldrev, @newrev, @ref )
+        execute_service(project, commit_author, @oldrev, @newrev, @ref )
       end
 
       it "doesn't create additional cross-reference notes" do
         expect(SystemNoteService).not_to receive(:cross_reference)
-        execute_service(project, user, @oldrev, @newrev, @ref )
+        execute_service(project, commit_author, @oldrev, @newrev, @ref )
       end
 
       it "doesn't close issues when external issue tracker is in use" do
@@ -277,7 +296,7 @@
 
         # The push still shouldn't create cross-reference notes.
         expect do
-          execute_service(project, user, @oldrev, @newrev,  'refs/heads/hurf' )
+          execute_service(project, commit_author, @oldrev, @newrev,  'refs/heads/hurf' )
         end.not_to change { Note.where(project_id: project.id, system: true).count }
       end
     end
@@ -299,7 +318,6 @@
       end
     end
 
-    # EE-only tests
     context "for jira issue tracker" do
       include JiraServiceHelper
 
@@ -349,7 +367,7 @@
             }
           }.to_json
 
-          execute_service(project, user, @oldrev, @newrev, @ref )
+          execute_service(project, commit_author, @oldrev, @newrev, @ref )
           expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
             body: transition_body
           ).once
@@ -360,7 +378,7 @@
             body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
           }.to_json
 
-          execute_service(project, user, @oldrev, @newrev, @ref )
+          execute_service(project, commit_author, @oldrev, @newrev, @ref )
           expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
             body: comment_body
           ).once
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index b982274c529073ee106ed318bf56d49d9c68c9d4..cc780587e74a01b54ad5b67b4ff15016abdf01ca 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -78,8 +78,8 @@
     end
   end
 
-  describe "Web Hooks" do
-    context "execute web hooks" do
+  describe "Webhooks" do
+    context "execute webhooks" do
       it "when pushing tags" do
         expect(project).to receive(:execute_hooks)
         service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
diff --git a/spec/services/projects/import_export/project_tree_saver_spec.rb b/spec/services/projects/import_export/project_tree_saver_spec.rb
index 383b3e4ce8ba8d2e953b669157c7c15a7ee85efe..960109e0c84ebea7bb3b5705cf2f8722ac15297c 100644
--- a/spec/services/projects/import_export/project_tree_saver_spec.rb
+++ b/spec/services/projects/import_export/project_tree_saver_spec.rb
@@ -85,6 +85,14 @@
       it 'has project members' do
         expect(saved_project_json['project_members']).not_to be_empty
       end
+
+      it 'has merge requests diffs' do
+        expect(saved_project_json['merge_requests'].first['merge_request_diff']).not_to be_empty
+      end
+
+      it 'has ci commits' do
+        expect(saved_project_json['commit_statuses'].first['commit']).not_to be_empty
+      end
     end
   end
 
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3c06a8901634512a99828a8bfab6144fcfb56a1b..e8b9e6b923840a4a5ac2406201aefd2d6950e3c0 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -102,8 +102,8 @@
 
   describe :visibility_level do
     let(:user) { create :user, admin: true }
-    let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
-    let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+    let(:project) { create(:project, :internal) }
+    let(:forked_project) { create(:forked_project_with_submodules, :internal) }
     let(:opts) { {} }
 
     before do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8f381f46e5774c269466e1603b02f9f63ff99ece..7d939ca7509f0767b172ca3d5434f04528d4fa36 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -14,7 +14,7 @@
 require 'rspec/rails'
 require 'shoulda/matchers'
 require 'sidekiq/testing/inline'
-require 'benchmark/ips'
+require 'rspec/retry'
 
 # Requires supporting ruby files with custom matchers and macros, etc,
 # in spec/support/ and its subdirectories.
@@ -25,6 +25,9 @@
   config.use_instantiated_fixtures  = false
   config.mock_with :rspec
 
+  config.verbose_retry = true
+  config.display_try_failure_messages = true
+
   config.include Devise::TestHelpers, type: :controller
   config.include LoginHelpers,        type: :feature
   config.include LoginHelpers,        type: :request
@@ -34,7 +37,6 @@
   config.include ActiveJob::TestHelper
   config.include StubGitlabCalls
   config.include StubGitlabData
-  config.include BenchmarkMatchers, benchmark: true
 
   config.infer_spec_type_from_file_location!
   config.raise_errors_for_deprecations!
diff --git a/spec/support/matchers/benchmark_matchers.rb b/spec/support/matchers/benchmark_matchers.rb
deleted file mode 100644
index 84f655c2119613d05d13a07ad05a70098ea2b777..0000000000000000000000000000000000000000
--- a/spec/support/matchers/benchmark_matchers.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module BenchmarkMatchers
-  extend RSpec::Matchers::DSL
-
-  def self.included(into)
-    into.extend(ClassMethods)
-  end
-
-  matcher :iterate_per_second do |min_iterations|
-    supports_block_expectations
-
-    match do |block|
-      @max_stddev ||= 30
-
-      @entry = benchmark(&block)
-
-      expect(@entry.ips).to be >= min_iterations
-      expect(@entry.stddev_percentage).to be <= @max_stddev
-    end
-
-    chain :with_maximum_stddev do |value|
-      @max_stddev = value
-    end
-
-    description do
-      "run at least #{min_iterations} iterations per second"
-    end
-
-    failure_message do
-      ips    = @entry.ips.round(2)
-      stddev = @entry.stddev_percentage.round(2)
-
-      "expected at least #{min_iterations} iterations per second " \
-        "with a maximum stddev of #{@max_stddev}%, instead of " \
-        "#{ips} iterations per second with a stddev of #{stddev}%"
-    end
-  end
-
-  # Benchmarks the given block and returns a Benchmark::IPS::Report::Entry.
-  def benchmark(&block)
-    report = Benchmark.ips(quiet: true) do |bench|
-      bench.report do
-        instance_eval(&block)
-      end
-    end
-
-    report.entries[0]
-  end
-
-  module ClassMethods
-    # Wraps around rspec's subject method so you can write:
-    #
-    #     benchmark_subject { SomeClass.some_method }
-    #
-    # instead of:
-    #
-    #     subject { -> { SomeClass.some_method } }
-    def benchmark_subject(&block)
-      subject { block }
-    end
-  end
-end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index e4151b9bb6a62bb6e306bfc262af2affba83c32e..0265dbe9c666707bab07214ff8b696b396326c41 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -11,7 +11,7 @@
     end
   end
 
-  context "web hook" do
+  context "webhook" do
     let(:project) { create(:project) }
     let(:key) { create(:key, user: project.owner) }
     let(:key_id) { key.shell_id }