diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d04069df885af8a0c7427fe1b244db80a396e85d..195783454f9c2f157b455787033ed84966f7f7c5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -331,7 +331,7 @@ trigger_docs:
   cache: {}
   artifacts: {}
   script:
-    - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/38069/trigger/builds"
+    - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/1794617/trigger/builds"
   only:
     - master@gitlab-org/gitlab-ce
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9411cc620031e39581eff8c77dd89328efb545a7..5b072ce9f6071d1819d8035d5f264056bf70d0a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ entry.
 
 - Show correct environment log in admin/logs (@duk3luk3 !7191)
 - Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
+- Diff collapse won't shift when collapsing.
 - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
 - Adds user project membership expired event to clarify why user was removed (Callum Dryden)
 - Trim leading and trailing whitespace on project_path (Linus Thiel)
@@ -13,6 +14,7 @@ entry.
 - Adds support for the `token` attribute in project hooks API (Gauvain Pocentek)
 - Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO)
 - Fix Markdown styling inside reference links (Jan Zdráhal)
+- Create new issue board list after creating a new label
 - Fix extra space on Build sidebar on Firefox !7060
 - Fail gracefully when creating merge request with non-existing branch (alexsanford)
 - Fix mobile layout issues in admin user overview page !7087
@@ -66,8 +68,10 @@ entry.
 - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
 - Improve search query parameter naming in /admin/users !7115 (YarNayar)
 - Fix table pagination to be responsive
+- Fix applying GitHub-imported labels when importing job is interrupted
 - Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
 - Updated commit SHA styling on the branches page.
+- Fix 404 when visit /projects page
 
 ## 8.13.3 (2016-11-02)
 
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 7ada0d303f3e7e49c3f18bfa9dcfa73a1895b28b..3eefcb9dd5b38e2c1dc061052455dd97bcd51e6c 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.8.5
+1.0.0
diff --git a/Gemfile b/Gemfile
index de624256c16dfe6fe59ae5bd25513a160f9a3156..cb2a847012651b85f6605577ff72e78d5f28c47b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,7 @@ gem 'omniauth-bitbucket',     '~> 0.0.2'
 gem 'omniauth-cas3',          '~> 1.1.2'
 gem 'omniauth-facebook',      '~> 4.0.0'
 gem 'omniauth-github',        '~> 1.1.1'
-gem 'omniauth-gitlab',        '~> 1.0.0'
+gem 'omniauth-gitlab',        '~> 1.0.2'
 gem 'omniauth-google-oauth2', '~> 0.4.1'
 gem 'omniauth-kerberos',      '~> 0.3.0', group: :kerberos
 gem 'omniauth-saml',          '~> 1.7.0'
@@ -152,7 +152,7 @@ gem 'settingslogic', '~> 2.0.9'
 gem 'version_sorter', '~> 2.1.0'
 
 # Cache
-gem 'redis-rails', '~> 4.0.0'
+gem 'redis-rails', '~> 5.0.1'
 
 # Redis
 gem 'redis', '~> 3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 6ea0578d9d2979dad9e975aa708a2bdc689323d0..290e4c3e1b32fb6b04827ea6290933211b1e19c3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -456,7 +456,7 @@ GEM
     omniauth-github (1.1.2)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.1)
-    omniauth-gitlab (1.0.1)
+    omniauth-gitlab (1.0.2)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.0)
     omniauth-google-oauth2 (0.4.1)
@@ -573,23 +573,23 @@ GEM
       json
     redcarpet (3.3.3)
     redis (3.2.2)
-    redis-actionpack (4.0.1)
-      actionpack (~> 4)
-      redis-rack (~> 1.5.0)
-      redis-store (~> 1.1.0)
-    redis-activesupport (4.1.5)
-      activesupport (>= 3, < 5)
-      redis-store (~> 1.1.0)
+    redis-actionpack (5.0.1)
+      actionpack (>= 4.0, < 6)
+      redis-rack (>= 1, < 3)
+      redis-store (>= 1.1.0, < 1.4.0)
+    redis-activesupport (5.0.1)
+      activesupport (>= 3, < 6)
+      redis-store (~> 1.2.0)
     redis-namespace (1.5.2)
       redis (~> 3.0, >= 3.0.4)
-    redis-rack (1.5.0)
+    redis-rack (1.6.0)
       rack (~> 1.5)
-      redis-store (~> 1.1.0)
-    redis-rails (4.0.0)
-      redis-actionpack (~> 4)
-      redis-activesupport (~> 4)
-      redis-store (~> 1.1.0)
-    redis-store (1.1.7)
+      redis-store (~> 1.2.0)
+    redis-rails (5.0.1)
+      redis-actionpack (~> 5.0.0)
+      redis-activesupport (~> 5.0.0)
+      redis-store (~> 1.2.0)
+    redis-store (1.2.0)
       redis (>= 2.2)
     request_store (1.3.1)
     rerun (0.11.0)
@@ -913,7 +913,7 @@ DEPENDENCIES
   omniauth-cas3 (~> 1.1.2)
   omniauth-facebook (~> 4.0.0)
   omniauth-github (~> 1.1.1)
-  omniauth-gitlab (~> 1.0.0)
+  omniauth-gitlab (~> 1.0.2)
   omniauth-google-oauth2 (~> 0.4.1)
   omniauth-kerberos (~> 0.3.0)
   omniauth-saml (~> 1.7.0)
@@ -938,7 +938,7 @@ DEPENDENCIES
   redcarpet (~> 3.3.3)
   redis (~> 3.2)
   redis-namespace (~> 1.5.2)
-  redis-rails (~> 4.0.0)
+  redis-rails (~> 5.0.1)
   request_store (~> 1.3)
   rerun (~> 0.11.0)
   responders (~> 2.0)
@@ -994,4 +994,4 @@ DEPENDENCIES
   wikicloth (= 0.8.1)
 
 BUNDLED WITH
-   1.13.5
+   1.13.6
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
index fe1a6dc7ea0494df10062477921163f46f39ccb8..14f618fd5d557579695c64be7d4b0ec02fda181f 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
@@ -2,6 +2,19 @@
 $(() => {
   const Store = gl.issueBoards.BoardsStore;
 
+  $(document).off('created.label').on('created.label', (e, label) => {
+    Store.new({
+      title: label.title,
+      position: Store.state.lists.length - 2,
+      list_type: 'label',
+      label: {
+        id: label.id,
+        title: label.title,
+        color: label.color
+      }
+    });
+  });
+
   $('.js-new-board-list').each(function () {
     const $this = $(this);
     new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6
index f20580b12798cb0fc9b33f22752269cd5102f8be..744aa0afa03e84aa9e0383c795c8c2e9410768dd 100644
--- a/app/assets/javascripts/create_label.js.es6
+++ b/app/assets/javascripts/create_label.js.es6
@@ -115,6 +115,8 @@
             .show();
         } else {
           this.$dropdownBack.trigger('click');
+
+          $(document).trigger('created.label', label);
         }
       });
     }
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6
index c74fc9ad074da624c5d6a17cb2c531c47148d2f8..afb2f0d69563af62843820961794bdf9e390674d 100644
--- a/app/assets/javascripts/extensions/element.js.es6
+++ b/app/assets/javascripts/extensions/element.js.es6
@@ -1,5 +1,7 @@
-/* eslint-disable */
-Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatches;
+/* global Element */
+/* eslint-disable consistent-return, max-len */
+
+Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
 
 Element.prototype.closest = function closest(selector, selectedElement = this) {
   if (!selectedElement) return;
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
index 8fc498be27d270d2431f96cbe46977c138d9a231..46503c290aed9ca9b6e9843602905a181f5a4f63 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js.es6
@@ -10,6 +10,7 @@
       Issuable.initSearch();
       Issuable.initChecks();
       Issuable.initResetFilters();
+      Issuable.resetIncomingEmailToken();
       return Issuable.initLabelFilterRemove();
     },
     initTemplates: function() {
@@ -154,6 +155,27 @@
         this.issuableBulkActions.willUpdateLabels = false;
       }
       return true;
+    },
+
+    resetIncomingEmailToken: function() {
+      $('.incoming-email-token-reset').on('click', function(e) {
+        e.preventDefault();
+
+        $.ajax({
+          type: 'PUT',
+          url: $('.incoming-email-token-reset').attr('href'),
+          dataType: 'json',
+          success: function(response) {
+            $('#issue_email').val(response.new_issue_address).focus();
+          },
+          beforeSend: function() {
+            $('.incoming-email-token-reset').text('resetting...');
+          },
+          complete: function() {
+            $('.incoming-email-token-reset').text('reset it');
+          }
+        });
+      });
     }
   };
 
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index 42d6799c82fc956f7ec24f13ce521c9f0a6e049c..a192273a18060963f4e85258ad9ca77dd94018ad 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -9,6 +9,8 @@
 
 (function() {
   $(function() {
+    if (!$(".network-graph").length) return;
+
     var network_graph;
     network_graph = new Network({
       url: $(".network-graph").attr('data-url'),
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index ce117c3fba5738a332408d15fa87d77ac854fc55..202ed5ae8fea5eef1cbe589a2c7315cd35efbfec 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -4,7 +4,7 @@
   margin-right: $margin-right;
 }
 
-.avatar-container {
+.avatar-circle {
   float: left;
   margin-right: 15px;
   border-radius: $avatar_radius;
@@ -27,7 +27,7 @@
 }
 
 .avatar {
-  @extend .avatar-container;
+  @extend .avatar-circle;
   width: 40px;
   height: 40px;
   padding: 0;
@@ -64,8 +64,8 @@
   &.s160 { font-size: 96px; line-height: 158px; }
 }
 
-.image-container {
-  @extend .avatar-container;
+.avatar-container {
+  @extend .avatar-circle;
   overflow: hidden;
   display: flex;
 
@@ -76,4 +76,4 @@
     margin: 0;
     align-self: center;
   }
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 13749f1b7bdbc53a24ffa72b15c7e9dbf2576072..920ce249b9a35cdb10ab86d3d392133840924e73 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -63,7 +63,7 @@
 }
 
 .select2-highlighted {
-  background: #3084bb !important;
+  background: $gl-link-color !important;
 }
 
 .select2-results li.select2-result-with-children > .select2-result-label {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index be2a7ceefff677097d4099de42634a30fcc10e3f..e0d00759c9cbbfc7df3acefe3ef8796f19aecb12 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -103,7 +103,7 @@ $gl-text-color-light: #8c8c8c;
 $gl-text-green: #4a2;
 $gl-text-red: #d12f19;
 $gl-text-orange: #d90;
-$gl-link-color: #3084bb;
+$gl-link-color: #3777b0;
 $gl-dark-link-color: #333;
 $gl-placeholder-color: #8f8f8f;
 $gl-icon-color: $gl-placeholder-color;
@@ -197,7 +197,7 @@ $line-number-new: #ddfbe6;
 $line-number-select: #fbf2da;
 $match-line: $gray-light;
 $table-border-gray: #f0f0f0;
-$line-target-blue: #eaf3fc;
+$line-target-blue: #f6faff;
 $line-select-yellow: #fcf8e7;
 $line-select-yellow-dark: #f0e2bd;
 
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 8ecf7fcb96d825aa21e4c83e5c5b24d3385df6e1..47d3e72679bb437111033fd2c430459a8c788dfb 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -36,9 +36,42 @@
     padding: 10px 0;
     margin-bottom: 0;
 
-    .commit-options-dropdown-caret {
-      @media (max-width: $screen-sm) {
-        margin-left: 0;
+    @media (min-width: $screen-sm-min) {
+      display: flex;
+      align-items: center;
+
+      .commit-meta {
+        flex: 1;
+      }
+    }
+
+    .commit-hash-full {
+      @media (max-width: $screen-sm-max) {
+        width: 80px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: inline-block;
+        vertical-align: bottom;
+      }
+    }
+
+    .commit-action-buttons {
+      i {
+        color: $gl-icon-color;
+        font-size: 13px;
+        margin-right: 3px;
+      }
+
+      @media (max-width: $screen-xs-max) {
+        .dropdown {
+          width: 100%;
+          margin-top: 10px;
+        }
+
+        .dropdown-toggle {
+          width: 100%;
+        }
       }
     }
   }
@@ -188,17 +221,6 @@
   }
 }
 
-.commit-action-buttons {
-  position: relative;
-  top: -1px;
-
-  i {
-    color: $gl-icon-color;
-    font-size: 13px;
-    margin-right: 3px;
-  }
-}
-
 /*
  * Commit message textarea for web editor and
  * custom merge request message
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ede29db1979aa1e2a5946552561f9a5d3186546d..6fab97a71aacf411c8e094108c16bca3f361d2ff 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -23,6 +23,10 @@
   color: $md-link-color;
 }
 
+.private-tokens-reset div.reset-action:not(:first-child) {
+  padding-top: 15px;
+}
+
 .oauth-buttons {
   .btn-group {
     margin-right: 10px;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 86e808314f4a81b45e39b22ea58182fdbbb7c905..52e0256943acdf1ec7a92d4201d76dd18b7f119a 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -117,6 +117,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :send_user_confirmation_email,
       :container_registry_token_expire_delay,
       :enabled_git_access_protocol,
+      :housekeeping_enabled,
+      :housekeeping_bitmaps_enabled,
+      :housekeeping_incremental_repack_period,
+      :housekeeping_full_repack_period,
+      :housekeeping_gc_period,
       repository_storages: [],
       restricted_visibility_levels: [],
       import_sources: [],
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index f71e0a1302bd9ba4b166046b743a18489767c7b9..f0c71725ea8c851a66c38bc209472f9657f8fa39 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -26,7 +26,15 @@ class ProfilesController < Profiles::ApplicationController
 
   def reset_private_token
     if current_user.reset_authentication_token!
-      flash[:notice] = "Token was successfully updated"
+      flash[:notice] = "Private token was successfully reset"
+    end
+
+    redirect_to profile_account_path
+  end
+
+  def reset_incoming_email_token
+    if current_user.reset_incoming_email_token!
+      flash[:notice] = "Incoming email token was successfully reset"
     end
 
     redirect_to profile_account_path
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 34318391dd909e3603ff5869dd61d336b2aae730..33a152ad34f86bdf2304a26e6afb089b4d112f1b 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -5,17 +5,29 @@ class Projects::NetworkController < Projects::ApplicationController
   before_action :require_non_empty_project
   before_action :assign_ref_vars
   before_action :authorize_download_code!
+  before_action :assign_commit
 
   def show
     @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
     @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
 
     respond_to do |format|
-      format.html
+      format.html do
+        if @options[:extended_sha1] && !@commit
+          flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
+        end
+      end
 
       format.json do
         @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
       end
     end
   end
+
+  def assign_commit
+    return if params[:extended_sha1].blank?
+
+    @options[:extended_sha1] = params[:extended_sha1]
+    @commit = @repo.commit(@options[:extended_sha1])
+  end
 end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 6988527a3be49d159926436aa67c14deea21e66c..28820adcc46578238e97fb513e052ac60f7d2cb0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -2,9 +2,9 @@ class ProjectsController < Projects::ApplicationController
   include IssuableCollections
   include ExtractsPath
 
-  before_action :authenticate_user!, except: [:show, :activity, :refs]
-  before_action :project, except: [:new, :create]
-  before_action :repository, except: [:new, :create]
+  before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
+  before_action :project, except: [:index, :new, :create]
+  before_action :repository, except: [:index, :new, :create]
   before_action :assign_ref_vars, only: [:show], if: :repo_exists?
   before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
 
@@ -160,6 +160,13 @@ class ProjectsController < Projects::ApplicationController
     end
   end
 
+  def new_issue_address
+    return render_404 unless Gitlab::IncomingEmail.supports_issue_creation?
+
+    current_user.reset_incoming_email_token!
+    render json: { new_issue_address: @project.new_issue_address(current_user) }
+  end
+
   def archive
     return access_denied! unless can?(current_user, :archive_project, @project)
 
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index d01e0dedf52aa03ecdd84b3a6087f5596c108880..b666aa01d6ba84e3942a24cf49d8dab9aac02dc3 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -16,7 +16,7 @@ class SearchController < ApplicationController
       @group = nil unless can?(current_user, :read_group, @group)
     end
 
-    return if params[:search].nil? || params[:search].blank?
+    return if params[:search].blank?
 
     @search_term = params[:search]
 
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5d27d30eaa3530b3d7f3d59e8864cc454eea1dc8
--- /dev/null
+++ b/app/helpers/accounts_helper.rb
@@ -0,0 +1,5 @@
+module AccountsHelper
+  def incoming_email_token_enabled?
+    current_user.incoming_email_token && Gitlab::IncomingEmail.supports_issue_creation?
+  end
+end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index e13b7cdd7077da04845d003a990e93d0ec9dc64d..07ff6fb94889c58503a881861c64f31d3a18f805 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -179,33 +179,6 @@ module BlobHelper
     }
   end
 
-  def selected_template(issuable)
-    templates = issuable_templates(issuable)
-    params[:issuable_template] if templates.include?(params[:issuable_template])
-  end
-
-  def can_add_template?(issuable)
-    names = issuable_templates(issuable)
-    names.empty? && can?(current_user, :push_code, @project) && !@project.private?
-  end
-
-  def merge_request_template_names
-    @merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
-  end
-
-  def issue_template_names
-    @issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
-  end
-
-  def issuable_templates(issuable)
-    @issuable_templates ||=
-      if issuable.is_a?(Issue)
-        issue_template_names
-      elsif issuable.is_a?(MergeRequest)
-        merge_request_template_names
-      end
-  end
-
   def ref_project
     @ref_project ||= @target_project || @project
   end
diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8893209b3146f255e54a8e92a25e13b641570b31
--- /dev/null
+++ b/app/helpers/components_helper.rb
@@ -0,0 +1,9 @@
+module ComponentsHelper
+  def gitlab_workhorse_version
+    if request.headers['Gitlab-Workhorse'].present?
+      request.headers['Gitlab-Workhorse'].split('-').first
+    else
+      Gitlab::Workhorse.version
+    end
+  end
+end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index ef6cfb235a900eff24fdcbc213662431270078fc..8127c3f3ee3e6006ef158667cc375418a9ba3b5c 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -30,6 +30,33 @@ module IssuablesHelper
     end
   end
 
+  def can_add_template?(issuable)
+    names = issuable_templates(issuable)
+    names.empty? && can?(current_user, :push_code, @project) && !@project.private?
+  end
+
+  def template_dropdown_tag(issuable, &block)
+    title = selected_template(issuable) || "Choose a template"
+    options = {
+      toggle_class: 'js-issuable-selector',
+      title: title,
+      filter: true,
+      placeholder: 'Filter',
+      footer_content: true,
+      data: {
+        data: issuable_templates(issuable),
+        field_name: 'issuable_template',
+        selected: selected_template(issuable),
+        project_path: ref_project.path,
+        namespace_path: ref_project.namespace.path
+      }
+    }
+
+    dropdown_tag(title, options: options) do
+      capture(&block)
+    end
+  end
+
   def user_dropdown_label(user_id, default_label)
     return default_label if user_id.nil?
     return "Unassigned" if user_id == "0"
@@ -153,4 +180,28 @@ module IssuablesHelper
 
     hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
   end
+
+  def issuable_templates(issuable)
+    @issuable_templates ||=
+      case issuable
+      when Issue
+        issue_template_names
+      when MergeRequest
+        merge_request_template_names
+      else
+        raise 'Unknown issuable type!'
+      end
+  end
+
+  def merge_request_template_names
+    @merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
+  end
+
+  def issue_template_names
+    @issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
+  end
+
+  def selected_template(issuable)
+    params[:issuable_template] if issuable_templates(issuable).include?(params[:issuable_template])
+  end
 end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index a9db8bb2b82351145411d60e9645cdaa010ba8be..09c6978679137bc1e69b71d448d8f8a4c5340819 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -61,6 +61,10 @@ module TodosHelper
     }
   end
 
+  def todos_filter_empty?
+    todos_filter_params.values.none?
+  end
+
   def todos_filter_path(options = {})
     without = options.delete(:without)
 
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 61a574d3dc0d1a9ec23360d3cefd03cde1bea350..79c3c2e62c5a93c0b9db253183cd7d6ab8ce583f 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,6 +1,6 @@
 class BaseMailer < ActionMailer::Base
-  add_template_helper ApplicationHelper
-  add_template_helper GitlabMarkdownHelper
+  helper ApplicationHelper
+  helper GitlabMarkdownHelper
 
   attr_accessor :current_user
   helper_method :current_user, :can?
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index eca6ec297671855184a909c84101df558684c26c..0bc1c19e9cd3b5099ce2583853ced1f0868d95cb 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -10,12 +10,12 @@ class Notify < BaseMailer
   include Emails::Pipelines
   include Emails::Members
 
-  add_template_helper MergeRequestsHelper
-  add_template_helper DiffHelper
-  add_template_helper BlobHelper
-  add_template_helper EmailsHelper
-  add_template_helper MembersHelper
-  add_template_helper GitlabRoutingHelper
+  helper MergeRequestsHelper
+  helper DiffHelper
+  helper BlobHelper
+  helper EmailsHelper
+  helper MembersHelper
+  helper GitlabRoutingHelper
 
   def test_email(recipient_email, subject, body)
     mail(to: recipient_email,
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 6e7a90e7d9c902e7577d3c254db8d9d4da4ae840..bb60cc8736cbc12b13f5bf0fc4b29ae132757fb2 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -85,6 +85,18 @@ class ApplicationSetting < ActiveRecord::Base
             presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
             if: :domain_blacklist_enabled?
 
+  validates :housekeeping_incremental_repack_period,
+            presence: true,
+            numericality: { only_integer: true, greater_than: 0 }
+
+  validates :housekeeping_full_repack_period,
+            presence: true,
+            numericality: { only_integer: true, greater_than: :housekeeping_incremental_repack_period }
+
+  validates :housekeeping_gc_period,
+            presence: true,
+            numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period }
+
   validates_each :restricted_visibility_levels do |record, attr, value|
     unless value.nil?
       value.each do |level|
@@ -168,6 +180,11 @@ class ApplicationSetting < ActiveRecord::Base
       container_registry_token_expire_delay: 5,
       repository_storages: ['default'],
       user_default_external: false,
+      housekeeping_enabled: true,
+      housekeeping_bitmaps_enabled: true,
+      housekeeping_incremental_repack_period: 10,
+      housekeeping_full_repack_period: 50,
+      housekeeping_gc_period: 200,
     )
   end
 
@@ -202,11 +219,7 @@ class ApplicationSetting < ActiveRecord::Base
   end
 
   def repository_storages
-    value = read_attribute(:repository_storages)
-    value = [value] if value.is_a?(String)
-    value = [] if value.nil?
-
-    value
+    Array(read_attribute(:repository_storages))
   end
 
   # repository_storage is still required in the API. Remove in 9.0
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 613444e0d704b9f6f93c00f5b326dee17ee35b6a..93a6b3122e07d4d7b614a57b1010bdd1cd7a8cf3 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -286,6 +286,11 @@ module Issuable
     false
   end
 
+  def assignee_or_author?(user)
+    # We're comparing IDs here so we don't need to load any associations.
+    author_id == user.id || assignee_id == user.id
+  end
+
   def record_metrics
     metrics = self.metrics || create_metrics
     metrics.record!
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 24c7b26d223d2536e1ca773f48208f03ca0ca1f9..04d30f462101b4428c1006db830265a5661bad2a 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -4,17 +4,21 @@ module TokenAuthenticatable
   private
 
   def write_new_token(token_field)
-    new_token = generate_token(token_field)
+    new_token = generate_available_token(token_field)
     write_attribute(token_field, new_token)
   end
 
-  def generate_token(token_field)
+  def generate_available_token(token_field)
     loop do
-      token = Devise.friendly_token
+      token = generate_token(token_field)
       break token unless self.class.unscoped.find_by(token_field => token)
     end
   end
 
+  def generate_token(token_field)
+    Devise.friendly_token
+  end
+
   class_methods do
     def authentication_token_fields
       @token_fields || []
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index fd9a8c1b8b7fa84d79e7d6adc5b400636ba4b6b3..91b508eb3251d9893c5ddae90bae4f318446dd7d 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -29,6 +29,15 @@ class ExternalIssue
     @project
   end
 
+  def project_id
+    @project.id
+  end
+
+  # Pattern used to extract `JIRA-123` issue references from text
+  def self.reference_pattern
+    @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
+  end
+
   def to_reference(_from_project = nil)
     id
   end
diff --git a/app/models/issue_collection.rb b/app/models/issue_collection.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f0b7d9914c80167f9c5aa94cbcaa3dd9e9b019d7
--- /dev/null
+++ b/app/models/issue_collection.rb
@@ -0,0 +1,42 @@
+# IssueCollection can be used to reduce a list of issues down to a subset.
+#
+# IssueCollection is not meant to be some sort of Enumerable, instead it's meant
+# to take a list of issues and return a new list of issues based on some
+# criteria. For example, given a list of issues you may want to return a list of
+# issues that can be read or updated by a given user.
+class IssueCollection
+  attr_reader :collection
+
+  def initialize(collection)
+    @collection = collection
+  end
+
+  # Returns all the issues that can be updated by the user.
+  def updatable_by_user(user)
+    return collection if user.admin?
+
+    # Given all the issue projects we get a list of projects that the current
+    # user has at least reporter access to.
+    projects_with_reporter_access = user.
+      projects_with_reporter_access_limited_to(project_ids).
+      pluck(:id)
+
+    collection.select do |issue|
+      if projects_with_reporter_access.include?(issue.project_id)
+        true
+      elsif issue.is_a?(Issue)
+        issue.assignee_or_author?(user)
+      else
+        false
+      end
+    end
+  end
+
+  alias_method :visible_to, :updatable_by_user
+
+  private
+
+  def project_ids
+    @project_ids ||= collection.map(&:project_id).uniq
+  end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 686d285410be7ade78112dbc82eccae4b0d66ac5..4c9c7c001dd4a44f32d6608de2746637befa0da1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -624,13 +624,12 @@ class Project < ActiveRecord::Base
   end
 
   def new_issue_address(author)
-    # This feature is disabled for the time being.
-    return nil
+    return unless Gitlab::IncomingEmail.supports_issue_creation? && author
 
-    if Gitlab::IncomingEmail.enabled? && author # rubocop:disable Lint/UnreachableCode
-      Gitlab::IncomingEmail.reply_address(
-        "#{path_with_namespace}+#{author.authentication_token}")
-    end
+    author.ensure_incoming_email_token!
+
+    Gitlab::IncomingEmail.reply_address(
+      "#{path_with_namespace}+#{author.incoming_email_token}")
   end
 
   def build_commit_note(commit)
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 0a493b7a12be00ed0ee827bad9986de98f9f99c7..2dbe007546564704cac3ea72c4c62029e29f6803 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -163,6 +163,21 @@ class JiraService < IssueTrackerService
     add_comment(data, issue_key)
   end
 
+  # reason why service cannot be tested
+  def disabled_title
+    "Please fill in Password and Username."
+  end
+
+  def can_test?
+    username.present? && password.present?
+  end
+
+  # JIRA does not need test data.
+  # We are requesting the project that belongs to the project key.
+  def test_data(user = nil, project = nil)
+    nil
+  end
+
   def test_settings
     return unless url.present?
     # Test settings by getting the project
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 30be7262438496bf0f1d50263b3cbfc9726bbeb1..7d06ce1e85bec0c12e51ca9d3155a8ef97e8ff7a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1064,6 +1064,10 @@ class Repository
   end
 
   def search_files(query, ref)
+    unless exists? && has_visible_content? && query.present?
+      return []
+    end
+
     offset = 2
     args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
     Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
diff --git a/app/models/user.rb b/app/models/user.rb
index 65e96ee6b2e4eda993b81aeaec093c23be6112b5..3813df6684ea7955ac0cebc06cebb91b3d0c78f1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -13,6 +13,7 @@ class User < ActiveRecord::Base
   DEFAULT_NOTIFICATION_LEVEL = :participating
 
   add_authentication_token_field :authentication_token
+  add_authentication_token_field :incoming_email_token
 
   default_value_for :admin, false
   default_value_for(:external) { current_application_settings.user_default_external }
@@ -119,7 +120,7 @@ class User < ActiveRecord::Base
   before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
 
   after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? }
-  before_save :ensure_authentication_token
+  before_save :ensure_authentication_token, :ensure_incoming_email_token
   before_save :ensure_external_user_rights
   after_save :ensure_namespace_correct
   after_initialize :set_projects_limit
@@ -444,6 +445,16 @@ class User < ActiveRecord::Base
     Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
   end
 
+  # Returns the projects this user has reporter (or greater) access to, limited
+  # to at most the given projects.
+  #
+  # This method is useful when you have a list of projects and want to
+  # efficiently check to which of these projects the user has at least reporter
+  # access.
+  def projects_with_reporter_access_limited_to(projects)
+    authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
+  end
+
   def viewable_starred_projects
     starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (#{projects_union.to_sql})",
                            [Project::PUBLIC, Project::INTERNAL])
@@ -946,4 +957,13 @@ class User < ActiveRecord::Base
       signup_domain =~ regexp
     end
   end
+
+  def generate_token(token_field)
+    if token_field == :incoming_email_token
+      # Needs to be all lowercase and alphanumeric because it's gonna be used in an email address.
+      SecureRandom.hex.to_i(16).to_s(36)
+    else
+      super
+    end
+  end
 end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index c253f9a93995ebcfb1b688f037f90c1414e3f577..9501e499507b966aa7086e351647a77aa2e096c8 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -4,7 +4,7 @@ class IssuablePolicy < BasePolicy
   end
 
   def rules
-    if @user && (@subject.author == @user || @subject.assignee == @user)
+    if @user && @subject.assignee_or_author?(@user)
       can! :"read_#{action_name}"
       can! :"update_#{action_name}"
     end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index bd1811a3c54d51e31b75687ee67534d8c6b22af3..52fa33bc4b032fc9c2239815badc60517365b5e3 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -8,9 +8,8 @@ class IssuePolicy < IssuablePolicy
 
     if @subject.confidential? && !can_read_confidential?
       cannot! :read_issue
-      cannot! :admin_issue
       cannot! :update_issue
-      cannot! :read_issue
+      cannot! :admin_issue
     end
   end
 
@@ -18,11 +17,7 @@ class IssuePolicy < IssuablePolicy
 
   def can_read_confidential?
     return false unless @user
-    return true if @user.admin?
-    return true if @subject.author == @user
-    return true if @subject.assignee == @user
-    return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER)
 
-    false
+    IssueCollection.new([@subject]).visible_to(@user).any?
   end
 end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e8415862de51be3daf703dc1e5584f6b2a961188..de313095bedb85b03c950b45a331a6140724be9b 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -105,35 +105,11 @@ class GitPushService < BaseService
   # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
   # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
   def process_commit_messages
-    is_default_branch = is_default_branch?
-
-    authors = Hash.new do |hash, commit|
-      email = commit.author_email
-      next hash[email] if hash.has_key?(email)
-
-      hash[email] = commit_user(commit)
-    end
+    default = is_default_branch?
 
     @push_commits.each do |commit|
-      # Keep track of the issues that will be actually closed because they are on a default branch.
-      # Hence, when creating cross-reference notes, the not-closed issues (on non-default branches)
-      # will also have cross-reference.
-      closed_issues = []
-
-      if is_default_branch
-        # Close issues if these commits were pushed to the project's default branch and the commit message matches the
-        # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
-        # a different branch.
-        closed_issues = commit.closes_issues(current_user)
-        closed_issues.each do |issue|
-          if can?(current_user, :update_issue, issue)
-            Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit: commit)
-          end
-        end
-      end
-
-      commit.create_cross_references!(authors[commit], closed_issues)
-      update_issue_metrics(commit, authors)
+      ProcessCommitWorker.
+        perform_async(project.id, current_user.id, commit.id, default)
     end
   end
 
@@ -176,11 +152,4 @@ class GitPushService < BaseService
   def branch_name
     @branch_name ||= Gitlab::Git.ref_name(params[:ref])
   end
-
-  def update_issue_metrics(commit, authors)
-    mentioned_issues = commit.all_references(authors[commit]).issues
-
-    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil).
-      update_all(first_mentioned_in_commit_at: commit.committed_date)
-  end
 end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 45cca216ccce68016b13a423295f3854c9c1f020..ab4c51386a42973cbe9f3d1ae3291fd7dbf48de6 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,8 +1,21 @@
 module Issues
   class CloseService < Issues::BaseService
+    # Closes the supplied issue if the current user is able to do so.
     def execute(issue, commit: nil, notifications: true, system_note: true)
       return issue unless can?(current_user, :update_issue, issue)
 
+      close_issue(issue,
+                  commit: commit,
+                  notifications: notifications,
+                  system_note: system_note)
+    end
+
+    # Closes the supplied issue without checking if the user is authorized to
+    # do so.
+    #
+    # The code calling this method is responsible for ensuring that a user is
+    # allowed to close the given issue.
+    def close_issue(issue, commit: nil, notifications: true, system_note: true)
       if project.jira_tracker? && project.jira_service.active
         project.jira_service.execute(commit, issue)
         todo_service.close_issue(issue, current_user)
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index c3dfc8cfbe8c974f626b22ebc6a022ca4891c1f5..4b8946f8ee21f4f7d8575fbffae0e1e860bd045c 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -7,6 +7,8 @@
 #
 module Projects
   class HousekeepingService < BaseService
+    include Gitlab::CurrentSettings
+
     LEASE_TIMEOUT = 3600
 
     class LeaseTaken < StandardError
@@ -20,13 +22,14 @@ module Projects
     end
 
     def execute
-      raise LeaseTaken unless try_obtain_lease
+      lease_uuid = try_obtain_lease
+      raise LeaseTaken unless lease_uuid.present?
 
-      execute_gitlab_shell_gc
+      execute_gitlab_shell_gc(lease_uuid)
     end
 
     def needed?
-      @project.pushes_since_gc >= 10
+      pushes_since_gc > 0 && period_match? && housekeeping_enabled?
     end
 
     def increment!
@@ -37,19 +40,59 @@ module Projects
 
     private
 
-    def execute_gitlab_shell_gc
-      GitGarbageCollectWorker.perform_async(@project.id)
+    def execute_gitlab_shell_gc(lease_uuid)
+      GitGarbageCollectWorker.perform_async(@project.id, task, lease_key, lease_uuid)
     ensure
-      Gitlab::Metrics.measure(:reset_pushes_since_gc) do
-        @project.reset_pushes_since_gc
+      if pushes_since_gc >= gc_period
+        Gitlab::Metrics.measure(:reset_pushes_since_gc) do
+          @project.reset_pushes_since_gc
+        end
       end
     end
 
     def try_obtain_lease
       Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
-        lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
+        lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
         lease.try_obtain
       end
     end
+
+    def lease_key
+      "project_housekeeping:#{@project.id}"
+    end
+
+    def pushes_since_gc
+      @project.pushes_since_gc
+    end
+
+    def task
+      if pushes_since_gc % gc_period == 0
+        :gc
+      elsif pushes_since_gc % full_repack_period == 0
+        :full_repack
+      else
+        :incremental_repack
+      end
+    end
+
+    def period_match?
+      [gc_period, full_repack_period, repack_period].any? { |period| pushes_since_gc % period == 0 }
+    end
+
+    def housekeeping_enabled?
+      current_application_settings.housekeeping_enabled
+    end
+
+    def gc_period
+      current_application_settings.housekeeping_gc_period
+    end
+
+    def full_repack_period
+      current_application_settings.housekeeping_full_repack_period
+    end
+
+    def repack_period
+      current_application_settings.housekeeping_incremental_repack_period
+    end
   end
 end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 28003e5f5091dcf647261b80c34f0b86d0227b58..450ec322f2c3adec07efbbf71c9b3810d5a82e05 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -422,5 +422,44 @@
             Enable this option to include the name of the author of the issue,
             merge request or comment in the email body instead.
 
+  %fieldset
+    %legend Automatic Git repository housekeeping
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :housekeeping_enabled do
+            = f.check_box :housekeeping_enabled
+            Enable automatic repository housekeeping (git repack, git gc)
+          .help-block
+            If you keep automatic housekeeping disabled for a long time Git
+            repository access on your GitLab server will become slower and your
+            repositories will use more disk space. We recommend to always leave
+            this enabled.
+        .checkbox
+          = f.label :housekeeping_bitmaps_enabled do
+            = f.check_box :housekeeping_bitmaps_enabled
+            Enable Git pack file bitmap creation
+          .help-block
+            Creating pack file bitmaps makes housekeeping take a little longer but
+            bitmaps should accelerate 'git clone' performance.
+    .form-group
+      = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :housekeeping_incremental_repack_period, class: 'form-control'
+        .help-block
+          Number of Git pushes after which an incremental 'git repack' is run.
+    .form-group
+      = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :housekeeping_full_repack_period, class: 'form-control'
+        .help-block
+          Number of Git pushes after which a full 'git repack' is run.
+    .form-group
+      = f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :housekeeping_gc_period, class: 'form-control'
+        .help-block
+          Number of Git pushes after which 'git gc' is run.
+
   .form-actions
     = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 90798c47d974534ba322180e3b58202c2419c8ba..1db2150f3366f15e9c8acd63ce7d2f54078ec163 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -87,7 +87,7 @@
         %p
           GitLab Workhorse
           %span.pull-right
-            = Gitlab::Workhorse.version
+            = gitlab_workhorse_version
         %p
           GitLab API
           %span.pull-right
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 05c88ca1cc86542b5b9d24b533c1c20ae31961df..664bb417c6a70ea4e76829fc7bc106d2b7193171 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -16,7 +16,7 @@
     %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
       = visibility_level_icon(group.visibility_level, fw: false)
 
-  .image-container.s40
+  .avatar-container.s40
     = image_tag group_icon(group), class: "avatar s40 hidden-xs"
   .title
     = link_to [:admin, group], class: 'group-name' do
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index a7c1a4f5038c8a3a284126eb03bcd8daac19f88c..40871e32913bc5b6aae16147e98339aa6f8273e4 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -13,7 +13,7 @@
         Group info:
       %ul.well-list
         %li
-          .image-container.s60
+          .avatar-container.s60
             = image_tag group_icon(@group), class: "avatar s60"
         %li
           %span.light Name:
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 10dce6f3d8fdcf5105fffeaa3ff3c444ac252463..b37b8d4fee78728d820a7fb0cfc144cbb2016556 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -76,7 +76,7 @@
             .title
               = link_to [:admin, project.namespace.becomes(Namespace), project] do
                 .dash-project-avatar
-                  .image-container.s40
+                  .avatar-container.s40
                     = project_icon(project, alt: '', class: 'avatar project-avatar s40')
                 %span.project-full-name
                   %span.namespace-name
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index e247eebc3fcdc856e339db0724e086c77e607de2..5b2465e25ee75d751919a6b5b16d2d344f6377cb 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -82,15 +82,19 @@
   - elsif current_user.todos.any?
     .todos-all-done
       = render "shared/empty_states/todos_all_done.svg"
-      %h4.text-center
-        Good job! Looks like you don't have any todos left.
-      %p.text-center
-        Are you looking for things to do? Take a look at
-        = succeed "," do
-          = link_to "the opened issues", issues_dashboard_path
-        contribute to
-        = link_to "merge requests", merge_requests_dashboard_path
-        or mention someone in a comment to assign a new todo automatically.
+      - if todos_filter_empty?
+        %h4.text-center
+          Good job! Looks like you don't have any todos left.
+        %p.text-center
+          Are you looking for things to do? Take a look at
+          = succeed "," do
+            = link_to "the opened issues", issues_dashboard_path
+          contribute to
+          = link_to "merge requests", merge_requests_dashboard_path
+          or mention someone in a comment to assign a new todo automatically.
+      - else
+        %h4.text-center
+          There are no todos to show.
   - else
     .todos-empty
       .todos-empty-hero
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2f90c19d4b4b6a699b7d5415e5e88d5a8268fae4..2706e8692d16793c3a707f6d0abc712c5c7f056c 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -8,7 +8,7 @@
 
       .form-group
         .col-sm-offset-2.col-sm-10
-          .image-container.s160
+          .avatar-container.s160
             = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
           %p.light
             - if @group.avatar?
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 275581b3af80cc3aa0285ed565fc673dc3340416..b439b40a75abbbc77a77f05dbabaa4f8d5582e1c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -6,7 +6,7 @@
 
 .cover-block.groups-cover-block
   %div{ class: container_class }
-    .image-container.s70.group-avatar
+    .avatar-container.s70.group-avatar
       = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
     .group-info
       .cover-title
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index 0995826775ae918c2d1515883e1b2837c34e1085..38c852f0a3a1f4b0bd7aa7d6d8dffcd0709a5f00 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -103,11 +103,11 @@
                                           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"}
                                             %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/
                                           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"}
-                                            %a{href: commit_url(@pipeline), style: "color:#3084bb;text-decoration:none;"}
+                                            %a{href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
                                               = @pipeline.short_sha
                                             - if @merge_request
                                               in
-                                              %a{href: merge_request_url(@merge_request), style: "color:#3084bb;text-decoration:none;"}
+                                              %a{href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;"}
                                                 = @merge_request.to_reference
                                     .commit{style: "color:#5c5c5c;font-weight:300;"}
                                       = @pipeline.git_commit_message.truncate(50)
@@ -134,7 +134,7 @@
                         %tr.pre-section
                           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"}
                             Pipeline
-                            %a{href: pipeline_url(@pipeline), style: "color:#3084bb;text-decoration:none;"}
+                            %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
                               = "\##{@pipeline.id}"
                             had
                             = failed.size
@@ -158,7 +158,7 @@
                                             %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"}
                                               = build.stage
                                     %td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"}
-                                      %a{href: pipeline_build_url(@pipeline, build), style: "color:#3084bb;text-decoration:none;"}
+                                      %a{href: pipeline_build_url(@pipeline, build), style: "color:#3777b0;text-decoration:none;"}
                                         = build.name
                                   %tr.build-log
                                     %td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"}
@@ -168,10 +168,10 @@
           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"}
             %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/
             %div
-              %a{href: profile_notifications_url, style: "color:#3084bb;text-decoration:none;"} Manage all notifications
+              %a{href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;"} Manage all notifications
               &middot;
-              %a{href: help_url, style: "color:#3084bb;text-decoration:none;"} Help
+              %a{href: help_url, style: "color:#3777b0;text-decoration:none;"} Help
             %div
               You're receiving this email because of your account on
               = succeed "." do
-                %a{href: root_url, style: "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host
+                %a{href: root_url, style: "color:#3777b0;text-decoration:none;"}= Gitlab.config.gitlab.host
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index cf9c1d4d72c7c7735411491d88e7045c97751414..697c8d19257cf79dd54bfb9e53617dc84248c376 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -103,11 +103,11 @@
                                           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"}
                                             %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/
                                           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"}
-                                            %a{href: commit_url(@pipeline), style: "color:#3084bb;text-decoration:none;"}
+                                            %a{href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
                                               = @pipeline.short_sha
                                             - if @merge_request
                                               in
-                                              %a{href: merge_request_url(@merge_request), style: "color:#3084bb;text-decoration:none;"}
+                                              %a{href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;"}
                                                 = @merge_request.to_reference
                                     .commit{style: "color:#5c5c5c;font-weight:300;"}
                                       = @pipeline.git_commit_message.truncate(50)
@@ -135,7 +135,7 @@
                             - build_count = @pipeline.statuses.latest.size
                             - stage_count = @pipeline.stages.size
                             Pipeline
-                            %a{href: pipeline_url(@pipeline), style: "color:#3084bb;text-decoration:none;"}
+                            %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
                               = "\##{@pipeline.id}"
                             successfully completed
                             = "#{build_count} #{'build'.pluralize(build_count)}"
@@ -145,10 +145,10 @@
           %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"}
             %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/
             %div
-              %a{href: profile_notifications_url, style: "color:#3084bb;text-decoration:none;"} Manage all notifications
+              %a{href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;"} Manage all notifications
               &middot;
-              %a{href: help_url, style: "color:#3084bb;text-decoration:none;"} Help
+              %a{href: help_url, style: "color:#3777b0;text-decoration:none;"} Help
             %div
               You're receiving this email because of your account on
               = succeed "." do
-                %a{href: root_url, style: "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host
+                %a{href: root_url, style: "color:#3777b0;text-decoration:none;"}= Gitlab.config.gitlab.host
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index e2e974ba07219f16c8d37b1067e4b87a4a8842a8..72f658d1b68d70756e5438a69e222d2489db3f32 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -8,24 +8,36 @@
 .row.prepend-top-default
   .col-lg-3.profile-settings-sidebar
     %h4.prepend-top-0
-      Private Token
+      = incoming_email_token_enabled? ? "Private Tokens" : "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|
+      Keep
+      = incoming_email_token_enabled? ? "these tokens" : "this token"
+      secret, anyone with access to them can interact with GitLab as if they were you.
+  .col-lg-9.private-tokens-reset
+    .reset-action
       %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"
+          = label_tag "private-token", "Private token", class: "label-light"
+          = text_field_tag "private-token", current_user.private_token, class: "form-control", readonly: true, onclick: "this.select()"
         - 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!
+          %span You don't have one yet. Click generate to fix it.
+        %p.help-block
+          Your private token is used to access the API and Atom feeds without username/password authentication.
       .prepend-top-default
         - if current_user.private_token
-          = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
+          = link_to 'Reset private token', reset_private_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default private-token"
         - else
           = f.submit 'Generate', class: "btn btn-default"
+    - if incoming_email_token_enabled?
+      .reset-action
+        %p.cgray
+          = label_tag "incoming-email-token", "Incoming Email Token", class: 'label-light'
+          = text_field_tag "incoming-email-token", current_user.incoming_email_token, class: "form-control", readonly: true, onclick: "this.select()"
+        %p.help-block
+          Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.
+        .prepend-top-default
+          = link_to 'Reset incoming email token', reset_incoming_email_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default incoming-email-token"
+
 %hr
 .row.prepend-top-default
   .col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index e67b66d1fffb4d5658702b2e93a41295eede2c49..5a04c3318cfabb806947cde2a6edbed60ff6bfd5 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,7 +1,7 @@
 - empty_repo = @project.empty_repo?
 .project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
   %div{ class: container_class }
-    .image-container.s70.project-avatar
+    .avatar-container.s70.project-avatar
       = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
     %h1.project-title
       = @project.name
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index d8c95376b94ea2966e10488f7d80ab9890d1dc58..0ebc38d16cf95c4bc7ba6f708125502682a0d336 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,25 +1,25 @@
 .commit-info-row.commit-info-row-header
-  %span.hidden-xs.hidden-sm Commit
-  = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace js-details-short"
-  = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
-    %span.text-expander
-      \...
-  %span.js-details-content.hide
-    = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm"
-  = clipboard_button(clipboard_text: @commit.id)
-  %span.hidden-xs authored
-  #{time_ago_with_tooltip(@commit.authored_date)}
-  %span by
-  = author_avatar(@commit, size: 24)
-  %strong
-    = commit_author_link(@commit, avatar: true, size: 24)
-  - if @commit.different_committer?
-    %span.light Committed by
+  .commit-meta
+    %strong Commit
+    %strong.monospace.js-details-short= @commit.short_id
+    = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
+      %span.text-expander
+        \...
+    %span.js-details-content.hide
+      %strong.monospace.commit-hash-full= @commit.id
+    = clipboard_button(clipboard_text: @commit.id)
+    %span.hidden-xs authored
+    #{time_ago_with_tooltip(@commit.authored_date)}
+    %span by
+    = author_avatar(@commit, size: 24)
     %strong
-      = commit_committer_link(@commit, avatar: true, size: 24)
-    #{time_ago_with_tooltip(@commit.committed_date)}
-
-  .pull-right.commit-action-buttons
+      = commit_author_link(@commit, avatar: true, size: 24)
+    - if @commit.different_committer?
+      %span.light Committed by
+      %strong
+        = commit_committer_link(@commit, avatar: true, size: 24)
+      #{time_ago_with_tooltip(@commit.committed_date)}
+  .commit-action-buttons
     - if defined?(@notes_count) && @notes_count > 0
       %span.btn.disabled.btn-grouped.hidden-xs.append-right-10
         = icon('comment')
@@ -28,8 +28,8 @@
       Browse Files
     .dropdown.inline
       %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
-        %span.hidden-xs Options
-        = icon('caret-down', class: ".commit-options-dropdown-caret")
+        %span Options
+        = icon('caret-down')
       %ul.dropdown-menu.dropdown-menu-align-right
         %li.visible-xs-block.visible-sm-block
           = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 73993f35b390020f26f5aa74703a759534328eb5..d3ed8e1bf3897cb701f575d0614a95e7e32a6cec 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -1,4 +1,4 @@
-%i.fa.diff-toggle-caret
+%i.fa.diff-toggle-caret.fa-fw
 - if defined?(blob) && blob && diff_file.submodule?
   %span
     = icon('archive fw')
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index a54229666179acb8d15d8592dd723e3afb58c138..0aa8801c2d8982e35fad17ab5ea38eba30872c42 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -118,7 +118,7 @@
             Project avatar
           .form-group
             - if @project.avatar?
-              .image-container.s160
+              .avatar-container.s160
                 = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
             %p.light
               - if @project.avatar_in_git
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
index 72669372497dc53193d858368bc43d13f2153db4..d2038a2be68ae07ee389179613c7bd1e01ac209a 100644
--- a/app/views/projects/issues/_issue_by_email.html.haml
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -12,16 +12,23 @@
           Create new issue by email
       .modal-body
         %p
-          Write an email to the below email address. (This is a private email address, so keep it secret.)
+          You can create a new issue inside this project by sending an email to the following email address:
         .email-modal-input-group.input-group
           = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
           .input-group-btn
             = clipboard_button(clipboard_target: '#issue_email')
         %p
-          Send an email to this address to create an issue.
-        %p
-          Use the subject line as the title of your issue.
+          The subject will be used as the title of the new issue, and the message will be the description.
+
+          = link_to 'Slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
+          and styling with
+          = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
+          are supported.
+
         %p
-          Use the message as the body of your issue (feel free to include some nice
-          = succeed ")." do
-            = link_to "Markdown", help_page_path('markdown', 'markdown')
+          This is a private email address, generated just for you.
+
+          Anyone who gets ahold of it can create issues as if they were you.
+          You should
+          = link_to 'reset it', new_issue_address_namespace_project_path(@project.namespace, @project), class: 'incoming-email-token-reset'
+          if that ever happens.
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 29df1bab04eed34a05db262ddb1c003275c8fa45..d8951e692421633d4695a6cbc681e07c372812d0 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -17,5 +17,6 @@
               = check_box_tag :filter_ref, 1, @options[:filter_ref]
               %span Begin with the selected commit
 
-    .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
-      = spinner nil, true
+    - if @commit
+      .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
+        = spinner nil, true
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 752fbc21a111e9cfbb56ea5d637c58b03fef4b99..b41edeb2c7eaaa49edb1340d031ca2b54404df39 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -12,6 +12,9 @@
       = form.submit 'Save changes', class: 'btn btn-save'
       &nbsp;
       - if @service.valid? && @service.activated?
-        - disabled = @service.can_test? ? '':'disabled'
-        = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled}", title: @service.disabled_title
+        - unless @service.can_test?
+          - disabled_class = 'disabled'
+          - disabled_title = @service.disabled_title
+
+        = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title
       = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml
index 5b2d83d6b92ecda8cf24fa261d7f1e52931aad59..f34eaf89027965c1f89b84bc4462b5ba369f3107 100644
--- a/app/views/search/results/_commit.html.haml
+++ b/app/views/search/results/_commit.html.haml
@@ -1 +1 @@
-= render 'projects/commits/commit', project: @project, commit: commit
+= render 'projects/commits/commit', project: @project, commit: commit, ref: nil
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 562291a61df41769f9a454e937b5e247430b9972..19221e3391fcb16bf3f70ce06d85c8acc70a43b8 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -24,7 +24,7 @@
     %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
       = visibility_level_icon(group.visibility_level, fw: false)
 
-  .image-container.s40
+  .avatar-container.s40
     = image_tag group_icon(group), class: "avatar s40 hidden-xs"
   .title
     = link_to group, class: 'group-name' do
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 0ace6be8f4e206c37376abbe5e966febf8e10d3c..8d9769527812af4327572c1185cb035f44d15947 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,4 +1,5 @@
 - project = @target_project || @project
+
 = form_errors(issuable)
 
 - if @conflict
@@ -11,23 +12,9 @@
 .form-group
   = f.label :title, class: 'control-label'
 
-  - issuable_template_names = issuable_templates(issuable)
-
-  - if issuable_template_names.any?
-    .col-sm-3.col-lg-2
-      .js-issuable-selector-wrap{ data: { issuable_type: issuable.class.to_s.underscore.downcase } }
-        - title = selected_template(issuable) || "Choose a template"
-
-        = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
-          title: title, filter: true, placeholder: 'Filter', footer_content: true,
-          data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
-          %ul.dropdown-footer-list
-            %li
-              %a.no-template
-                No template
-              %a.reset-template
-                Reset template
-  %div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' }
+  = render 'shared/issuable/form/template_selector', issuable: issuable
+
+  %div{ class: issuable_templates(issuable).any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' }
     = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
         class: 'form-control pad', required: true
 
diff --git a/app/views/shared/issuable/form/_template_selector.html.haml b/app/views/shared/issuable/form/_template_selector.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..d613bd31d81f19db2c657c4219b3f48aff4f21bf
--- /dev/null
+++ b/app/views/shared/issuable/form/_template_selector.html.haml
@@ -0,0 +1,13 @@
+- issuable = local_assigns.fetch(:issuable, nil)
+
+- return unless issuable && issuable_templates(issuable).any?
+
+.col-sm-3.col-lg-2
+  .js-issuable-selector-wrap{ data: { issuable_type: issuable.to_ability_name } }
+    = template_dropdown_tag(issuable) do
+      %ul.dropdown-footer-list
+        %li
+          %a.no-template
+            No template
+          %a.reset-template
+            Reset template
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 3d2122a159ced6b3b0be8aa33fbf79bccb539b72..264391fe84f6fd25403e7e9952a61e9e882cb7fc 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -32,7 +32,7 @@
       = link_to project_path(project), class: dom_class(project) do
         - if avatar
           .dash-project-avatar
-            .image-container.s40
+            .avatar-container.s40
               - if use_creator_avatar
                 = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
               - else
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 78f253f90233a0d3f7f9a96d9e53b7274f45308e..eff6c80d1442b93c3bab6dc628f3309d81902ec4 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,5 +1,5 @@
 .clearfix
   - groups.each do |group|
     = link_to group, class: 'profile-groups-avatars inline', title: group.name do
-      .image-container.s40
+      .avatar-container.s40
         = image_tag group_icon(group), class: 'avatar group-avatar s40'
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 65f8093b5b092d1c0df7243ecbba0ab2cef16c5b..d369b639ae9949df6ae0b5cf50ad7f675f62eded 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -1,17 +1,58 @@
 class GitGarbageCollectWorker
   include Sidekiq::Worker
-  include Gitlab::ShellAdapter
   include DedicatedSidekiqQueue
+  include Gitlab::CurrentSettings
 
   sidekiq_options retry: false
 
-  def perform(project_id)
+  def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil)
     project = Project.find(project_id)
+    task = task.to_sym
+
+    cmd = command(task)
+    repo_path = project.repository.path_to_repo
+    description = "'#{cmd.join(' ')}' in #{repo_path}"
+
+    Gitlab::GitLogger.info(description)
+
+    output, status = Gitlab::Popen.popen(cmd, repo_path)
+    Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
 
-    gitlab_shell.gc(project.repository_storage_path, project.path_with_namespace)
     # Refresh the branch cache in case garbage collection caused a ref lookup to fail
+    flush_ref_caches(project) if task == :gc
+  ensure
+    Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) if lease_key.present? && lease_uuid.present?
+  end
+
+  private
+
+  def command(task)
+    case task
+    when :gc
+      git(write_bitmaps: bitmaps_enabled?) + %w[gc]
+    when :full_repack
+      git(write_bitmaps: bitmaps_enabled?) + %w[repack -A -d --pack-kept-objects]
+    when :incremental_repack
+      # Normal git repack fails when bitmaps are enabled. It is impossible to
+      # create a bitmap here anyway.
+      git(write_bitmaps: false) + %w[repack -d]
+    else
+      raise "Invalid gc task: #{task.inspect}"
+    end
+  end
+
+  def flush_ref_caches(project)
     project.repository.after_create_branch
     project.repository.branch_names
     project.repository.has_visible_content?
   end
+
+  def bitmaps_enabled?
+    current_application_settings.housekeeping_bitmaps_enabled
+  end
+
+  def git(write_bitmaps:)
+    config_value = write_bitmaps ? 'true' : 'false'
+    %W[git -c repack.writeBitmaps=#{config_value}]
+  end
 end
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..071741fbacd2cd6e6aca6860b9731f9a3040cc31
--- /dev/null
+++ b/app/workers/process_commit_worker.rb
@@ -0,0 +1,67 @@
+# Worker for processing individiual commit messages pushed to a repository.
+#
+# Jobs for this worker are scheduled for every commit that is being pushed. As a
+# result of this the workload of this worker should be kept to a bare minimum.
+# Consider using an extra worker if you need to add any extra (and potentially
+# slow) processing of commits.
+class ProcessCommitWorker
+  include Sidekiq::Worker
+  include DedicatedSidekiqQueue
+
+  # project_id - The ID of the project this commit belongs to.
+  # user_id - The ID of the user that pushed the commit.
+  # commit_sha - The SHA1 of the commit to process.
+  # default - The data was pushed to the default branch.
+  def perform(project_id, user_id, commit_sha, default = false)
+    project = Project.find_by(id: project_id)
+
+    return unless project
+
+    user = User.find_by(id: user_id)
+
+    return unless user
+
+    commit = find_commit(project, commit_sha)
+
+    return unless commit
+
+    author = commit.author || user
+
+    process_commit_message(project, commit, user, author, default)
+
+    update_issue_metrics(commit, author)
+  end
+
+  def process_commit_message(project, commit, user, author, default = false)
+    closed_issues = default ? commit.closes_issues(user) : []
+
+    unless closed_issues.empty?
+      close_issues(project, user, author, commit, closed_issues)
+    end
+
+    commit.create_cross_references!(author, closed_issues)
+  end
+
+  def close_issues(project, user, author, commit, issues)
+    # We don't want to run permission related queries for every single issue,
+    # therefor we use IssueCollection here and skip the authorization check in
+    # Issues::CloseService#execute.
+    IssueCollection.new(issues).updatable_by_user(user).each do |issue|
+      Issues::CloseService.new(project, author).
+        close_issue(issue, commit: commit)
+    end
+  end
+
+  def update_issue_metrics(commit, author)
+    mentioned_issues = commit.all_references(author).issues
+
+    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil).
+      update_all(first_mentioned_in_commit_at: commit.committed_date)
+  end
+
+  private
+
+  def find_commit(project, sha)
+    project.commit(sha)
+  end
+end
diff --git a/changelogs/unreleased/21664-incorrect-workhorse-version-number-displayed.yml b/changelogs/unreleased/21664-incorrect-workhorse-version-number-displayed.yml
new file mode 100644
index 0000000000000000000000000000000000000000..95d8fef10990431370b9bb2151a2132c9e49cd74
--- /dev/null
+++ b/changelogs/unreleased/21664-incorrect-workhorse-version-number-displayed.yml
@@ -0,0 +1,4 @@
+---
+title: Use the Gitlab Workhorse HTTP header in the admin dashboard
+merge_request:
+author: Chris Wright
diff --git a/changelogs/unreleased/23036-replace-git-blame-spinach-tests-with-rspec-feature-tests.yml b/changelogs/unreleased/23036-replace-git-blame-spinach-tests-with-rspec-feature-tests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7b54d3df56d2b105dfed01369687bbecbe224ea9
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-git-blame-spinach-tests-with-rspec-feature-tests.yml
@@ -0,0 +1,4 @@
+---
+title: Rewrite git blame spinach feature tests to rspec feature tests
+merge_request: 7197
+author: Lisanne Fellinger
diff --git a/changelogs/unreleased/24255-search-fix.yml b/changelogs/unreleased/24255-search-fix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0afade9bc89e285822e8343a8bb90b58662ef38
--- /dev/null
+++ b/changelogs/unreleased/24255-search-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fix broken commits search
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/add-api-label-id.yml b/changelogs/unreleased/add-api-label-id.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3af4f5e677d79b41ca9230ee5e154b8efebb951f
--- /dev/null
+++ b/changelogs/unreleased/add-api-label-id.yml
@@ -0,0 +1,4 @@
+---
+title: Expose label IDs in API
+merge_request: 7275
+author: Rares Sfirlogea
diff --git a/changelogs/unreleased/add-project-import-data-index.yml b/changelogs/unreleased/add-project-import-data-index.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5e4005f544fa04e4ffbe038d8baf77b5a6da013
--- /dev/null
+++ b/changelogs/unreleased/add-project-import-data-index.yml
@@ -0,0 +1,4 @@
+---
+title: Add an index for project_id in project_import_data to improve performance
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/api-label-priorities.yml b/changelogs/unreleased/api-label-priorities.yml
new file mode 100644
index 0000000000000000000000000000000000000000..85b6c2761bb1a74d1d259cee3e3ba3ce8d80e1b1
--- /dev/null
+++ b/changelogs/unreleased/api-label-priorities.yml
@@ -0,0 +1,4 @@
+---
+title: API: Ability to retrieve version information
+merge_request: 7286
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-return-400-if-post-systemhook-fails.yml b/changelogs/unreleased/api-return-400-if-post-systemhook-fails.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d132d7e79c3ff3a22e9ac4be2e302e2a87e067f9
--- /dev/null
+++ b/changelogs/unreleased/api-return-400-if-post-systemhook-fails.yml
@@ -0,0 +1,4 @@
+---
+title: Return 400 when creating a system hook fails
+merge_request: 7350
+author: Robert Schilling
diff --git a/changelogs/unreleased/broken-link-frontend-dev-guide.yml b/changelogs/unreleased/broken-link-frontend-dev-guide.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d7b6f4a77018aebee89564b73b22fcf33558bcc3
--- /dev/null
+++ b/changelogs/unreleased/broken-link-frontend-dev-guide.yml
@@ -0,0 +1,4 @@
+---
+title: Fix broken link to observatory cli on Frontend Dev Guide
+merge_request: 
+author: Sam Rose
diff --git a/changelogs/unreleased/faster_project_search.yml b/changelogs/unreleased/faster_project_search.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e29a9f34ed45ec8710f00f76ef0018b43c4b212b
--- /dev/null
+++ b/changelogs/unreleased/faster_project_search.yml
@@ -0,0 +1,4 @@
+---
+title: Faster search inside Project
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/fix-404-on-network-when-entering-a-nonexistent-git-revision.yml b/changelogs/unreleased/fix-404-on-network-when-entering-a-nonexistent-git-revision.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d1bc8ea2eb154a2c30f45a0b89906cce614bd0be
--- /dev/null
+++ b/changelogs/unreleased/fix-404-on-network-when-entering-a-nonexistent-git-revision.yml
@@ -0,0 +1,4 @@
+---
+title: Fix 404 on network page when entering non-existent git revision
+merge_request: 7172
+author: Hiroyuki Sato
diff --git a/changelogs/unreleased/git-gc-improvements.yml b/changelogs/unreleased/git-gc-improvements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f15e667ce87357ba1e41127c1de35d43e0e0ee77
--- /dev/null
+++ b/changelogs/unreleased/git-gc-improvements.yml
@@ -0,0 +1,4 @@
+---
+title: Finer-grained Git gargage collection
+merge_request: 6588
+author: 
diff --git a/changelogs/unreleased/issue_23032.yml b/changelogs/unreleased/issue_23032.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d376cf5211208e6eb09156a7bf2679eab91107c6
--- /dev/null
+++ b/changelogs/unreleased/issue_23032.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to test JIRA service settings without having a repository
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/process-commits-using-sidekiq.yml b/changelogs/unreleased/process-commits-using-sidekiq.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9f596e6a584c3ff8236b980daf03261de8a1293c
--- /dev/null
+++ b/changelogs/unreleased/process-commits-using-sidekiq.yml
@@ -0,0 +1,4 @@
+---
+title: Process commits using a dedicated Sidekiq worker
+merge_request: 6802
+author: 
diff --git a/changelogs/unreleased/sh-bump-omniauth-gitlab.yml b/changelogs/unreleased/sh-bump-omniauth-gitlab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..17cd5a993dd1b7870c9e34914f8642d485e9a605
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-omniauth-gitlab.yml
@@ -0,0 +1,4 @@
+---
+title: Bump omniauth-gitlab to 1.0.2 to fix incompatibility with omniauth-oauth2
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/sidekiq_default_retries.yml b/changelogs/unreleased/sidekiq_default_retries.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3df2a415dbcb6066dc79fae9c655342784f4979e
--- /dev/null
+++ b/changelogs/unreleased/sidekiq_default_retries.yml
@@ -0,0 +1,4 @@
+---
+title: Set default Sidekiq retries to 3
+merge_request: 7294
+author: 
diff --git a/changelogs/unreleased/use-separate-token-for-incoming-email.yml b/changelogs/unreleased/use-separate-token-for-incoming-email.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e498f8dd0a6b7002fba35850db98350a38012947
--- /dev/null
+++ b/changelogs/unreleased/use-separate-token-for-incoming-email.yml
@@ -0,0 +1,4 @@
+---
+title: Use separate email-token for incoming email and revert back the inactive feature
+merge_request: 5914
+author: 
diff --git a/config/initializers/routing_draw.rb b/config/initializers/routing_draw.rb
new file mode 100644
index 0000000000000000000000000000000000000000..25003cf0239045f497a9153b8a7c286ecce18a2f
--- /dev/null
+++ b/config/initializers/routing_draw.rb
@@ -0,0 +1,7 @@
+# Adds draw method into Rails routing
+# It allows us to keep routing splitted into files
+class ActionDispatch::Routing::Mapper
+  def draw(routes_name)
+    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
+  end
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 0455a98dbfe991eb66b3d189279101ae7ed34399..023af2af23caa75b2e1cd0f725c8fdf123221f3c 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -2,6 +2,9 @@
 redis_config_hash = Gitlab::Redis.params
 redis_config_hash[:namespace] = Gitlab::Redis::SIDEKIQ_NAMESPACE
 
+# Default is to retry 25 times with exponential backoff. That's too much.
+Sidekiq.default_worker_options = { retry: 3 }
+
 Sidekiq.configure_server do |config|
   config.redis = redis_config_hash
 
diff --git a/config/routes.rb b/config/routes.rb
index 659ea51bc75b67e6dbcd842b53312483e1574112..7bf6c03e69b4e974543eb972312f5f6711f7871a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,12 +2,6 @@ require 'sidekiq/web'
 require 'sidekiq/cron/web'
 require 'api/api'
 
-class ActionDispatch::Routing::Mapper
-  def draw(routes_name)
-    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
-  end
-end
-
 Rails.application.routes.draw do
   concern :access_requestable do
     post :request_access, on: :collection
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03adc4815f38be4204cbe0762bac4b8341cb7a3f
--- /dev/null
+++ b/config/routes/git_http.rb
@@ -0,0 +1,37 @@
+scope constraints: { id: /.+\.git/, format: nil } do
+  # Git HTTP clients ('git clone' etc.)
+  get '/info/refs', to: 'git_http#info_refs'
+  post '/git-upload-pack', to: 'git_http#git_upload_pack'
+  post '/git-receive-pack', to: 'git_http#git_receive_pack'
+
+  # Git LFS API (metadata)
+  post '/info/lfs/objects/batch', to: 'lfs_api#batch'
+  post '/info/lfs/objects', to: 'lfs_api#deprecated'
+  get '/info/lfs/objects/*oid', to: 'lfs_api#deprecated'
+
+  # GitLab LFS object storage
+  scope constraints: { oid: /[a-f0-9]{64}/ } do
+    get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#download'
+
+    scope constraints: { size: /[0-9]+/ } do
+      put '/gitlab-lfs/objects/*oid/*size/authorize', to: 'lfs_storage#upload_authorize'
+      put '/gitlab-lfs/objects/*oid/*size', to: 'lfs_storage#upload_finalize'
+    end
+  end
+end
+
+# Allow /info/refs, /info/refs?service=git-upload-pack, and
+# /info/refs?service=git-receive-pack, but nothing else.
+#
+git_http_handshake = lambda do |request|
+  request.query_string.blank? ||
+    request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)
+end
+
+ref_redirect = redirect do |params, request|
+  path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs"
+  path << "?#{request.query_string}" unless request.query_string.blank?
+  path
+end
+
+get '/info/refs', constraints: git_http_handshake, to: ref_redirect
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 826048ba196cf9290cd624c4022f034860bbce2e..3c392f77ef67746734666a2c72a54d4ac5059690 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -3,7 +3,7 @@ require 'constraints/group_url_constrainer'
 constraints(GroupUrlConstrainer.new) do
   scope(path: ':id',
         as: :group,
-        constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
+        constraints: { id: Gitlab::Regex.namespace_route_regex },
         controller: :groups) do
     get '/', action: :show
     patch '/', action: :update
@@ -12,26 +12,26 @@ constraints(GroupUrlConstrainer.new) do
   end
 end
 
-scope constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do
-  resources :groups, except: [:show] do
-    member do
-      get :issues
-      get :merge_requests
-      get :projects
-      get :activity
-    end
+resources :groups, only: [:index, :new, :create]
 
-    scope module: :groups do
-      resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
-        post :resend_invite, on: :member
-        delete :leave, on: :collection
-      end
-
-      resource :avatar, only: [:destroy]
-      resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
+scope(path: 'groups/:id', controller: :groups) do
+  get :edit, as: :edit_group
+  get :issues, as: :issues_group
+  get :merge_requests, as: :merge_requests_group
+  get :projects, as: :projects_group
+  get :activity, as: :activity_group
+end
 
-      resources :labels, except: [:show], constraints: { id: /\d+/ }
-    end
+scope(path: 'groups/:group_id', module: :groups, as: :group) do
+  resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
+    post :resend_invite, on: :member
+    delete :leave, on: :collection
   end
-  get 'groups/:id' => 'groups#show', as: :group_canonical
+
+  resource :avatar, only: [:destroy]
+  resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
+  resources :labels, except: [:show], constraints: { id: /\d+/ }
 end
+
+# Must be last route in this file
+get 'groups/:id' => 'groups#show', as: :group_canonical
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index 4cb68c9b34a7cf6dcfbb670e546c5c6de848365c..52b9a565db8654ccce3746057e4b99df7fe00dc8 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -4,6 +4,7 @@ resource :profile, only: [:show, :update] do
     get :applications, to: 'oauth/applications#index'
 
     put :reset_private_token
+    put :reset_incoming_email_token
     put :update_username
   end
 
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 8142e231621e7e5fbdbbdb33bb6fb741e4db0784..82defb0ba7150baea051d5b9254cdae08e04a860 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -18,152 +18,17 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
       get :autocomplete_sources
       get :activity
       get :refs
+      put :new_issue_address
     end
 
     scope module: :projects do
-      scope constraints: { id: /.+\.git/, format: nil } do
-        # Git HTTP clients ('git clone' etc.)
-        get '/info/refs', to: 'git_http#info_refs'
-        post '/git-upload-pack', to: 'git_http#git_upload_pack'
-        post '/git-receive-pack', to: 'git_http#git_receive_pack'
-
-        # Git LFS API (metadata)
-        post '/info/lfs/objects/batch', to: 'lfs_api#batch'
-        post '/info/lfs/objects', to: 'lfs_api#deprecated'
-        get '/info/lfs/objects/*oid', to: 'lfs_api#deprecated'
-
-        # GitLab LFS object storage
-        scope constraints: { oid: /[a-f0-9]{64}/ } do
-          get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#download'
-
-          scope constraints: { size: /[0-9]+/ } do
-            put '/gitlab-lfs/objects/*oid/*size/authorize', to: 'lfs_storage#upload_authorize'
-            put '/gitlab-lfs/objects/*oid/*size', to: 'lfs_storage#upload_finalize'
-          end
-        end
-      end
-
-      # Allow /info/refs, /info/refs?service=git-upload-pack, and
-      # /info/refs?service=git-receive-pack, but nothing else.
-      #
-      git_http_handshake = lambda do |request|
-        request.query_string.blank? ||
-          request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)
-      end
-
-      ref_redirect = redirect do |params, request|
-        path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs"
-        path << "?#{request.query_string}" unless request.query_string.blank?
-        path
-      end
-
-      get '/info/refs', constraints: git_http_handshake, to: ref_redirect
-
-      # Blob routes:
-      get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
-      post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
-      get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
-      put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
-      post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
+      draw :git_http
 
       #
       # Templates
       #
       get '/templates/:template_type/:key' => 'templates#show', as: :template
 
-      scope do
-        get(
-          '/blob/*id/diff',
-          to: 'blob#diff',
-          constraints: { id: /.+/, format: false },
-          as: :blob_diff
-        )
-        get(
-          '/blob/*id',
-          to: 'blob#show',
-          constraints: { id: /.+/, format: false },
-          as: :blob
-        )
-        delete(
-          '/blob/*id',
-          to: 'blob#destroy',
-          constraints: { id: /.+/, format: false }
-        )
-        put(
-          '/blob/*id',
-          to: 'blob#update',
-          constraints: { id: /.+/, format: false }
-        )
-        post(
-          '/blob/*id',
-          to: 'blob#create',
-          constraints: { id: /.+/, format: false }
-        )
-      end
-
-      scope do
-        get(
-          '/raw/*id',
-          to: 'raw#show',
-          constraints: { id: /.+/, format: /(html|js)/ },
-          as: :raw
-        )
-      end
-
-      scope do
-        get(
-          '/tree/*id',
-          to: 'tree#show',
-          constraints: { id: /.+/, format: /(html|js)/ },
-          as: :tree
-        )
-      end
-
-      scope do
-        get(
-          '/find_file/*id',
-          to: 'find_file#show',
-          constraints: { id: /.+/, format: /html/ },
-          as: :find_file
-        )
-      end
-
-      scope do
-        get(
-          '/files/*id',
-          to: 'find_file#list',
-          constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ },
-          as: :files
-        )
-      end
-
-      scope do
-        post(
-          '/create_dir/*id',
-            to: 'tree#create_dir',
-            constraints: { id: /.+/ },
-            as: 'create_dir'
-        )
-      end
-
-      scope do
-        get(
-          '/blame/*id',
-          to: 'blame#show',
-          constraints: { id: /.+/, format: /(html|js)/ },
-          as: :blame
-        )
-      end
-
-      scope do
-        get(
-          '/commits/*id',
-          to: 'commits#show',
-          constraints: { id: /.+/, format: false },
-          as: :commits
-        )
-      end
-
       resource  :avatar, only: [:show, :destroy]
       resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
         member do
@@ -206,29 +71,6 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
         end
       end
 
-      WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID
-
-      scope do
-        # Order matters to give priority to these matches
-        get '/wikis/git_access', to: 'wikis#git_access'
-        get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages'
-        post '/wikis', to: 'wikis#create'
-
-        get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID
-        get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID
-
-        get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID
-        delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID
-        put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID
-        post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown'
-      end
-
-      resource :repository, only: [:create] do
-        member do
-          get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
-        end
-      end
-
       resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
         member do
           get :test
@@ -245,23 +87,6 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
       resources :forks, only: [:index, :new, :create]
       resource :import, only: [:new, :create, :show]
 
-      resources :refs, only: [] do
-        collection do
-          get 'switch'
-        end
-
-        member do
-          # tree viewer logs
-          get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
-          # Directories with leading dots erroneously get rejected if git
-          # ref regex used in constraints. Regex verification now done in controller.
-          get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
-            id: /.*/,
-            path: /.*/
-          }
-        end
-      end
-
       resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do
         member do
           get :commits
@@ -467,6 +292,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
           end
         end
       end
+
+      # Since both wiki and repository routing contains wildcard characters
+      # its preferable to keep it below all other project routes
+      draw :wiki
+      draw :repository
     end
   end
 end
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
new file mode 100644
index 0000000000000000000000000000000000000000..76dcf113aea6dd9ef2fae24498369d4e815f57a3
--- /dev/null
+++ b/config/routes/repository.rb
@@ -0,0 +1,110 @@
+# All routing related to repositoty browsing
+
+resource :repository, only: [:create] do
+  member do
+    get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
+  end
+end
+
+resources :refs, only: [] do
+  collection do
+    get 'switch'
+  end
+
+  member do
+    # tree viewer logs
+    get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+    # Directories with leading dots erroneously get rejected if git
+    # ref regex used in constraints. Regex verification now done in controller.
+    get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
+      id: /.*/,
+      path: /.*/
+    }
+  end
+end
+
+get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
+post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
+get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
+put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
+post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
+
+scope do
+  get(
+    '/blob/*id/diff',
+    to: 'blob#diff',
+    constraints: { id: /.+/, format: false },
+    as: :blob_diff
+  )
+  get(
+    '/blob/*id',
+    to: 'blob#show',
+    constraints: { id: /.+/, format: false },
+    as: :blob
+  )
+  delete(
+    '/blob/*id',
+    to: 'blob#destroy',
+    constraints: { id: /.+/, format: false }
+  )
+  put(
+    '/blob/*id',
+    to: 'blob#update',
+    constraints: { id: /.+/, format: false }
+  )
+  post(
+    '/blob/*id',
+    to: 'blob#create',
+    constraints: { id: /.+/, format: false }
+  )
+
+  get(
+    '/raw/*id',
+    to: 'raw#show',
+    constraints: { id: /.+/, format: /(html|js)/ },
+    as: :raw
+  )
+
+  get(
+    '/tree/*id',
+    to: 'tree#show',
+    constraints: { id: /.+/, format: /(html|js)/ },
+    as: :tree
+  )
+
+  get(
+    '/find_file/*id',
+    to: 'find_file#show',
+    constraints: { id: /.+/, format: /html/ },
+    as: :find_file
+  )
+
+  get(
+    '/files/*id',
+    to: 'find_file#list',
+    constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ },
+    as: :files
+  )
+
+  post(
+    '/create_dir/*id',
+      to: 'tree#create_dir',
+      constraints: { id: /.+/ },
+      as: 'create_dir'
+  )
+
+  get(
+    '/blame/*id',
+    to: 'blame#show',
+    constraints: { id: /.+/, format: /(html|js)/ },
+    as: :blame
+  )
+
+  # File/dir history
+  get(
+    '/commits/*id',
+    to: 'commits#show',
+    constraints: { id: /.+/, format: false },
+    as: :commits
+  )
+end
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 0a9c924863da8d86f6e46bd5cc95146856bd8e6c..dc1068af6f632658bc9ea447eab0d00e7d569e3e 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -14,31 +14,32 @@ end
 constraints(UserUrlConstrainer.new) do
   scope(path: ':username',
         as: :user,
-        constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
+        constraints: { username: Gitlab::Regex.namespace_route_regex },
         controller: :users) do
     get '/', action: :show
   end
 end
 
-scope(path: 'users/:username',
-      as: :user,
-      constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
-      controller: :users) do
-  get :calendar
-  get :calendar_activities
-  get :groups
-  get :projects
-  get :contributed, as: :contributed_projects
-  get :snippets
-  get :exists
-  get '/', to: redirect('/%{username}')
-end
+scope(constraints: { username: Gitlab::Regex.namespace_route_regex }) do
+  scope(path: 'users/:username',
+        as: :user,
+        controller: :users) do
+    get :calendar
+    get :calendar_activities
+    get :groups
+    get :projects
+    get :contributed, as: :contributed_projects
+    get :snippets
+    get :exists
+    get '/', to: redirect('/%{username}')
+  end
 
-# Compatibility with old routing
-# TODO (dzaporozhets): remove in 10.0
-get '/u/:username', to: redirect('/%{username}'), constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
-# TODO (dzaporozhets): remove in 9.0
-get '/u/:username/groups', to: redirect('/users/%{username}/groups'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
-get '/u/:username/projects', to: redirect('/users/%{username}/projects'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
-get '/u/:username/snippets', to: redirect('/users/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
-get '/u/:username/contributed', to: redirect('/users/%{username}/contributed'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
+  # Compatibility with old routing
+  # TODO (dzaporozhets): remove in 10.0
+  get '/u/:username', to: redirect('/%{username}')
+  # TODO (dzaporozhets): remove in 9.0
+  get '/u/:username/groups', to: redirect('/users/%{username}/groups')
+  get '/u/:username/projects', to: redirect('/users/%{username}/projects')
+  get '/u/:username/snippets', to: redirect('/users/%{username}/snippets')
+  get '/u/:username/contributed', to: redirect('/users/%{username}/contributed')
+end
diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ecd4d395d668277aafa7fcc57f18534e1f086477
--- /dev/null
+++ b/config/routes/wiki.rb
@@ -0,0 +1,16 @@
+WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID
+
+scope do
+  # Order matters to give priority to these matches
+  get '/wikis/git_access', to: 'wikis#git_access'
+  get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages'
+  post '/wikis', to: 'wikis#create'
+
+  get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID
+  get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID
+
+  get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID
+  delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID
+  put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID
+  post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown'
+end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index f36fe893fd0cf9c9bb508d6f016d7d5c768b5996..0aec8aedf724ffd979ef88ac6043bdc9d63cb97c 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -21,6 +21,7 @@
   - [post_receive, 5]
   - [merge, 5]
   - [update_merge_requests, 3]
+  - [process_commit, 2]
   - [new_note, 2]
   - [build, 2]
   - [pipeline, 2]
diff --git a/db/migrate/20160819232256_add_incoming_email_token_to_users.rb b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f2cf956adc96faa27c3e52891c1ac9f7a2d6113a
--- /dev/null
+++ b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb
@@ -0,0 +1,16 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIncomingEmailTokenToUsers < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def change
+    add_column :users, :incoming_email_token, :string
+    add_concurrent_index :users, :incoming_email_token
+  end
+end
diff --git a/db/migrate/20161031155516_add_housekeeping_to_application_settings.rb b/db/migrate/20161031155516_add_housekeeping_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5a451fb575b8c83441544b08786fbb2c263be6b7
--- /dev/null
+++ b/db/migrate/20161031155516_add_housekeeping_to_application_settings.rb
@@ -0,0 +1,32 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddHousekeepingToApplicationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default(:application_settings, :housekeeping_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:application_settings, :housekeeping_bitmaps_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:application_settings, :housekeeping_incremental_repack_period, :integer, default: 10, allow_null: false)
+    add_column_with_default(:application_settings, :housekeeping_full_repack_period, :integer, default: 50, allow_null: false)
+    add_column_with_default(:application_settings, :housekeeping_gc_period, :integer, default: 200, allow_null: false)
+  end
+
+  def down
+    remove_column(:application_settings, :housekeeping_enabled, :boolean, default: true, allow_null: false)
+    remove_column(:application_settings, :housekeeping_bitmaps_enabled, :boolean, default: true, allow_null: false)
+    remove_column(:application_settings, :housekeeping_incremental_repack_period, :integer, default: 10, allow_null: false)
+    remove_column(:application_settings, :housekeeping_full_repack_period, :integer, default: 50, allow_null: false)
+    remove_column(:application_settings, :housekeeping_gc_period, :integer, default: 200, allow_null: false)
+  end
+end
diff --git a/db/migrate/20161103171205_rename_repository_storage_column.rb b/db/migrate/20161103171205_rename_repository_storage_column.rb
index e9f992793b42cc98d4cc45da41963a9e0a9a5a9c..932805739394b592086b826be37ba0e68e424e8e 100644
--- a/db/migrate/20161103171205_rename_repository_storage_column.rb
+++ b/db/migrate/20161103171205_rename_repository_storage_column.rb
@@ -5,12 +5,12 @@ class RenameRepositoryStorageColumn < ActiveRecord::Migration
   include Gitlab::Database::MigrationHelpers
 
   # Set this constant to true if this migration requires downtime.
-  DOWNTIME = false
+  DOWNTIME = true
 
   # When a migration requires downtime you **must** uncomment the following
   # constant and define a short and easy to understand explanation as to why the
   # migration requires downtime.
-  # DOWNTIME_REASON = ''
+  DOWNTIME_REASON = 'Renaming the application_settings.repository_storage column'
 
   # When using the methods "add_concurrent_index" or "add_column_with_default"
   # you must disable the use of transactions as these methods can not run in an
diff --git a/db/migrate/20161106185620_add_project_import_data_project_index.rb b/db/migrate/20161106185620_add_project_import_data_project_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..750a6a8c51ebab2aef1adc088a9593e5e43423dd
--- /dev/null
+++ b/db/migrate/20161106185620_add_project_import_data_project_index.rb
@@ -0,0 +1,12 @@
+class AddProjectImportDataProjectIndex < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def change
+    add_concurrent_index :project_import_data, :project_id
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5476b0c93e5c7840287e8254a2ace977993bd635..62c325a52d7f04d0cfe6dce5870644f111c33b10 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20161103171205) do
+ActiveRecord::Schema.define(version: 20161106185620) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -98,6 +98,11 @@ ActiveRecord::Schema.define(version: 20161103171205) do
     t.text "help_page_text_html"
     t.text "shared_runners_text_html"
     t.text "after_sign_up_text_html"
+    t.boolean "housekeeping_enabled", default: true, null: false
+    t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
+    t.integer "housekeeping_incremental_repack_period", default: 10, null: false
+    t.integer "housekeeping_full_repack_period", default: 50, null: false
+    t.integer "housekeeping_gc_period", default: 200, null: false
   end
 
   create_table "audit_events", force: :cascade do |t|
@@ -867,6 +872,8 @@ ActiveRecord::Schema.define(version: 20161103171205) do
     t.string "encrypted_credentials_salt"
   end
 
+  add_index "project_import_data", ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
+
   create_table "projects", force: :cascade do |t|
     t.string "name"
     t.string "path"
@@ -1176,6 +1183,7 @@ ActiveRecord::Schema.define(version: 20161103171205) do
     t.boolean "ldap_email", default: false, null: false
     t.boolean "external", default: false
     t.string "organization"
+    t.string "incoming_email_token"
   end
 
   add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -1185,6 +1193,7 @@ ActiveRecord::Schema.define(version: 20161103171205) do
   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", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
   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
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index ad1fa98b63ba2f4ef81d0fecb238b78d794afac9..f846c06ca4224fa39a32c5b77563fe0b7c559ee4 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -3,6 +3,14 @@
 > [Introduced][ce-2371] in GitLab 8.4.
 
 ---
+## Automatic housekeeping
+
+GitLab automatically runs `git gc` and `git repack` on repositories
+after Git pushes. If needed you can change how often this happens, or
+to turn it off, go to **Admin area > Settings**
+(`/admin/application_settings`).
+
+## Manual housekeeping
 
 The housekeeping function runs `git gc` ([man page][man]) on the current
 project Git repository.
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 656232cc9403e78527443680818c0d7148061a71..78686fdcad4d13ee67ec6ae3182e42178de4d234 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -20,46 +20,61 @@ Example response:
 
 ```json
 [
-   {
-      "name" : "bug",
-      "color" : "#d9534f",
-      "description": "Bug reported by user",
-      "open_issues_count": 1,
-      "closed_issues_count": 0,
-      "open_merge_requests_count": 1
-   },
-   {
-      "color" : "#d9534f",
-      "name" : "confirmed",
-      "description": "Confirmed issue",
-      "open_issues_count": 2,
-      "closed_issues_count": 5,
-      "open_merge_requests_count": 0
-   },
-   {
-      "name" : "critical",
-      "color" : "#d9534f",
-      "description": "Critical issue. Need fix ASAP",
-      "open_issues_count": 1,
-      "closed_issues_count": 3,
-      "open_merge_requests_count": 1
-   },
-   {
-      "name" : "documentation",
-      "color" : "#f0ad4e",
-      "description": "Issue about documentation",
-      "open_issues_count": 1,
-      "closed_issues_count": 0,
-      "open_merge_requests_count": 2
-   },
-   {
-      "color" : "#5cb85c",
-      "name" : "enhancement",
-      "description": "Enhancement proposal",
-      "open_issues_count": 1,
-      "closed_issues_count": 0,
-      "open_merge_requests_count": 1
-   }
+  {
+    "id" : 1,
+    "name" : "bug",
+    "color" : "#d9534f",
+    "description": "Bug reported by user",
+    "open_issues_count": 1,
+    "closed_issues_count": 0,
+    "open_merge_requests_count": 1,
+    "subscribed": false,
+    "priority": 10
+  },
+  {
+    "id" : 4,
+    "color" : "#d9534f",
+    "name" : "confirmed",
+    "description": "Confirmed issue",
+    "open_issues_count": 2,
+    "closed_issues_count": 5,
+    "open_merge_requests_count": 0,
+    "subscribed": false,
+    "priority": null
+  },
+  {
+    "id" : 7,
+    "name" : "critical",
+    "color" : "#d9534f",
+    "description": "Critical issue. Need fix ASAP",
+    "open_issues_count": 1,
+    "closed_issues_count": 3,
+    "open_merge_requests_count": 1,
+    "subscribed": false,
+    "priority": null
+  },
+  {
+    "id" : 8,
+    "name" : "documentation",
+    "color" : "#f0ad4e",
+    "description": "Issue about documentation",
+    "open_issues_count": 1,
+    "closed_issues_count": 0,
+    "open_merge_requests_count": 2,
+    "subscribed": false,
+    "priority": null
+  },
+  {
+    "id" : 9,
+    "color" : "#5cb85c",
+    "name" : "enhancement",
+    "description": "Enhancement proposal",
+    "open_issues_count": 1,
+    "closed_issues_count": 0,
+    "open_merge_requests_count": 1,
+    "subscribed": true,
+    "priority": null
+  }
 ]
 ```
 
@@ -80,6 +95,7 @@ POST /projects/:id/labels
 | `name`        | string  | yes      | The name of the label        |
 | `color`       | string  | yes      | The color of the label in 6-digit hex notation with leading `#` sign |
 | `description` | string  | no       | The description of the label |
+| `priority`    | integer | no       | The priority of the label. Must be greater or equal than zero or `null` to remove the priority. |
 
 ```bash
 curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
@@ -89,9 +105,15 @@ Example response:
 
 ```json
 {
-   "name" : "feature",
-   "color" : "#5843AD",
-   "description":null
+  "id" : 10,
+  "name" : "feature",
+  "color" : "#5843AD",
+  "description":null,
+  "open_issues_count": 0,
+  "closed_issues_count": 0,
+  "open_merge_requests_count": 0,
+  "subscribed": false,
+  "priority": null
 }
 ```
 
@@ -120,14 +142,15 @@ Example response:
 
 ```json
 {
-   "title" : "feature",
-   "color" : "#5843AD",
-   "description": "New feature proposal",
-   "updated_at" : "2015-11-03T21:22:30.737Z",
-   "template" : false,
-   "project_id" : 1,
-   "created_at" : "2015-11-03T21:22:30.737Z",
-   "id" : 9
+  "id" : 1,
+  "name" : "bug",
+  "color" : "#d9534f",
+  "description": "Bug reported by user",
+  "open_issues_count": 1,
+  "closed_issues_count": 0,
+  "open_merge_requests_count": 1,
+  "subscribed": false,
+  "priority": null
 }
 ```
 
@@ -151,6 +174,8 @@ PUT /projects/:id/labels
 | `new_name`      | string  | yes if `color` is not provided    | The new name of the label        |
 | `color`         | string  | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
 | `description`   | string  | no                                | The new description of the label |
+| `priority`    | integer | no       | The new priority of the label. Must be greater or equal than zero or `null` to remove the priority. |
+
 
 ```bash
 curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
@@ -160,9 +185,15 @@ Example response:
 
 ```json
 {
-   "color" : "#8E44AD",
-   "name" : "docs",
-   "description": "Documentation"
+  "id" : 8,
+  "name" : "docs",
+  "color" : "#8E44AD",
+  "description": "Documentation",
+  "open_issues_count": 1,
+  "closed_issues_count": 0,
+  "open_merge_requests_count": 2,
+  "subscribed": false,
+  "priority": null
 }
 ```
 
@@ -191,13 +222,15 @@ Example response:
 
 ```json
 {
-    "name": "Docs",
-    "color": "#cc0033",
-    "description": "",
-    "open_issues_count": 0,
-    "closed_issues_count": 0,
-    "open_merge_requests_count": 0,
-    "subscribed": true
+  "id" : 1,
+  "name" : "bug",
+  "color" : "#d9534f",
+  "description": "Bug reported by user",
+  "open_issues_count": 1,
+  "closed_issues_count": 0,
+  "open_merge_requests_count": 1,
+  "subscribed": true,
+  "priority": null
 }
 ```
 
@@ -226,12 +259,14 @@ Example response:
 
 ```json
 {
-    "name": "Docs",
-    "color": "#cc0033",
-    "description": "",
-    "open_issues_count": 0,
-    "closed_issues_count": 0,
-    "open_merge_requests_count": 0,
-    "subscribed": false
+  "id" : 1,
+  "name" : "bug",
+  "color" : "#d9534f",
+  "description": "Bug reported by user",
+  "open_issues_count": 1,
+  "closed_issues_count": 0,
+  "open_merge_requests_count": 1,
+  "subscribed": false,
+  "priority": null
 }
 ```
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index 1d7d9127a646767203ba8506ea7e8e6fdcb605de..ec8f2d6531c9f4d3f4f4f441d36917899d3146f4 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -228,7 +228,7 @@ For our currently-supported browsers, see our [requirements][requirements].
 [page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
 [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
 [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
-[observatory-cli]: https://github.com/mozilla/http-observatory-cli)
+[observatory-cli]: https://github.com/mozilla/http-observatory-cli
 [qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html
 [secure_headers]: https://github.com/twitter/secureheaders
 [mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 2574c2c04727c58a76bca7a86d1a04ef2ceff035..bbcd26477f34ff46cdeacd66c73c3ec1569d20f5 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -66,6 +66,12 @@ producing errors whenever it tries to use the `dummy` column.
 As a result of the above downtime _is_ required when removing a column, even
 when using PostgreSQL.
 
+## Renaming Columns
+
+Renaming columns requires downtime as running GitLab instances will continue
+using the old column name until a new version is deployed. This can result
+in the instance producing errors, which in turn can impact the user experience.
+
 ## Changing Column Constraints
 
 Generally changing column constraints requires checking all rows in the table to
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 7e947e4b2bad733095ca38abc576cd2430278885..b5e2640b3806cd616e88d0fd95a0720aacf8349c 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -403,7 +403,7 @@ If you are not using Linux you may have to run `gmake` instead of
     cd /home/git
     sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
     cd gitlab-workhorse
-    sudo -u git -H git checkout v0.8.5
+    sudo -u git -H git checkout v1.0.0
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
diff --git a/doc/university/README.md b/doc/university/README.md
index 510b753f70df397fbc9ebbc0ef190439e3a30e75..49714e4fb59de640de73d6b7f54f9c478fae89d8 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -200,7 +200,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
 
 ## 4. <a name="external"></a>  External Articles
 
-1. [2011 WSJ article by Mark Andreeson - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
+1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
 1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/)
 1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
 
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index 787511fd6cfbe8082c35cb97cb00e516300fc6b4..46ea19d11d0c422c92163c9647b3dc06a32ec9ef 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -84,7 +84,7 @@ GitLab 8.1.
 ```bash
 cd /home/git/gitlab-workhorse
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v0.8.5
+sudo -u git -H git checkout v1.0.0
 sudo -u git -H make
 ```
 
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index 447dd92a458b23a963d120a5cffcdb386f20fa0e..dc1339deb4c5c0636827a97ec10db8d74d1e1079 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -59,11 +59,6 @@ Feature: Profile
     When I unsuccessfully change my password
     Then I should see a password error message
 
-  Scenario: I reset my token
-    Given I visit profile account page
-    Then I reset my token
-    And I should see new token
-
   Scenario: I visit history tab
     Given I have activity
     When I visit Audit Log page
diff --git a/features/project/network_graph.feature b/features/project/network_graph.feature
index 89a02706bd283c04fe90fd46b7c2bd8d2d712314..93c884e23c58c451f6dba531f1fe59fa234cec5f 100644
--- a/features/project/network_graph.feature
+++ b/features/project/network_graph.feature
@@ -43,4 +43,4 @@ Feature: Project Network Graph
 
   Scenario: I should fail to look for a commit
     When I look for a commit by ";"
-    Then page status code should be 404
+    Then I should see non-existent git revision error message
diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature
deleted file mode 100644
index 48b1077dc6b8f5d28cb633d72ce4ca7b732e69c3..0000000000000000000000000000000000000000
--- a/features/project/source/git_blame.feature
+++ /dev/null
@@ -1,10 +0,0 @@
-Feature: Project Source Git Blame
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    Given I visit project source page
-
-  Scenario: I blame file
-    Given I click on ".gitignore" file in repo
-    And I click Blame button
-    Then I should see git file blame
diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature
deleted file mode 100644
index c2afb63b6d894917cb90be8fe5a18faa193420f3..0000000000000000000000000000000000000000
--- a/features/snippets/public_snippets.feature
+++ /dev/null
@@ -1,10 +0,0 @@
-Feature: Public snippets
-  Scenario: Unauthenticated user should see public snippets
-    Given There is public "Personal snippet one" snippet
-    And I visit snippet page "Personal snippet one"
-    Then I should see snippet "Personal snippet one"
-
-  Scenario: Unauthenticated user should see raw public snippets
-    Given There is public "Personal snippet one" snippet
-    And I visit snippet raw page "Personal snippet one"
-    Then I should see raw snippet "Personal snippet one"
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 0e81e99120bee607e5d0e97953ef6c26f150d3d1..0c88838767cdc33d8ac8f6f0795264fad3c566ea 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -117,7 +117,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   end
 
   step 'I visit group "NonExistentGroup" page' do
-    visit group_path(-1)
+    visit group_path("NonExistentGroup")
   end
 
   step 'the archived project have some issues' do
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 05ab2a7dc73421b1cb236d2bdb7e4abfd6c5f6a3..ea480d2ad68d7fe8299b53119278e0aa40b16e2a 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -104,18 +104,6 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
     end
   end
 
-  step 'I reset my token' do
-    page.within '.private-token' do
-      @old_token = @user.private_token
-      click_button "Reset private token"
-    end
-  end
-
-  step 'I should see new token' do
-    expect(find("#token").value).not_to eq @old_token
-    expect(find("#token").value).to eq @user.reload.private_token
-  end
-
   step 'I have activity' do
     create(:closed_issue_event, author: current_user)
   end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 019b3124a863d5b5d6de957750e01dd69c9dc5b3..ff9251615c930d84c146e83e15cf879c132bcf53 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -109,4 +109,8 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
       find('button').click
     end
   end
+
+  step 'I should see non-existent git revision error message' do
+    expect(page).to have_selector '.flash-alert', text: "Git revision ';' does not exist."
+  end
 end
diff --git a/features/steps/project/source/git_blame.rb b/features/steps/project/source/git_blame.rb
deleted file mode 100644
index d0a27f47e2a4739cae9ed33c5f157d2246e779bc..0000000000000000000000000000000000000000
--- a/features/steps/project/source/git_blame.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-  include SharedPaths
-
-  step 'I click on ".gitignore" file in repo' do
-    click_link ".gitignore"
-  end
-
-  step 'I click Blame button' do
-    click_link 'Blame'
-  end
-
-  step 'I should see git file blame' do
-    expect(page).to have_content "*.rb"
-    expect(page).to have_content "Dmitriy Zaporozhets"
-    expect(page).to have_content "Initial commit"
-  end
-end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 4df4e89f5b98a33c1bfd7caf347b6908f9b21b74..35b7159970823abaf2236f9120a3c6902f533a14 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -210,7 +210,7 @@ module SharedDiffNote
   end
 
   step 'I click side-by-side diff button' do
-    find('#parallel-diff-btn').click
+    find('#parallel-diff-btn').trigger('click')
   end
 
   step 'I see side-by-side diff button' do
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
deleted file mode 100644
index 2ebdca5ed3084e2d4a25e74cf90ff66bc5860f62..0000000000000000000000000000000000000000
--- a/features/steps/snippets/public_snippets.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedSnippet
-
-  step 'I should see snippet "Personal snippet one"' do
-    expect(page).to have_no_xpath("//i[@class='public-snippet']")
-  end
-
-  step 'I should see raw snippet "Personal snippet one"' do
-    expect(page).to have_text(snippet.content)
-  end
-
-  step 'I visit snippet page "Personal snippet one"' do
-    visit snippet_path(snippet)
-  end
-
-  step 'I visit snippet raw page "Personal snippet one"' do
-    visit raw_snippet_path(snippet)
-  end
-
-  def snippet
-    @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
-  end
-end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 01e31f6f7d143be08bcc6acc57751b1766455bf7..1942aeea6566277fbb093f2650b611d9d0d70c10 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -433,11 +433,14 @@ module API
     end
 
     class LabelBasic < Grape::Entity
-      expose :name, :color, :description
+      expose :id, :name, :color, :description
     end
 
     class Label < LabelBasic
       expose :open_issues_count, :closed_issues_count, :open_merge_requests_count
+      expose :priority do |label, options|
+        label.priority(options[:project])
+      end
 
       expose :subscribed do |label, options|
         label.subscribed?(options[:current_user])
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 238cea00fbab916999359461a2a3059c23d67ce5..97218054f3764cb2b01048e4cc985f3ced2b6a4c 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -11,7 +11,7 @@ module API
         success Entities::Label
       end
       get ':id/labels' do
-        present available_labels, with: Entities::Label, current_user: current_user
+        present available_labels, with: Entities::Label, current_user: current_user, project: user_project
       end
 
       desc 'Create a new label' do
@@ -21,6 +21,7 @@ module API
         requires :name, type: String, desc: 'The name of the label to be created'
         requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)"
         optional :description, type: String, desc: 'The description of label to be created'
+        optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
       end
       post ':id/labels' do
         authorize! :admin_label, user_project
@@ -28,10 +29,15 @@ module API
         label = available_labels.find_by(title: params[:name])
         conflict!('Label already exists') if label
 
-        label = user_project.labels.create(declared(params, include_parent_namespaces: false).to_h)
+        priority = params.delete(:priority)
+        label_params = declared(params,
+                                include_parent_namespaces: false,
+                                include_missing: false).to_h
+        label = user_project.labels.create(label_params)
 
         if label.valid?
-          present label, with: Entities::Label, current_user: current_user
+          label.prioritize!(user_project, priority) if priority
+          present label, with: Entities::Label, current_user: current_user, project: user_project
         else
           render_validation_error!(label)
         end
@@ -49,7 +55,7 @@ module API
         label = user_project.labels.find_by(title: params[:name])
         not_found!('Label') unless label
 
-        present label.destroy, with: Entities::Label, current_user: current_user
+        present label.destroy, with: Entities::Label, current_user: current_user, project: user_project
       end
 
       desc 'Update an existing label. At least one optional parameter is required.' do
@@ -60,7 +66,8 @@ module API
         optional :new_name, type: String, desc: 'The new name of the label'
         optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)"
         optional :description, type: String, desc: 'The new description of label'
-        at_least_one_of :new_name, :color, :description
+        optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
+        at_least_one_of :new_name, :color, :description, :priority
       end
       put ':id/labels' do
         authorize! :admin_label, user_project
@@ -68,17 +75,25 @@ module API
         label = user_project.labels.find_by(title: params[:name])
         not_found!('Label not found') unless label
 
-        update_params = declared(params,
-                                 include_parent_namespaces: false,
-                                 include_missing: false).to_h
+        update_priority = params.key?(:priority)
+        priority = params.delete(:priority)
+        label_params = declared(params,
+                                include_parent_namespaces: false,
+                                include_missing: false).to_h
         # Rename new name to the actual label attribute name
-        update_params['name'] = update_params.delete('new_name') if update_params.key?('new_name')
+        label_params[:name] = label_params.delete('new_name') if label_params.key?('new_name')
 
-        if label.update(update_params)
-          present label, with: Entities::Label, current_user: current_user
-        else
-          render_validation_error!(label)
+        render_validation_error!(label) unless label.update(label_params)
+
+        if update_priority
+          if priority.nil?
+            label.unprioritize!(user_project)
+          else
+            label.prioritize!(user_project, priority)
+          end
         end
+
+        present label, with: Entities::Label, current_user: current_user, project: user_project
       end
     end
   end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 32f731c565221eaf2740548345dbb5a5445eec7e..b6bfff9f20f2d2387ac96387a48dca8c167f539a 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -32,7 +32,7 @@ module API
         if hook.save
           present hook, with: Entities::Hook
         else
-          not_found!
+          render_validation_error!(hook)
         end
       end
 
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index ce048a36fa063c602b63ef826cc4f5681a90c935..f31fb6c3f71371783be9460dd71dca08ac887497 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -46,7 +46,7 @@ module Banzai
       return html if html.present?
 
       html = cacheless_render_field(object, field)
-      object.update_column(html_field, html) unless object.new_record? || object.destroyed?
+      update_object(object, html_field, html) unless object.new_record? || object.destroyed?
 
       html
     end
@@ -166,5 +166,9 @@ module Banzai
       return unless cache_key
       Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
     end
+
+    def update_object(object, html_field, html)
+      object.update_column(html_field, html)
+    end
   end
 end
diff --git a/lib/constraints/constrainer_helper.rb b/lib/constraints/constrainer_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab07a6793d90d5b3a6b8067a46b8b8d80a7972f1
--- /dev/null
+++ b/lib/constraints/constrainer_helper.rb
@@ -0,0 +1,15 @@
+module ConstrainerHelper
+  def extract_resource_path(path)
+    id = path.dup
+    id.sub!(/\A#{relative_url_root}/, '') if relative_url_root
+    id.sub(/\A\/+/, '').sub(/\/+\z/, '').sub(/.atom\z/, '')
+  end
+
+  private
+
+  def relative_url_root
+    if defined?(Gitlab::Application.config.relative_url_root)
+      Gitlab::Application.config.relative_url_root
+    end
+  end
+end
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
index ca39b1961ae0d015594ed2244e2a37e388d97d32..2af6e1a11c861c5f1a843f360ef0fddf96cdaa73 100644
--- a/lib/constraints/group_url_constrainer.rb
+++ b/lib/constraints/group_url_constrainer.rb
@@ -1,7 +1,15 @@
-require 'constraints/namespace_url_constrainer'
+require_relative 'constrainer_helper'
 
-class GroupUrlConstrainer < NamespaceUrlConstrainer
-  def find_resource(id)
-    Group.find_by_path(id)
+class GroupUrlConstrainer
+  include ConstrainerHelper
+
+  def matches?(request)
+    id = extract_resource_path(request.path)
+
+    if id =~ Gitlab::Regex.namespace_regex
+      Group.find_by(path: id).present?
+    else
+      false
+    end
   end
 end
diff --git a/lib/constraints/namespace_url_constrainer.rb b/lib/constraints/namespace_url_constrainer.rb
deleted file mode 100644
index 91b70143f1137fae7616389d153a6b851a2068f7..0000000000000000000000000000000000000000
--- a/lib/constraints/namespace_url_constrainer.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-class NamespaceUrlConstrainer
-  def matches?(request)
-    id = request.path
-    id = id.sub(/\A#{relative_url_root}/, '') if relative_url_root
-    id = id.sub(/\A\/+/, '').split('/').first
-    id = id.sub(/.atom\z/, '') if id
-
-    if id =~ Gitlab::Regex.namespace_regex
-      find_resource(id)
-    end
-  end
-
-  def find_resource(id)
-    Namespace.find_by_path(id)
-  end
-
-  private
-
-  def relative_url_root
-    if defined?(Gitlab::Application.config.relative_url_root)
-      Gitlab::Application.config.relative_url_root
-    end
-  end
-end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
index 504a0f5d93e0fe1aff6f53ea98c0a9f19ece987e..4d722ad5af248af5c4b62504d7fbd628a81ad052 100644
--- a/lib/constraints/user_url_constrainer.rb
+++ b/lib/constraints/user_url_constrainer.rb
@@ -1,7 +1,15 @@
-require 'constraints/namespace_url_constrainer'
+require_relative 'constrainer_helper'
 
-class UserUrlConstrainer < NamespaceUrlConstrainer
-  def find_resource(id)
-    User.find_by('lower(username) = ?', id.downcase)
+class UserUrlConstrainer
+  include ConstrainerHelper
+
+  def matches?(request)
+    id = extract_resource_path(request.path)
+
+    if id =~ Gitlab::Regex.namespace_regex
+      User.find_by('lower(username) = ?', id.downcase).present?
+    else
+      false
+    end
   end
 end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 9b74364849e5e2c6251065930a9f145140dce548..82551f1f2223767fea1b16ab48e4553fb8dab363 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -106,7 +106,7 @@ module ExtractsPath
   # resolved (e.g., when a user inserts an invalid path or ref).
   def assign_ref_vars
     # assign allowed options
-    allowed_options = ["filter_ref", "extended_sha1"]
+    allowed_options = ["filter_ref"]
     @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
     @options = HashWithIndifferentAccess.new(@options)
 
@@ -114,17 +114,13 @@ module ExtractsPath
     @ref, @path = extract_ref(@id)
     @repo = @project.repository
 
-    if @options[:extended_sha1].present?
-      @commit = @repo.commit(@options[:extended_sha1])
-    else
-      @commit = @repo.commit(@ref)
+    @commit = @repo.commit(@ref)
 
-      if @path.empty? && !@commit && @id.ends_with?('.atom')
-        @id = @ref = extract_ref_without_atom(@id)
-        @commit = @repo.commit(@ref)
+    if @path.empty? && !@commit && @id.ends_with?('.atom')
+      @id = @ref = extract_ref_without_atom(@id)
+      @commit = @repo.commit(@ref)
 
-        request.format = :atom if @commit
-      end
+      request.format = :atom if @commit
     end
 
     raise InvalidPathError unless @commit
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 9cec71a32220a2632ea7b0f47332ba9a92db2b51..82e194c1af12f32943e9fe67478c4583161d39dd 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -127,19 +127,6 @@ module Gitlab
                                    'rm-project', storage, "#{name}.git"])
     end
 
-    # Gc repository
-    #
-    # storage - project storage path
-    # path - project path with namespace
-    #
-    # Ex.
-    #   gc("/path/to/storage", "gitlab/gitlab-ci")
-    #
-    def gc(storage, path)
-      Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'gc',
-                                   storage, "#{path}.git"])
-    end
-
     # Add new key to gitlab-shell
     #
     # Ex.
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index 5cf9d5ebe28d67be964a8fc4cd9beac8e106dac6..bd3267e2a80ba54f31753d732b664b19e8838583 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -4,8 +4,7 @@ require 'gitlab/email/handler/create_issue_handler'
 module Gitlab
   module Email
     module Handler
-      # The `CreateIssueHandler` feature is disabled for the time being.
-      HANDLERS = [CreateNoteHandler]
+      HANDLERS = [CreateNoteHandler, CreateIssueHandler]
 
       def self.for(mail, mail_key)
         HANDLERS.find do |klass|
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 4e6566af8abed30fb7658266c8ec2897802fa23b..9f90a3ec2b2f70645ae27db6dbabf5557d31cfb8 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -5,16 +5,16 @@ module Gitlab
   module Email
     module Handler
       class CreateIssueHandler < BaseHandler
-        attr_reader :project_path, :authentication_token
+        attr_reader :project_path, :incoming_email_token
 
         def initialize(mail, mail_key)
           super(mail, mail_key)
-          @project_path, @authentication_token =
+          @project_path, @incoming_email_token =
             mail_key && mail_key.split('+', 2)
         end
 
         def can_handle?
-          !authentication_token.nil?
+          !incoming_email_token.nil?
         end
 
         def execute
@@ -29,7 +29,7 @@ module Gitlab
         end
 
         def author
-          @author ||= User.find_by(authentication_token: authentication_token)
+          @author ||= User.find_by(incoming_email_token: incoming_email_token)
         end
 
         def project
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 7e8f35e9298cf18c0e6e831ed9b97b6c89127abf..2dd427043962581265f199dd0dc389960827a847 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -1,66 +1,52 @@
+require 'securerandom'
+
 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:
+  # 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.
-  #
-  # This class has no 'cancel' method. I originally decided against adding
-  # it because it would add complexity and a false sense of security. The
-  # complexity: instead of setting '1' we would have to set a UUID, and to
-  # delete it we would have to execute Lua on the Redis server to only
-  # delete the key if the value was our own UUID. Otherwise there is a
-  # chance that when you intend to cancel your lease you actually delete
-  # someone else's. The false sense of security: you cannot design your
-  # system to rely too much on the lease being cancelled after use because
-  # the calling (Ruby) process may crash or be killed. You _cannot_ count
-  # on begin/ensure blocks to cancel a lease, because the 'ensure' does
-  # not always run. Think of 'kill -9' from the Unicorn master for
-  # instance.
-  #
-  # If you find that leases are getting in your way, ask yourself: would
-  # it be enough to lower the lease timeout? Another thing that might be
-  # appropriate is to only use a lease for bulk/automated operations, and
-  # to ignore the lease when you get a single 'manual' user request (a
-  # button click).
-  #
   class ExclusiveLease
+    LUA_CANCEL_SCRIPT = <<-EOS
+      local key, uuid = KEYS[1], ARGV[1]
+      if redis.call("get", key) == uuid then
+        redis.call("del", key)
+      end
+    EOS
+
+    def self.cancel(key, uuid)
+      Gitlab::Redis.with do |redis|
+        redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_key(key)], argv: [uuid])
+      end
+    end
+
+    def self.redis_key(key)
+      "gitlab:exclusive_lease:#{key}"
+    end
+
     def initialize(key, timeout:)
-      @key, @timeout = key, timeout
+      @redis_key = self.class.redis_key(key)
+      @timeout = timeout
+      @uuid = SecureRandom.uuid
     end
 
-    # Try to obtain the lease. Return true on success,
+    # Try to obtain the lease. Return lease UUID on success,
     # false if the lease is already taken.
     def try_obtain
       # Performing a single SET is atomic
       Gitlab::Redis.with do |redis|
-        !!redis.set(redis_key, '1', nx: true, ex: @timeout)
+        redis.set(@redis_key, @uuid, nx: true, ex: @timeout) && @uuid
       end
     end
 
     # Returns true if the key for this lease is set.
     def exists?
       Gitlab::Redis.with do |redis|
-        redis.exists(redis_key)
+        redis.exists(@redis_key)
       end
     end
-
-    # No #cancel method. See comments above!
-
-    private
-
-    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 ecc28799737b4c06f14371c8dc129225bdca5cc3..90cf38a8513e9bf83689689037700be99afc115e 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -52,13 +52,14 @@ module Gitlab
         fetch_resources(:labels, repo, per_page: 100) do |labels|
           labels.each do |raw|
             begin
-              label = LabelFormatter.new(project, raw).create!
-              @labels[label.title] = label.id
+              LabelFormatter.new(project, raw).create!
             rescue => e
               errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
             end
           end
         end
+
+        cache_labels!
       end
 
       def import_milestones
@@ -234,6 +235,12 @@ module Gitlab
         end
       end
 
+      def cache_labels!
+        project.labels.select(:id, :title).find_each do |label|
+          @labels[label.title] = label.id
+        end
+      end
+
       def fetch_resources(resource_type, *opts)
         return if imported?(resource_type)
 
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index d7be50bd43725d8cee76cf04299b0cc3d7b80308..801dfde9a368f90edefa2f7d20331c6ec670e2cf 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,5 +1,7 @@
 module Gitlab
   module IncomingEmail
+    WILDCARD_PLACEHOLDER = '%{key}'.freeze
+
     class << self
       FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
 
@@ -7,8 +9,16 @@ module Gitlab
         config.enabled && config.address
       end
 
+      def supports_wildcard?
+        config.address && config.address.include?(WILDCARD_PLACEHOLDER)
+      end
+
+      def supports_issue_creation?
+        enabled? && supports_wildcard?
+      end
+
       def reply_address(key)
-        config.address.gsub('%{key}', key)
+        config.address.gsub(WILDCARD_PLACEHOLDER, key)
       end
 
       def key_from_address(address)
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 24733435a5a860ca986ac24c60d5899a25c04f2d..b8326a64b222603087207c17302b7463e81148b6 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -5,11 +5,7 @@ module Gitlab
     def initialize(current_user, project, query, repository_ref = nil)
       @current_user = current_user
       @project = project
-      @repository_ref = if repository_ref.present?
-                          repository_ref
-                        else
-                          nil
-                        end
+      @repository_ref = repository_ref.presence
       @query = query
     end
 
@@ -47,33 +43,31 @@ module Gitlab
     private
 
     def blobs
-      if project.empty_repo? || query.blank?
-        []
-      else
-        project.repository.search_files(query, repository_ref)
-      end
+      @blobs ||= project.repository.search_files(query, repository_ref)
     end
 
     def wiki_blobs
-      if project.wiki_enabled? && query.present?
-        project_wiki = ProjectWiki.new(project)
+      @wiki_blobs ||= begin
+        if project.wiki_enabled? && query.present?
+          project_wiki = ProjectWiki.new(project)
 
-        unless project_wiki.empty?
-          project_wiki.search_files(query)
+          unless project_wiki.empty?
+            project_wiki.search_files(query)
+          else
+            []
+          end
         else
           []
         end
-      else
-        []
       end
     end
 
     def notes
-      project.notes.user.search(query, as_user: @current_user).order('updated_at DESC')
+      @notes ||= project.notes.user.search(query, as_user: @current_user).order('updated_at DESC')
     end
 
     def commits
-      project.repository.find_commits_by_message(query)
+      @commits ||= project.repository.find_commits_by_message(query)
     end
 
     def project_ids_relation
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 0d30e1bb92ed36fdd420ae88f40862619424e071..cb1659f9cee9cfc1b6a197aca46a8d0a44ab0d28 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -8,6 +8,10 @@ module Gitlab
       @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
     end
 
+    def namespace_route_regex
+      @namespace_route_regex ||= /#{NAMESPACE_REGEX_STR}/.freeze
+    end
+
     def namespace_regex_message
       "can contain only letters, digits, '_', '-' and '.'. " \
       "Cannot start with '-' or end in '.', '.git' or '.atom'." \
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 8eefa284ba06762032c4b4fb9afacb4088b7ef83..5ddcaa60dc6a39f11ee15c4af557c01fbc11f4e1 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -7,6 +7,26 @@ describe ProjectsController do
   let(:jpg)     { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
   let(:txt)     { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
 
+  describe 'GET index' do
+    context 'as a user' do
+      it 'redirects to root page' do
+        sign_in(user)
+
+        get :index
+
+        expect(response).to redirect_to(root_path)
+      end
+    end
+
+    context 'as a guest' do
+      it 'redirects to Explore page' do
+        get :index
+
+        expect(response).to redirect_to(explore_root_path)
+      end
+    end
+  end
+
   describe "GET show" do
     context "user not project member" do
       before { sign_in(user) }
@@ -264,6 +284,33 @@ describe ProjectsController do
     end
   end
 
+  describe 'PUT #new_issue_address' do
+    subject do
+      put :new_issue_address,
+        namespace_id: project.namespace.to_param,
+        id: project.to_param
+      user.reload
+    end
+
+    before do
+      sign_in(user)
+      project.team << [user, :developer]
+      allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+    end
+
+    it 'has http status 200' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'changes the user incoming email token' do
+      expect { subject }.to change { user.incoming_email_token }
+    end
+
+    it 'changes projects new issue address' do
+      expect { subject }.to change { project.new_issue_address(user) }
+    end
+  end
+
   describe "POST #toggle_star" do
     it "toggles star if user is signed in" do
       sign_in(user)
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index a92075fec8f2e43c546db2e02e737437147b3354..6cb8753e8fc6797aa71a271881408cdd8a4be221 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -380,6 +380,25 @@ describe 'Issue Boards', feature: true, js: true do
 
           wait_for_board_cards(1, 5)
         end
+
+        it 'creates new list from a new label' do
+          click_button 'Create new list'
+
+          wait_for_ajax
+
+          click_link 'Create new label'
+
+          fill_in('new_label_name', with: 'Testing New Label')
+
+          first('.suggest-colors a').click
+
+          click_button 'Create'
+
+          wait_for_ajax
+          wait_for_vue_resource
+
+          expect(page).to have_selector('.board', count: 5)
+        end
       end
     end
 
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f6409e00f22fb2b4180d47480faa754f6c2661ee
--- /dev/null
+++ b/spec/features/global_search_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'Global search', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+  end
+
+  describe 'I search through the issues and I see pagination' do
+    before do
+      allow_any_instance_of(Gitlab::SearchResults).to receive(:per_page).and_return(1)
+      create_list(:issue, 2, project: project, title: 'initial')
+    end
+
+    it "has a pagination" do
+      visit dashboard_projects_path
+
+      fill_in "search", with: "initial"
+      click_button "Go"
+
+      select_filter("Issues")
+      expect(page).to have_selector('.gl-pagination .page', count: 2)
+    end
+  end
+end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index fb0c47042857b1184fb362b7ed3ae01fd3f470c0..755f4eb1b0b9a532f49d851dea00cef28cdb0dc7 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -18,8 +18,8 @@ feature 'Start new branch from an issue', feature: true do
     end
 
     context "when there is a referenced merge request" do
-      let(:note) do
-        create(:note, :on_issue, :system, project: project,
+      let!(:note) do
+        create(:note, :on_issue, :system, project: project, noteable: issue,
                                           note: "Mentioned in !#{referenced_mr.iid}")
       end
       let(:referenced_mr) do
@@ -28,12 +28,13 @@ feature 'Start new branch from an issue', feature: true do
       end
 
       before do
-        issue.notes << note
+        referenced_mr.cache_merge_request_closes_issues!(user)
 
         visit namespace_project_issue_path(project.namespace, project, issue)
       end
 
       it "hides the new branch button", js: true do
+        expect(page).to have_css('#new-branch .unavailable')
         expect(page).not_to have_css('#new-branch .available')
         expect(page).to have_content /1 Related Merge Request/
       end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index b504329656fc6fd1b65a61b083c82e93315dc872..cdd02a8c8e36a012ebd7b775646dce009810b997 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
 describe 'Issues', feature: true do
   include IssueHelpers
   include SortingHelper
+  include WaitForAjax
 
   let(:project) { create(:project) }
 
@@ -368,6 +369,26 @@ describe 'Issues', feature: true do
     end
   end
 
+  describe 'when I want to reset my incoming email token' do
+    let(:project1) { create(:project, namespace: @user.namespace) }
+
+    before do
+      allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+      project1.team << [@user, :master]
+      visit namespace_project_issues_path(@user.namespace, project1)
+    end
+
+    it 'changes incoming email address token', js: true do
+      find('.issue-email-modal-btn').click
+      previous_token = find('input#issue_email').value
+
+      find('.incoming-email-token-reset').click
+      wait_for_ajax
+
+      expect(find('input#issue_email').value).not_to eq(previous_token)
+    end
+  end
+
   describe 'update labels from issue#show', js: true do
     let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
     let!(:label) { create(:label, project: project) }
@@ -553,7 +574,7 @@ describe 'Issues', feature: true do
     end
   end
 
-  xdescribe 'new issue by email' do
+  describe 'new issue by email' do
     shared_examples 'show the email in the modal' do
       before do
         stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index c3d8c349ca4c1bfeae0526a1a52383814a111932..7a562b5e03d079c4559c9df83136f8967a497320 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -32,4 +32,33 @@ describe 'Profile account page', feature: true do
       expect(current_path).to eq(profile_account_path)
     end
   end
+
+  describe 'when I reset private token' do
+    before do
+      visit profile_account_path
+    end
+
+    it 'resets private token' do
+      previous_token = find("#private-token").value
+
+      click_link('Reset private token')
+
+      expect(find('#private-token').value).not_to eq(previous_token)
+    end
+  end
+
+  describe 'when I reset incoming email token' do
+    before do
+      allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+      visit profile_account_path
+    end
+
+    it 'resets incoming email token' do
+      previous_token = find('#incoming-email-token').value
+
+      click_link('Reset incoming email token')
+
+      expect(find('#incoming-email-token').value).not_to eq(previous_token)
+    end
+  end
 end
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..69295e450d01611d6255d69c0eb38b571024f901
--- /dev/null
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'user checks git blame', feature: true do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+    visit namespace_project_tree_path(project.namespace, project, project.default_branch)
+  end
+
+  scenario "can see blame of '.gitignore'" do
+    click_link ".gitignore"
+    click_link 'Blame'
+    
+    expect(page).to have_content "*.rb"
+    expect(page).to have_content "Dmitriy Zaporozhets"
+    expect(page).to have_content "Initial commit"
+  end
+end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index b3ba40b35afc5adc7581fdb924a4434724e74a34..472491188c9aecba02ee75bc021688f8a69fdd34 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -22,8 +22,20 @@ feature 'Ref switcher', feature: true, js: true do
       input.native.send_keys :down
       input.native.send_keys :down
       input.native.send_keys :enter
+    end
+
+    expect(page).to have_title 'expand-collapse-files'
+  end
+
+  it "user selects ref with special characters" do
+    click_button 'master'
+    wait_for_ajax
 
-      expect(page).to have_content 'expand-collapse-files'
+    page.within '.project-refs-form' do
+      page.fill_in 'Search branches and tags', with: "'test'"
+      click_link "'test'"
     end
+
+    expect(page).to have_title "'test'"
   end
 end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 1806200c82cc5d7abe8acb11a4dc9db0a4053945..caecd027aaa4bda68c48387b3f3a5aa1e437e295 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -100,6 +100,32 @@ describe "Search", feature: true  do
 
       expect(page).to have_link(snippet.title)
     end
+
+    it 'finds a commit' do
+      visit namespace_project_path(project.namespace, project)
+
+      page.within '.search' do
+        fill_in 'search', with: 'add'
+        click_button 'Go'
+      end
+
+      click_link "Commits"
+
+      expect(page).to have_selector('.commit-row-description')
+    end
+
+    it 'finds a code' do
+      visit namespace_project_path(project.namespace, project)
+
+      page.within '.search' do
+        fill_in 'search', with: 'def'
+        click_button 'Go'
+      end
+
+      click_link "Code"
+
+      expect(page).to have_selector('.file-content .code')
+    end
   end
 
   describe 'Right header search field', feature: true do
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index db53a9cec9747c7191a21f6ba516a629f5ed7459..49deacc5c7437180c0fae8a73315b0c038010b01 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe "Internal Project Snippets Access", feature: true  do
   include AccessMatchers
 
-  let(:project) { create(:project, :internal) }
+  let(:project) { create(:empty_project, :internal) }
 
   let(:owner)     { project.owner }
   let(:master)    { create(:user) }
@@ -48,31 +48,63 @@ describe "Internal Project Snippets Access", feature: true  do
     it { is_expected.to be_denied_for :visitor }
   end
 
-  describe "GET /:project_path/snippets/:id for an internal snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+  describe "GET /:project_path/snippets/:id" do
+    context "for an internal snippet" do
+      subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
+
+    context "for a private snippet" do
+      subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_denied_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
   end
 
-  describe "GET /:project_path/snippets/:id for a private snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+  describe "GET /:project_path/snippets/:id/raw" do
+    context "for an internal snippet" do
+      subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
+
+    context "for a private snippet" do
+      subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_denied_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
   end
 end
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index d23d645c8e56f8a05b3ae0e9fd52f32072c39cee..a1bfc076d99527533163a61fd5d0811ff981a3a6 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe "Private Project Snippets Access", feature: true  do
   include AccessMatchers
 
-  let(:project) { create(:project, :private) }
+  let(:project) { create(:empty_project, :private) }
 
   let(:owner)     { project.owner }
   let(:master)    { create(:user) }
@@ -60,4 +60,18 @@ describe "Private Project Snippets Access", feature: true  do
     it { is_expected.to be_denied_for :external }
     it { is_expected.to be_denied_for :visitor }
   end
+
+  describe "GET /:project_path/snippets/:id/raw for a private snippet" do
+    subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+    it { is_expected.to be_allowed_for :admin }
+    it { is_expected.to be_allowed_for owner }
+    it { is_expected.to be_allowed_for master }
+    it { is_expected.to be_allowed_for developer }
+    it { is_expected.to be_allowed_for reporter }
+    it { is_expected.to be_allowed_for guest }
+    it { is_expected.to be_denied_for :user }
+    it { is_expected.to be_denied_for :external }
+    it { is_expected.to be_denied_for :visitor }
+  end
 end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index e3665b6116ab83e0953cc0c9f5d3f9a70b7421f6..30bcd87ef049622a8a23a073a2b796323fb20448 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe "Public Project Snippets Access", feature: true  do
   include AccessMatchers
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
 
   let(:owner)     { project.owner }
   let(:master)    { create(:user) }
@@ -49,45 +49,91 @@ describe "Public Project Snippets Access", feature: true  do
     it { is_expected.to be_denied_for :visitor }
   end
 
-  describe "GET /:project_path/snippets/:id for a public snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+  describe "GET /:project_path/snippets/:id" do
+    context "for a public snippet" do
+      subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
-  end
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_allowed_for :external }
+      it { is_expected.to be_allowed_for :visitor }
+    end
 
-  describe "GET /:project_path/snippets/:id for an internal snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+    context "for an internal snippet" do
+      subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
+
+    context "for a private snippet" do
+      subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_denied_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
   end
 
-  describe "GET /:project_path/snippets/:id for a private snippet" do
-    subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+  describe "GET /:project_path/snippets/:id/raw" do
+    context "for a public snippet" do
+      subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_allowed_for :external }
+      it { is_expected.to be_allowed_for :visitor }
+    end
+
+    context "for an internal snippet" do
+      subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_allowed_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
+
+    context "for a private snippet" do
+      subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+      it { is_expected.to be_allowed_for :admin }
+      it { is_expected.to be_allowed_for owner }
+      it { is_expected.to be_allowed_for master }
+      it { is_expected.to be_allowed_for developer }
+      it { is_expected.to be_allowed_for reporter }
+      it { is_expected.to be_allowed_for guest }
+      it { is_expected.to be_denied_for :user }
+      it { is_expected.to be_denied_for :external }
+      it { is_expected.to be_denied_for :visitor }
+    end
   end
 end
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..34300ccb9407ca1183794567dc2130aeb2896b45
--- /dev/null
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+feature 'Public Snippets', feature: true do
+  scenario 'Unauthenticated user should see public snippets' do
+    public_snippet = create(:personal_snippet, :public)
+
+    visit snippet_path(public_snippet)
+
+    expect(page).to have_content(public_snippet.content)
+  end
+
+  scenario 'Unauthenticated user should see raw public snippets' do
+    public_snippet = create(:personal_snippet, :public)
+
+    visit raw_snippet_path(public_snippet)
+
+    expect(page).to have_content(public_snippet.content)
+  end
+end
diff --git a/spec/fixtures/emails/wrong_authentication_token.eml b/spec/fixtures/emails/wrong_incoming_email_token.eml
similarity index 100%
rename from spec/fixtures/emails/wrong_authentication_token.eml
rename to spec/fixtures/emails/wrong_incoming_email_token.eml
diff --git a/spec/helpers/components_helper_spec.rb b/spec/helpers/components_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94a59193be846dee42d3647fc65cc0fd50443e22
--- /dev/null
+++ b/spec/helpers/components_helper_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe ComponentsHelper do
+  describe '#gitlab_workhorse_version' do
+    context 'without a Gitlab-Workhorse header' do
+      it 'shows the version from Gitlab::Workhorse.version' do
+        expect(helper.gitlab_workhorse_version).to eq Gitlab::Workhorse.version
+      end
+    end
+
+    context 'with a Gitlab-Workhorse header' do
+      before do
+        helper.request.headers['Gitlab-Workhorse'] = '42.42.0-rc3'
+      end
+
+      it 'shows the actual GitLab Workhorse version currently in use' do
+        expect(helper.gitlab_workhorse_version).to eq '42.42.0'
+      end
+    end
+  end
+end
diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6
index 6208c2386b04a395d7a29ee4a2a68bb15736207e..b84dfc8197beb764d80ee799c20570466f1b9cd0 100644
--- a/spec/javascripts/boards/boards_store_spec.js.es6
+++ b/spec/javascripts/boards/boards_store_spec.js.es6
@@ -13,8 +13,9 @@
 //= require boards/stores/boards_store
 //= require ./mock_data
 
-(() => {
+describe('Store', () => {
   beforeEach(() => {
+    Vue.http.interceptors.push(boardsMockInterceptor);
     gl.boardService = new BoardService('/test/issue-boards/board', '1');
     gl.issueBoards.BoardsStore.create();
 
@@ -24,145 +25,147 @@
     });
   });
 
-  describe('Store', () => {
-    it('starts with a blank state', () => {
-      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
-    });
+  afterEach(() => {
+    Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+  });
 
-    describe('lists', () => {
-      it('creates new list without persisting to DB', () => {
-        gl.issueBoards.BoardsStore.addList(listObj);
+  it('starts with a blank state', () => {
+    expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
+  });
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
-      });
+  describe('lists', () => {
+    it('creates new list without persisting to DB', () => {
+      gl.issueBoards.BoardsStore.addList(listObj);
 
-      it('finds list by ID', () => {
-        gl.issueBoards.BoardsStore.addList(listObj);
-        const list = gl.issueBoards.BoardsStore.findList('id', 1);
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+    });
 
-        expect(list.id).toBe(1);
-      });
+    it('finds list by ID', () => {
+      gl.issueBoards.BoardsStore.addList(listObj);
+      const list = gl.issueBoards.BoardsStore.findList('id', 1);
 
-      it('finds list by type', () => {
-        gl.issueBoards.BoardsStore.addList(listObj);
-        const list = gl.issueBoards.BoardsStore.findList('type', 'label');
+      expect(list.id).toBe(1);
+    });
 
-        expect(list).toBeDefined();
-      });
+    it('finds list by type', () => {
+      gl.issueBoards.BoardsStore.addList(listObj);
+      const list = gl.issueBoards.BoardsStore.findList('type', 'label');
 
-      it('finds list limited by type', () => {
-        gl.issueBoards.BoardsStore.addList({
-          id: 1,
-          position: 0,
-          title: 'Test',
-          list_type: 'backlog'
-        });
-        const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog');
+      expect(list).toBeDefined();
+    });
 
-        expect(list).toBeDefined();
+    it('finds list limited by type', () => {
+      gl.issueBoards.BoardsStore.addList({
+        id: 1,
+        position: 0,
+        title: 'Test',
+        list_type: 'backlog'
       });
+      const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog');
 
-      it('gets issue when new list added', (done) => {
-        gl.issueBoards.BoardsStore.addList(listObj);
-        const list = gl.issueBoards.BoardsStore.findList('id', 1);
+      expect(list).toBeDefined();
+    });
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+    it('gets issue when new list added', (done) => {
+      gl.issueBoards.BoardsStore.addList(listObj);
+      const list = gl.issueBoards.BoardsStore.findList('id', 1);
 
-        setTimeout(() => {
-          expect(list.issues.length).toBe(1);
-          expect(list.issues[0].id).toBe(1);
-          done();
-        }, 0);
-      });
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
 
-      it('persists new list', (done) => {
-        gl.issueBoards.BoardsStore.new({
-          title: 'Test',
-          type: 'label',
-          label: {
-            id: 1,
-            title: 'Testing',
-            color: 'red',
-            description: 'testing;'
-          }
-        });
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
-
-        setTimeout(() => {
-          const list = gl.issueBoards.BoardsStore.findList('id', 1);
-          expect(list).toBeDefined();
-          expect(list.id).toBe(1);
-          expect(list.position).toBe(0);
-          done();
-        }, 0);
-      });
+      setTimeout(() => {
+        expect(list.issues.length).toBe(1);
+        expect(list.issues[0].id).toBe(1);
+        done();
+      }, 0);
+    });
 
-      it('check for blank state adding', () => {
-        expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+    it('persists new list', (done) => {
+      gl.issueBoards.BoardsStore.new({
+        title: 'Test',
+        type: 'label',
+        label: {
+          id: 1,
+          title: 'Testing',
+          color: 'red',
+          description: 'testing;'
+        }
       });
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
 
-      it('check for blank state not adding', () => {
-        gl.issueBoards.BoardsStore.addList(listObj);
-        expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
-      });
+      setTimeout(() => {
+        const list = gl.issueBoards.BoardsStore.findList('id', 1);
+        expect(list).toBeDefined();
+        expect(list.id).toBe(1);
+        expect(list.position).toBe(0);
+        done();
+      }, 0);
+    });
 
-      it('check for blank state adding when backlog & done list exist', () => {
-        gl.issueBoards.BoardsStore.addList({
-          list_type: 'backlog'
-        });
-        gl.issueBoards.BoardsStore.addList({
-          list_type: 'done'
-        });
+    it('check for blank state adding', () => {
+      expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+    });
+
+    it('check for blank state not adding', () => {
+      gl.issueBoards.BoardsStore.addList(listObj);
+      expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
+    });
 
-        expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+    it('check for blank state adding when backlog & done list exist', () => {
+      gl.issueBoards.BoardsStore.addList({
+        list_type: 'backlog'
       });
+      gl.issueBoards.BoardsStore.addList({
+        list_type: 'done'
+      });
+
+      expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+    });
 
-      it('adds the blank state', () => {
-        gl.issueBoards.BoardsStore.addBlankState();
+    it('adds the blank state', () => {
+      gl.issueBoards.BoardsStore.addBlankState();
 
-        const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank');
-        expect(list).toBeDefined();
-      });
+      const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank');
+      expect(list).toBeDefined();
+    });
 
-      it('removes list from state', () => {
-        gl.issueBoards.BoardsStore.addList(listObj);
+    it('removes list from state', () => {
+      gl.issueBoards.BoardsStore.addList(listObj);
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
 
-        gl.issueBoards.BoardsStore.removeList(1, 'label');
+      gl.issueBoards.BoardsStore.removeList(1, 'label');
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
-      });
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
+    });
 
-      it('moves the position of lists', () => {
-        const listOne = gl.issueBoards.BoardsStore.addList(listObj),
-              listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+    it('moves the position of lists', () => {
+      const listOne = gl.issueBoards.BoardsStore.addList(listObj),
+            listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
 
-        gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']);
+      gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']);
 
-        expect(listOne.position).toBe(1);
-      });
+      expect(listOne.position).toBe(1);
+    });
 
-      it('moves an issue from one list to another', (done) => {
-        const listOne = gl.issueBoards.BoardsStore.addList(listObj),
-              listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+    it('moves an issue from one list to another', (done) => {
+      const listOne = gl.issueBoards.BoardsStore.addList(listObj),
+            listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
 
-        expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+      expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
 
-        setTimeout(() => {
-          expect(listOne.issues.length).toBe(1);
-          expect(listTwo.issues.length).toBe(1);
+      setTimeout(() => {
+        expect(listOne.issues.length).toBe(1);
+        expect(listTwo.issues.length).toBe(1);
 
-          gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1));
+        gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1));
 
-          expect(listOne.issues.length).toBe(0);
-          expect(listTwo.issues.length).toBe(1);
+        expect(listOne.issues.length).toBe(0);
+        expect(listTwo.issues.length).toBe(1);
 
-          done();
-        }, 0);
-      });
+        done();
+      }, 0);
     });
   });
-})();
+});
diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6
index 1a0427fdd90e1fa7729f62ccf1c0285f402f3859..dfbcbe3a7c1db031e9ce1fda89d700ac050167e1 100644
--- a/spec/javascripts/boards/list_spec.js.es6
+++ b/spec/javascripts/boards/list_spec.js.es6
@@ -17,12 +17,17 @@ describe('List model', () => {
   let list;
 
   beforeEach(() => {
+    Vue.http.interceptors.push(boardsMockInterceptor);
     gl.boardService = new BoardService('/test/issue-boards/board', '1');
     gl.issueBoards.BoardsStore.create();
 
     list = new List(listObj);
   });
 
+  afterEach(() => {
+    Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+  });
+
   it('gets issues when created', (done) => {
     setTimeout(() => {
       expect(list.issues.length).toBe(1);
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6
index 80d05e8a1a3f03b6fb11597374de875f74f93836..fcb3d8f17d8c6326ce8e216b0b2db4d478f36956 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js.es6
@@ -48,10 +48,10 @@ const BoardsMockData = {
   }
 };
 
-Vue.http.interceptors.push((request, next) => {
+const boardsMockInterceptor = (request, next) => {
   const body = BoardsMockData[request.method][request.url];
 
   next(request.respondWith(JSON.stringify(body), {
     status: 200
   }));
-});
+};
diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js.es6
index 5d817802602b31ba68456e30309809b4a209f7cf..9b2845af608835168b08f339011546690d17b6fd 100644
--- a/spec/javascripts/diff_comments_store_spec.js.es6
+++ b/spec/javascripts/diff_comments_store_spec.js.es6
@@ -92,7 +92,6 @@
     it('is unresolved with 2 notes', () => {
       const discussion = CommentsStore.state['a'];
       createDiscussion(2, false);
-      console.log(discussion.isResolved());
 
       expect(discussion.isResolved()).toBe(false);
     });
diff --git a/spec/lib/constraints/constrainer_helper_spec.rb b/spec/lib/constraints/constrainer_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27c8d72aefc0386b0e8c4f5c48d21e6a80bd248b
--- /dev/null
+++ b/spec/lib/constraints/constrainer_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe ConstrainerHelper, lib: true do
+  include ConstrainerHelper
+
+  describe '#extract_resource_path' do
+    it { expect(extract_resource_path('/gitlab/')).to eq('gitlab') }
+    it { expect(extract_resource_path('///gitlab//')).to eq('gitlab') }
+    it { expect(extract_resource_path('/gitlab.atom')).to eq('gitlab') }
+
+    context 'relative url' do
+      before do
+        allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' }
+      end
+
+      it { expect(extract_resource_path('/gitlab/foo')).to eq('foo') }
+      it { expect(extract_resource_path('/foo/bar')).to eq('foo/bar') }
+    end
+  end
+end
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index f0b75a664f27d25cbff2336552b77324077d0adc..42299b17c2bc471edb8146f4d53712fba2f1115b 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,10 +1,19 @@
 require 'spec_helper'
 
 describe GroupUrlConstrainer, lib: true do
-  let!(:username) { create(:group, path: 'gitlab-org') }
+  let!(:group) { create(:group, path: 'gitlab') }
 
-  describe '#find_resource' do
-    it { expect(!!subject.find_resource('gitlab-org')).to be_truthy }
-    it { expect(!!subject.find_resource('gitlab-com')).to be_falsey }
+  describe '#matches?' do
+    context 'root group' do
+      it { expect(subject.matches?(request '/gitlab')).to be_truthy }
+      it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy }
+      it { expect(subject.matches?(request '/gitlab/edit')).to be_falsey }
+      it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey }
+      it { expect(subject.matches?(request '/.gitlab')).to be_falsey }
+    end
+  end
+
+  def request(path)
+    double(:request, path: path)
   end
 end
diff --git a/spec/lib/constraints/namespace_url_constrainer_spec.rb b/spec/lib/constraints/namespace_url_constrainer_spec.rb
deleted file mode 100644
index 7814711fe278640e2e2bafa31ccb3b13d638c9f7..0000000000000000000000000000000000000000
--- a/spec/lib/constraints/namespace_url_constrainer_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe NamespaceUrlConstrainer, lib: true do
-  let!(:group) { create(:group, path: 'gitlab') }
-
-  describe '#matches?' do
-    context 'existing namespace' do
-      it { expect(subject.matches?(request '/gitlab')).to be_truthy }
-      it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy }
-      it { expect(subject.matches?(request '/gitlab/')).to be_truthy }
-      it { expect(subject.matches?(request '//gitlab/')).to be_truthy }
-    end
-
-    context 'non-existing namespace' do
-      it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey }
-      it { expect(subject.matches?(request '/gitlab.ce')).to be_falsey }
-      it { expect(subject.matches?(request '/g/gitlab')).to be_falsey }
-      it { expect(subject.matches?(request '/.gitlab')).to be_falsey }
-    end
-
-    context 'relative url' do
-      before do
-        allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' }
-      end
-
-      it { expect(subject.matches?(request '/gitlab/gitlab')).to be_truthy }
-      it { expect(subject.matches?(request '/gitlab/gitlab-ce')).to be_falsey }
-      it { expect(subject.matches?(request '/gitlab/')).to be_falsey }
-    end
-  end
-
-  def request(path)
-    OpenStruct.new(path: path)
-  end
-end
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index 4b26692672f0f63d43164aa0b8aa664448d2cb21..b3f8530c609793d20123cf113ce5ecb3a457ec6c 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -3,8 +3,14 @@ require 'spec_helper'
 describe UserUrlConstrainer, lib: true do
   let!(:username) { create(:user, username: 'dz') }
 
-  describe '#find_resource' do
-    it { expect(!!subject.find_resource('dz')).to be_truthy }
-    it { expect(!!subject.find_resource('john')).to be_falsey }
+  describe '#matches?' do
+    it { expect(subject.matches?(request '/dz')).to be_truthy }
+    it { expect(subject.matches?(request '/dz.atom')).to be_truthy }
+    it { expect(subject.matches?(request '/dz/projects')).to be_falsey }
+    it { expect(subject.matches?(request '/gitlab')).to be_falsey }
+  end
+
+  def request(path)
+    double(:request, path: path)
   end
 end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index f826d0d1b04c6f2e9fc427797c6f0e6457d58bd5..4b08a02ec730f948d0cb71f745c18a827857c26f 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -14,7 +14,6 @@ describe Gitlab::Shell, lib: true do
   it { is_expected.to respond_to :add_repository }
   it { is_expected.to respond_to :remove_repository }
   it { is_expected.to respond_to :fork_repository }
-  it { is_expected.to respond_to :gc }
   it { is_expected.to respond_to :add_namespace }
   it { is_expected.to respond_to :rm_namespace }
   it { is_expected.to respond_to :mv_namespace }
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index a5cc7b02936f339b2fc5d6bd9789b9d8989af8dc..cb3651e3845be2137f4be158b7ff56cdb822370f 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require_relative '../email_shared_blocks'
 
-xdescribe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
+describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
   include_context :email_shared_context
   it_behaves_like :email_shared_examples
 
@@ -18,7 +18,7 @@ xdescribe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
     create(
       :user,
       email: 'jake@adventuretime.ooo',
-      authentication_token: 'auth_token'
+      incoming_email_token: 'auth_token'
     )
   end
 
@@ -60,8 +60,8 @@ xdescribe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
       end
     end
 
-    context "when we can't find the authentication_token" do
-      let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") }
+    context "when we can't find the incoming_email_token" do
+      let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
 
       it "raises an UserNotFoundError" do
         expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 6b3bd08b9784588801f4a20a626720e0cb100cbc..a366d68a1460772624a89d04455f43ce6605b13b 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -5,32 +5,47 @@ describe Gitlab::ExclusiveLease, type: :redis do
 
   describe '#try_obtain' 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)
+      lease = described_class.new(unique_key, timeout: 3600)
+      expect(lease.try_obtain).to be_present
       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 = described_class.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)
+      expect(lease.try_obtain).to be_present
     end
   end
 
   describe '#exists?' do
     it 'returns true for an existing lease' do
-      lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+      lease = described_class.new(unique_key, timeout: 3600)
       lease.try_obtain
 
       expect(lease.exists?).to eq(true)
     end
 
     it 'returns false for a lease that does not exist' do
-      lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+      lease = described_class.new(unique_key, timeout: 3600)
 
       expect(lease.exists?).to eq(false)
     end
   end
+
+  describe '.cancel' do
+    it 'can cancel a lease' do
+      uuid = new_lease(unique_key)
+      expect(uuid).to be_present
+      expect(new_lease(unique_key)).to eq(false)
+
+      described_class.cancel(unique_key, uuid)
+      expect(new_lease(unique_key)).to be_present
+    end
+
+    def new_lease(key)
+      described_class.new(key, timeout: 3600).try_obtain
+    end
+  end
 end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 2b76e056f3c01a3b931f31f5a4e4f10052f099ac..b950fcdd81aae02426e796e1ac8193872ad84afa 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -98,6 +98,24 @@ describe ApplicationSetting, models: true do
         end
       end
     end
+
+    context 'housekeeping settings' do
+      it { is_expected.not_to allow_value(0).for(:housekeeping_incremental_repack_period) }
+
+      it 'wants the full repack period to be longer than the incremental repack period' do
+        subject.housekeeping_incremental_repack_period = 2
+        subject.housekeeping_full_repack_period = 1
+
+        expect(subject).not_to be_valid
+      end
+
+      it 'wants the gc period to be longer than the full repack period' do
+        subject.housekeeping_full_repack_period = 2
+        subject.housekeeping_gc_period = 1
+
+        expect(subject).not_to be_valid
+      end
+    end
   end
 
   context 'restricted signup domains' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index a59d30687f6ecc915a0ac875433419d935f81687..a9603074c3240f90561f92b14c678eb1c4777eb5 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -341,4 +341,25 @@ describe Issue, "Issuable" do
       expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
     end
   end
+
+  describe '#assignee_or_author?' do
+    let(:user) { build(:user, id: 1) }
+    let(:issue) { build(:issue) }
+
+    it 'returns true for a user that is assigned to an issue' do
+      issue.assignee = user
+
+      expect(issue.assignee_or_author?(user)).to eq(true)
+    end
+
+    it 'returns true for a user that is the author of an issue' do
+      issue.author = user
+
+      expect(issue.assignee_or_author?(user)).to eq(true)
+    end
+
+    it 'returns false for a user that is not the assignee or author' do
+      expect(issue.assignee_or_author?(user)).to eq(false)
+    end
+  end
 end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index ebba6e14578ca998b56c2c4b731e8f622a9044bf..2debe1289a3f748fb5c2ad70b4f6f99a9f0a0094 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ExternalIssue, models: true do
-  let(:project) { double('project', to_reference: 'namespace1/project1') }
+  let(:project) { double('project', id: 1, to_reference: 'namespace1/project1') }
   let(:issue)   { described_class.new('EXT-1234', project) }
 
   describe 'modules' do
@@ -36,4 +36,10 @@ describe ExternalIssue, models: true do
       end
     end
   end
+
+  describe '#project_id' do
+    it 'returns the ID of the project' do
+      expect(issue.project_id).to eq(project.id)
+    end
+  end
 end
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d742c8146802c2848ca9495662310fa714daf243
--- /dev/null
+++ b/spec/models/issue_collection_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe IssueCollection do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:issue1) { create(:issue, project: project) }
+  let(:issue2) { create(:issue, project: project) }
+  let(:collection) { described_class.new([issue1, issue2]) }
+
+  describe '#collection' do
+    it 'returns the issues in the same order as the input Array' do
+      expect(collection.collection).to eq([issue1, issue2])
+    end
+  end
+
+  describe '#updatable_by_user' do
+    context 'using an admin user' do
+      it 'returns all issues' do
+        user = create(:admin)
+
+        expect(collection.updatable_by_user(user)).to eq([issue1, issue2])
+      end
+    end
+
+    context 'using a user that has no access to the project' do
+      it 'returns no issues when the user is not an assignee or author' do
+        expect(collection.updatable_by_user(user)).to be_empty
+      end
+
+      it 'returns the issues the user is assigned to' do
+        issue1.assignee = user
+
+        expect(collection.updatable_by_user(user)).to eq([issue1])
+      end
+
+      it 'returns the issues for which the user is the author' do
+        issue1.author = user
+
+        expect(collection.updatable_by_user(user)).to eq([issue1])
+      end
+    end
+
+    context 'using a user that has reporter access to the project' do
+      it 'returns the issues of the project' do
+        project.team << [user, :reporter]
+
+        expect(collection.updatable_by_user(user)).to eq([issue1, issue2])
+      end
+    end
+
+    context 'using a user that is the owner of a project' do
+      it 'returns the issues of the project' do
+        expect(collection.updatable_by_user(project.namespace.owner)).
+          to eq([issue1, issue2])
+      end
+    end
+  end
+
+  describe '#visible_to' do
+    it 'is an alias for updatable_by_user' do
+      updatable_by_user = described_class.instance_method(:updatable_by_user)
+      visible_to = described_class.instance_method(:visible_to)
+
+      expect(visible_to).to eq(updatable_by_user)
+    end
+  end
+end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index ee0e38bd3736351e0ab43e57e9b29397c36cccfa..05ee4a08391caa365e16e9212aabda503a5ce493 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -33,6 +33,41 @@ describe JiraService, models: true do
     end
   end
 
+  describe '#can_test?' do
+    let(:jira_service) { described_class.new }
+
+    it 'returns false if username is blank' do
+      allow(jira_service).to receive_messages(
+        url: 'http://jira.example.com',
+        username: '',
+        password: '12345678'
+      )
+
+      expect(jira_service.can_test?).to be_falsy
+    end
+
+    it 'returns false if password is blank' do
+      allow(jira_service).to receive_messages(
+        url: 'http://jira.example.com',
+        username: 'tester',
+        password: ''
+      )
+
+      expect(jira_service.can_test?).to be_falsy
+    end
+
+    it 'returns true if password and username are present' do
+      jira_service = described_class.new
+      allow(jira_service).to receive_messages(
+        url: 'http://jira.example.com',
+        username: 'tester',
+        password: '12345678'
+      )
+
+      expect(jira_service.can_test?).to be_truthy
+    end
+  end
+
   describe "Execute" do
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
@@ -46,16 +81,19 @@ describe JiraService, models: true do
         service_hook: true,
         url: 'http://jira.example.com',
         username: 'gitlab_jira_username',
-        password: 'gitlab_jira_password'
+        password: 'gitlab_jira_password',
+        project_key: 'GitLabProject'
       )
 
       @jira_service.save
 
-      project_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123'
-      @transitions_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
-      @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
+      project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123'
+      @project_url       = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject'
+      @transitions_url   = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
+      @comment_url       = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
 
-      WebMock.stub_request(:get, project_url)
+      WebMock.stub_request(:get, @project_url)
+      WebMock.stub_request(:get, project_issues_url)
       WebMock.stub_request(:post, @transitions_url)
       WebMock.stub_request(:post, @comment_url)
     end
@@ -99,6 +137,14 @@ describe JiraService, models: true do
         body: /this-is-a-custom-id/
       ).once
     end
+
+    context "when testing" do
+      it "tries to get jira project" do
+        @jira_service.execute(nil)
+
+        expect(WebMock).to have_requested(:get, @project_url)
+      end
+    end
   end
 
   describe "Stored password invalidation" do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 0245897938c14f85fa0ba945528d23fe0047084e..0810d06b50ff4d2dacc3cb57de9ae0a37fb10daa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -295,7 +295,7 @@ describe Project, models: true do
     end
   end
 
-  xdescribe "#new_issue_address" do
+  describe "#new_issue_address" do
     let(:project) { create(:empty_project, path: "somewhere") }
     let(:user) { create(:user) }
 
@@ -305,8 +305,7 @@ describe Project, models: true do
       end
 
       it 'returns the address to create a new issue' do
-        token = user.authentication_token
-        address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab"
+        address = "p+#{project.path_with_namespace}+#{user.incoming_email_token}@gl.ab"
 
         expect(project.new_issue_address(user)).to eq(address)
       end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 04b7d19d41496dbc02056927105a6615251b09cb..12989d4db531be8352f6a333ba2fde18e907f273 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -362,6 +362,19 @@ describe Repository, models: true do
       expect(results.first).not_to start_with('fatal:')
     end
 
+    it 'properly handles when query is not present' do
+      results = repository.search_files('', 'master')
+
+      expect(results).to match_array([])
+    end
+
+    it 'properly handles query when repo is empty' do
+      repository = create(:empty_project).repository
+      results = repository.search_files('test', 'master')
+
+      expect(results).to match_array([])
+    end
+
     describe 'result' do
       subject { results.first }
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ba47479a2e1042aa48d671ec203eb72cbf87f71f..3b152e15b618ed7b5a8e4402095f7409a631cfda 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1205,4 +1205,40 @@ describe User, models: true do
       expect(user.viewable_starred_projects).not_to include(private_project)
     end
   end
+
+  describe '#projects_with_reporter_access_limited_to' do
+    let(:project1) { create(:project) }
+    let(:project2) { create(:project) }
+    let(:user) { create(:user) }
+
+    before do
+      project1.team << [user, :reporter]
+      project2.team << [user, :guest]
+    end
+
+    it 'returns the projects when using a single project ID' do
+      projects = user.projects_with_reporter_access_limited_to(project1.id)
+
+      expect(projects).to eq([project1])
+    end
+
+    it 'returns the projects when using an Array of project IDs' do
+      projects = user.projects_with_reporter_access_limited_to([project1.id])
+
+      expect(projects).to eq([project1])
+    end
+
+    it 'returns the projects when using an ActiveRecord relation' do
+      projects = user.
+        projects_with_reporter_access_limited_to(Project.select(:id))
+
+      expect(projects).to eq([project1])
+    end
+
+    it 'does not return projects you do not have reporter access to' do
+      projects = user.projects_with_reporter_access_limited_to(project2.id)
+
+      expect(projects).to be_empty
+    end
+  end
 end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7591bfd147145e3684a41f439000e95fe7c0aaeb
--- /dev/null
+++ b/spec/policies/issue_policy_spec.rb
@@ -0,0 +1,119 @@
+require 'spec_helper'
+
+describe IssuePolicy, models: true do
+  let(:user) { create(:user) }
+
+  describe '#rules' do
+    context 'using a regular issue' do
+      let(:project) { create(:project, :public) }
+      let(:issue) { create(:issue, project: project) }
+      let(:policies) { described_class.abilities(user, issue).to_set }
+
+      context 'with a regular user' do
+        it 'includes the read_issue permission' do
+          expect(policies).to include(:read_issue)
+        end
+
+        it 'does not include the admin_issue permission' do
+          expect(policies).not_to include(:admin_issue)
+        end
+
+        it 'does not include the update_issue permission' do
+          expect(policies).not_to include(:update_issue)
+        end
+      end
+
+      context 'with a user that is a project reporter' do
+        before do
+          project.team << [user, :reporter]
+        end
+
+        it 'includes the read_issue permission' do
+          expect(policies).to include(:read_issue)
+        end
+
+        it 'includes the admin_issue permission' do
+          expect(policies).to include(:admin_issue)
+        end
+
+        it 'includes the update_issue permission' do
+          expect(policies).to include(:update_issue)
+        end
+      end
+
+      context 'with a user that is a project guest' do
+        before do
+          project.team << [user, :guest]
+        end
+
+        it 'includes the read_issue permission' do
+          expect(policies).to include(:read_issue)
+        end
+
+        it 'does not include the admin_issue permission' do
+          expect(policies).not_to include(:admin_issue)
+        end
+
+        it 'does not include the update_issue permission' do
+          expect(policies).not_to include(:update_issue)
+        end
+      end
+    end
+
+    context 'using a confidential issue' do
+      let(:issue) { create(:issue, :confidential) }
+
+      context 'with a regular user' do
+        let(:policies) { described_class.abilities(user, issue).to_set }
+
+        it 'does not include the read_issue permission' do
+          expect(policies).not_to include(:read_issue)
+        end
+
+        it 'does not include the admin_issue permission' do
+          expect(policies).not_to include(:admin_issue)
+        end
+
+        it 'does not include the update_issue permission' do
+          expect(policies).not_to include(:update_issue)
+        end
+      end
+
+      context 'with a user that is a project member' do
+        let(:policies) { described_class.abilities(user, issue).to_set }
+
+        before do
+          issue.project.team << [user, :reporter]
+        end
+
+        it 'includes the read_issue permission' do
+          expect(policies).to include(:read_issue)
+        end
+
+        it 'includes the admin_issue permission' do
+          expect(policies).to include(:admin_issue)
+        end
+
+        it 'includes the update_issue permission' do
+          expect(policies).to include(:update_issue)
+        end
+      end
+
+      context 'without a user' do
+        let(:policies) { described_class.abilities(nil, issue).to_set }
+
+        it 'does not include the read_issue permission' do
+          expect(policies).not_to include(:read_issue)
+        end
+
+        it 'does not include the admin_issue permission' do
+          expect(policies).not_to include(:admin_issue)
+        end
+
+        it 'does not include the update_issue permission' do
+          expect(policies).not_to include(:update_issue)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index f702dfaaf53a9eb2cc794a059cd983ca185f55a3..2ff90b6deac5c9032dec288379fe78560f0f1657 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -6,6 +6,7 @@ describe API::API, api: true  do
   let(:user) { create(:user) }
   let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
   let!(:label1) { create(:label, title: 'label1', project: project) }
+  let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
 
   before do
     project.team << [user, :master]
@@ -16,13 +17,27 @@ describe API::API, api: true  do
       group = create(:group)
       group_label = create(:group_label, group: group)
       project.update(group: group)
+      expected_keys = [
+        'id', 'name', 'color', 'description',
+        'open_issues_count', 'closed_issues_count', 'open_merge_requests_count',
+        'subscribed', 'priority'
+      ]
 
       get api("/projects/#{project.id}/labels", user)
 
       expect(response).to have_http_status(200)
       expect(json_response).to be_an Array
-      expect(json_response.size).to eq(2)
-      expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, label1.name])
+      expect(json_response.size).to eq(3)
+      expect(json_response.first.keys).to match_array expected_keys
+      expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+      expect(json_response.last['name']).to eq(label1.name)
+      expect(json_response.last['color']).to be_present
+      expect(json_response.last['description']).to be_nil
+      expect(json_response.last['open_issues_count']).to eq(0)
+      expect(json_response.last['closed_issues_count']).to eq(0)
+      expect(json_response.last['open_merge_requests_count']).to eq(0)
+      expect(json_response.last['priority']).to be_nil
+      expect(json_response.last['subscribed']).to be_falsey
     end
   end
 
@@ -31,21 +46,39 @@ describe API::API, api: true  do
       post api("/projects/#{project.id}/labels", user),
            name: 'Foo',
            color: '#FFAABB',
-           description: 'test'
+           description: 'test',
+           priority: 2
+
       expect(response).to have_http_status(201)
       expect(json_response['name']).to eq('Foo')
       expect(json_response['color']).to eq('#FFAABB')
       expect(json_response['description']).to eq('test')
+      expect(json_response['priority']).to eq(2)
     end
 
     it 'returns created label when only required params' do
       post api("/projects/#{project.id}/labels", user),
            name: 'Foo & Bar',
            color: '#FFAABB'
+
       expect(response.status).to eq(201)
       expect(json_response['name']).to eq('Foo & Bar')
       expect(json_response['color']).to eq('#FFAABB')
       expect(json_response['description']).to be_nil
+      expect(json_response['priority']).to be_nil
+    end
+
+    it 'creates a prioritized label' do
+      post api("/projects/#{project.id}/labels", user),
+           name: 'Foo & Bar',
+           color: '#FFAABB',
+           priority: 3
+
+      expect(response.status).to eq(201)
+      expect(json_response['name']).to eq('Foo & Bar')
+      expect(json_response['color']).to eq('#FFAABB')
+      expect(json_response['description']).to be_nil
+      expect(json_response['priority']).to eq(3)
     end
 
     it 'returns a 400 bad request if name not given' do
@@ -95,6 +128,15 @@ describe API::API, api: true  do
       expect(json_response['message']).to eq('Label already exists')
     end
 
+    it 'returns 400 for invalid priority' do
+      post api("/projects/#{project.id}/labels", user),
+           name: 'Foo',
+           color: '#FFAAFFFF',
+           priority: 'foo'
+
+      expect(response).to have_http_status(400)
+    end
+
     it 'returns 409 if label already exists in project' do
       post api("/projects/#{project.id}/labels", user),
            name: 'label1',
@@ -155,11 +197,43 @@ describe API::API, api: true  do
 
     it 'returns 200 if description is changed' do
       put api("/projects/#{project.id}/labels", user),
-          name: 'label1',
+          name: 'bug',
           description: 'test'
+
       expect(response).to have_http_status(200)
-      expect(json_response['name']).to eq(label1.name)
+      expect(json_response['name']).to eq(priority_label.name)
       expect(json_response['description']).to eq('test')
+      expect(json_response['priority']).to eq(3)
+    end
+
+    it 'returns 200 if priority is changed' do
+      put api("/projects/#{project.id}/labels", user),
+           name: 'bug',
+           priority: 10
+
+      expect(response.status).to eq(200)
+      expect(json_response['name']).to eq(priority_label.name)
+      expect(json_response['priority']).to eq(10)
+    end
+
+    it 'returns 200 if a priority is added' do
+      put api("/projects/#{project.id}/labels", user),
+           name: 'label1',
+           priority: 3
+
+      expect(response.status).to eq(200)
+      expect(json_response['name']).to eq(label1.name)
+      expect(json_response['priority']).to eq(3)
+    end
+
+    it 'returns 200 if the priority is removed' do
+      put api("/projects/#{project.id}/labels", user),
+          name: priority_label.name,
+          priority: nil
+
+      expect(response.status).to eq(200)
+      expect(json_response['name']).to eq(priority_label.name)
+      expect(json_response['priority']).to be_nil
     end
 
     it 'returns 404 if label does not exist' do
@@ -178,7 +252,7 @@ describe API::API, api: true  do
     it 'returns 400 if no new parameters given' do
       put api("/projects/#{project.id}/labels", user), name: 'label1'
       expect(response).to have_http_status(400)
-      expect(json_response['error']).to eq('new_name, color, description are missing, '\
+      expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
                                            'at least one parameter must be provided')
     end
 
@@ -206,6 +280,14 @@ describe API::API, api: true  do
       expect(response).to have_http_status(400)
       expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
+
+    it 'returns 400 for invalid priority' do
+      post api("/projects/#{project.id}/labels", user),
+           name: 'Foo',
+           priority: 'foo'
+
+      expect(response).to have_http_status(400)
+    end
   end
 
   describe "POST /projects/:id/labels/:label_id/subscription" do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index f685a3685e6969f4779d23fa64478cd2a5656624..6c9df21f5983843feda4ad2d08fbbbba0fcf7d80 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -52,6 +52,12 @@ describe API::API, api: true  do
       expect(response).to have_http_status(400)
     end
 
+    it "responds with 400 if url is invalid" do
+      post api("/hooks", admin), url: 'hp://mep.mep'
+
+      expect(response).to have_http_status(400)
+    end
+
     it "does not create new hook without url" do
       expect do
         post api("/hooks", admin)
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index c18a2d55e438607d69acfab7cf83450a96a50527..61dca5d5a62c4c64fbccfdf44ba38fd856240d4b 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -266,13 +266,13 @@ describe "Groups", "routing" do
   end
 
   it "also display group#show on the short path" do
-    allow(Group).to receive(:find_by_path).and_return(true)
+    allow(Group).to receive(:find_by).and_return(true)
 
     expect(get('/1')).to route_to('groups#show', id: '1')
   end
 
   it "also display group#show with dot in the path" do
-    allow(Group).to receive(:find_by_path).and_return(true)
+    allow(Group).to receive(:find_by).and_return(true)
 
     expect(get('/group.with.dot')).to route_to('groups#show', id: 'group.with.dot')
   end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 45bc44ba1720003020f594ae92beea15ab834c4d..cea7e6429f953da7f9601edd1fb8f0ad69c78c3a 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -302,6 +302,9 @@ describe GitPushService, services: true do
         author_email: commit_author.email
       )
 
+      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+        and_return(commit)
+
       allow(project.repository).to receive(:commits_between).and_return([commit])
     end
 
@@ -357,6 +360,9 @@ describe GitPushService, services: true do
         committed_date: commit_time
       )
 
+      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+        and_return(commit)
+
       allow(project.repository).to receive(:commits_between).and_return([commit])
     end
 
@@ -393,6 +399,9 @@ describe GitPushService, services: true do
       allow(project.repository).to receive(:commits_between).
         and_return([closing_commit])
 
+      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+        and_return(closing_commit)
+
       project.team << [commit_author, :master]
     end
 
@@ -538,9 +547,16 @@ describe GitPushService, services: true do
     let(:housekeeping) { Projects::HousekeepingService.new(project) }
 
     before do
+      # Flush any raw Redis data stored by the housekeeping code.
+      Gitlab::Redis.with { |conn| conn.flushall }
+
       allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
     end
 
+    after do
+      Gitlab::Redis.with { |conn| conn.flushall }
+    end
+
     it 'does not perform housekeeping when not needed' do
       expect(housekeeping).not_to receive(:execute)
 
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 5dfb33f4b28d5499f9e0f61dfbd6fa700bc76aa9..4465f22a001ac48873168bff0daf5ce252475970 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -15,10 +15,39 @@ describe Issues::CloseService, services: true do
   end
 
   describe '#execute' do
+    let(:service) { described_class.new(project, user) }
+
+    it 'checks if the user is authorized to update the issue' do
+      expect(service).to receive(:can?).with(user, :update_issue, issue).
+        and_call_original
+
+      service.execute(issue)
+    end
+
+    it 'does not close the issue when the user is not authorized to do so' do
+      allow(service).to receive(:can?).with(user, :update_issue, issue).
+        and_return(false)
+
+      expect(service).not_to receive(:close_issue)
+      expect(service.execute(issue)).to eq(issue)
+    end
+
+    it 'closes the issue when the user is authorized to do so' do
+      allow(service).to receive(:can?).with(user, :update_issue, issue).
+        and_return(true)
+
+      expect(service).to receive(:close_issue).
+        with(issue, commit: nil, notifications: true, system_note: true)
+
+      service.execute(issue)
+    end
+  end
+
+  describe '#close_issue' do
     context "valid params" do
       before do
         perform_enqueued_jobs do
-          described_class.new(project, user).execute(issue)
+          described_class.new(project, user).close_issue(issue)
         end
       end
 
@@ -41,24 +70,12 @@ describe Issues::CloseService, services: true do
       end
     end
 
-    context 'current user is not authorized to close issue' do
-      before do
-        perform_enqueued_jobs do
-          described_class.new(project, guest).execute(issue)
-        end
-      end
-
-      it 'does not close the issue' do
-        expect(issue).to be_open
-      end
-    end
-
     context 'when issue is not confidential' do
       it 'executes issue hooks' do
         expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
         expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
 
-        described_class.new(project, user).execute(issue)
+        described_class.new(project, user).close_issue(issue)
       end
     end
 
@@ -69,14 +86,14 @@ describe Issues::CloseService, services: true do
         expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
         expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
 
-        described_class.new(project, user).execute(issue)
+        described_class.new(project, user).close_issue(issue)
       end
     end
 
     context 'external issue tracker' do
       before do
         allow(project).to receive(:default_issues_tracker?).and_return(false)
-        described_class.new(project, user).execute(issue)
+        described_class.new(project, user).close_issue(issue)
       end
 
       it { expect(issue).to be_valid }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index cf90b33dfb43b4d2c9c4545574839e26e824c036..57a5aa5cedc11d22082c932466f73bce98f74fcf 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -14,8 +14,10 @@ describe Projects::HousekeepingService do
 
   describe '#execute' do
     it 'enqueues a sidekiq job' do
-      expect(subject).to receive(:try_obtain_lease).and_return(true)
-      expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id)
+      expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
+      expect(subject).to receive(:lease_key).and_return(:the_lease_key)
+      expect(subject).to receive(:task).and_return(:the_task)
+      expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :the_task, :the_lease_key, :the_uuid)
 
       subject.execute
       expect(project.reload.pushes_since_gc).to eq(0)
@@ -58,4 +60,26 @@ describe Projects::HousekeepingService do
       end.to change { project.pushes_since_gc }.from(0).to(1)
     end
   end
+
+  it 'uses all three kinds of housekeeping we offer' do
+    allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
+    allow(subject).to receive(:lease_key).and_return(:the_lease_key)
+
+    # At push 200
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid).
+      exactly(1).times
+    # At push 50, 100, 150
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid).
+      exactly(3).times
+    # At push 10, 20, ... (except those above)
+    expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid).
+      exactly(16).times
+
+    201.times do
+      subject.increment!
+      subject.execute if subject.needed?
+    end
+
+    expect(project.pushes_since_gc).to eq(1)
+  end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b2ca856f89f57bc7d7195873a4a82b3bf21d3577..73cf4c9a24cb123e9487f6ce6c979a6261b2c0b0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -29,6 +29,7 @@ RSpec.configure do |config|
   config.include Devise::Test::ControllerHelpers,   type: :controller
   config.include Warden::Test::Helpers, type: :request
   config.include LoginHelpers,          type: :feature
+  config.include SearchHelpers,         type: :feature
   config.include StubConfiguration
   config.include EmailHelpers
   config.include TestEnv
diff --git a/spec/support/search_helpers.rb b/spec/support/search_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..abbbb636d66c6607b10028065f6f869e7ad83003
--- /dev/null
+++ b/spec/support/search_helpers.rb
@@ -0,0 +1,5 @@
+module SearchHelpers
+  def select_filter(name)
+    find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click
+  end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index c79975d8667ef71be6aafc13edd5b60521ebf36c..778e665500d43c879c40713ca8a6cdde15069eb4 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -204,20 +204,18 @@ module TestEnv
   end
 
   def set_repo_refs(repo_path, branch_sha)
+    instructions = branch_sha.map {|branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
+    update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
+    reset = proc do
+      IO.popen(update_refs, "w") {|io| io.write(instructions) }
+      $?.success?
+    end
+
     Dir.chdir(repo_path) do
-      branch_sha.each do |branch, sha|
-        # Try to reset without fetching to avoid using the network.
-        reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
-        unless system(*reset)
-          if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
-            unless system(*reset)
-              raise 'The fetched test seed '\
-              'does not contain the required revision.'
-            end
-          else
-            raise 'Could not fetch test seed repository.'
-          end
-        end
+      # Try to reset without fetching to avoid using the network.
+      unless reset.call
+        raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
+        raise 'The fetched test seed does not contain the required revision.' unless reset.call
       end
     end
   end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index c9f5aae0815b364f883060d8fcee62c67bd9a360..e471a68a49afeb0bfe17e1e7f2b032dfb967f6f7 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -1,3 +1,5 @@
+require 'fileutils'
+
 require 'spec_helper'
 
 describe GitGarbageCollectWorker do
@@ -6,16 +8,12 @@ describe GitGarbageCollectWorker do
 
   subject { GitGarbageCollectWorker.new }
 
-  before do
-    allow(subject).to receive(:gitlab_shell).and_return(shell)
-  end
-
   describe "#perform" do
-    it "runs `git gc`" do
-      expect(shell).to receive(:gc).with(
-        project.repository_storage_path,
-        project.path_with_namespace).
-      and_return(true)
+    it "flushes ref caches when the task is 'gc'" do
+      expect(subject).to receive(:command).with(:gc).and_return([:the, :command])
+      expect(Gitlab::Popen).to receive(:popen).
+        with([:the, :command], project.repository.path_to_repo).and_return(["", 0])
+
       expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
       expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
       expect_any_instance_of(Repository).to receive(:branch_count).and_call_original
@@ -23,5 +21,110 @@ describe GitGarbageCollectWorker do
 
       subject.perform(project.id)
     end
+
+    shared_examples 'gc tasks' do
+      before { allow(subject).to receive(:bitmaps_enabled?).and_return(bitmaps_enabled) }
+
+      it 'incremental repack adds a new packfile' do
+        create_objects(project)
+        before_packs = packs(project)
+
+        expect(before_packs.count).to be >= 1
+
+        subject.perform(project.id, 'incremental_repack')
+        after_packs = packs(project)
+
+        # Exactly one new pack should have been created
+        expect(after_packs.count).to eq(before_packs.count + 1)
+
+        # Previously existing packs are still around
+        expect(before_packs & after_packs).to eq(before_packs)
+      end
+
+      it 'full repack consolidates into 1 packfile' do
+        create_objects(project)
+        subject.perform(project.id, 'incremental_repack')
+        before_packs = packs(project)
+
+        expect(before_packs.count).to be >= 2
+
+        subject.perform(project.id, 'full_repack')
+        after_packs = packs(project)
+
+        expect(after_packs.count).to eq(1)
+
+        # Previously existing packs should be gone now
+        expect(after_packs - before_packs).to eq(after_packs)
+
+        expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
+      end
+
+      it 'gc consolidates into 1 packfile and updates packed-refs' do
+        create_objects(project)
+        before_packs = packs(project)
+        before_packed_refs = packed_refs(project)
+
+        expect(before_packs.count).to be >= 1
+
+        subject.perform(project.id, 'gc')
+        after_packed_refs = packed_refs(project)
+        after_packs = packs(project)
+
+        expect(after_packs.count).to eq(1)
+
+        # Previously existing packs should be gone now
+        expect(after_packs - before_packs).to eq(after_packs)
+
+        # The packed-refs file should have been updated during 'git gc'
+        expect(before_packed_refs).not_to eq(after_packed_refs)
+
+        expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
+      end
+    end
+
+    context 'with bitmaps enabled' do
+      let(:bitmaps_enabled) { true }
+
+      include_examples 'gc tasks'
+    end
+
+    context 'with bitmaps disabled' do
+      let(:bitmaps_enabled) { false }
+
+      include_examples 'gc tasks'
+    end
+  end
+
+  # Create a new commit on a random new branch
+  def create_objects(project)
+    rugged = project.repository.rugged
+    old_commit = rugged.branches.first.target
+    new_commit_sha = Rugged::Commit.create(
+      rugged,
+      message: "hello world #{SecureRandom.hex(6)}",
+      author: Gitlab::Git::committer_hash(email: 'foo@bar', name: 'baz'),
+      committer: Gitlab::Git::committer_hash(email: 'foo@bar', name: 'baz'),
+      tree: old_commit.tree,
+      parents: [old_commit],
+    )
+    project.repository.update_ref!(
+      "refs/heads/#{SecureRandom.hex(6)}",
+      new_commit_sha,
+      Gitlab::Git::BLANK_SHA
+    )
+  end
+
+  def packs(project)
+    Dir["#{project.repository.path_to_repo}/objects/pack/*.pack"]
+  end
+
+  def packed_refs(project)
+    path = "#{project.repository.path_to_repo}/packed-refs"
+    FileUtils.touch(path)
+    File.read(path)
+  end
+
+  def bitmap_path(pack)
+    pack.sub(/\.pack\z/, '.bitmap')
   end
 end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e4fee422409e3eea2cf079b409141db77189ca5
--- /dev/null
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+describe ProcessCommitWorker do
+  let(:worker) { described_class.new }
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :public) }
+  let(:issue) { create(:issue, project: project, author: user) }
+  let(:commit) { project.commit }
+
+  describe '#perform' do
+    it 'does not process the commit when the project does not exist' do
+      expect(worker).not_to receive(:close_issues)
+
+      worker.perform(-1, user.id, commit.id)
+    end
+
+    it 'does not process the commit when the user does not exist' do
+      expect(worker).not_to receive(:close_issues)
+
+      worker.perform(project.id, -1, commit.id)
+    end
+
+    it 'does not process the commit when the commit no longer exists' do
+      expect(worker).not_to receive(:close_issues)
+
+      worker.perform(project.id, user.id, 'this-should-does-not-exist')
+    end
+
+    it 'processes the commit message' do
+      expect(worker).to receive(:process_commit_message).and_call_original
+
+      worker.perform(project.id, user.id, commit.id)
+    end
+
+    it 'updates the issue metrics' do
+      expect(worker).to receive(:update_issue_metrics).and_call_original
+
+      worker.perform(project.id, user.id, commit.id)
+    end
+  end
+
+  describe '#process_commit_message' do
+    context 'when pushing to the default branch' do
+      it 'closes issues that should be closed per the commit message' do
+        allow(commit).to receive(:safe_message).
+          and_return("Closes #{issue.to_reference}")
+
+        expect(worker).to receive(:close_issues).
+          with(project, user, user, commit, [issue])
+
+        worker.process_commit_message(project, commit, user, user, true)
+      end
+    end
+
+    context 'when pushing to a non-default branch' do
+      it 'does not close any issues' do
+        allow(commit).to receive(:safe_message).
+          and_return("Closes #{issue.to_reference}")
+
+        expect(worker).not_to receive(:close_issues)
+
+        worker.process_commit_message(project, commit, user, user, false)
+      end
+    end
+
+    it 'creates cross references' do
+      expect(commit).to receive(:create_cross_references!)
+
+      worker.process_commit_message(project, commit, user, user)
+    end
+  end
+
+  describe '#close_issues' do
+    context 'when the user can update the issues' do
+      it 'closes the issues' do
+        worker.close_issues(project, user, user, commit, [issue])
+
+        issue.reload
+
+        expect(issue.closed?).to eq(true)
+      end
+    end
+
+    context 'when the user can not update the issues' do
+      it 'does not close the issues' do
+        other_user = create(:user)
+
+        worker.close_issues(project, other_user, other_user, commit, [issue])
+
+        issue.reload
+
+        expect(issue.closed?).to eq(false)
+      end
+    end
+  end
+
+  describe '#update_issue_metrics' do
+    it 'updates any existing issue metrics' do
+      allow(commit).to receive(:safe_message).
+        and_return("Closes #{issue.to_reference}")
+
+      worker.update_issue_metrics(commit, user)
+
+      metric = Issue::Metrics.first
+
+      expect(metric.first_mentioned_in_commit_at).to eq(commit.committed_date)
+    end
+  end
+end