diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 50f1f65e4e8e20c1c5a4e14cf38d253e887d91ed..ddc2c5f25423429711049119692f49f7db2a9f7e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,8 +19,8 @@ variables:
 
 before_script:
   - bundle --version
-  - . scripts/utils.sh
-  - ./scripts/prepare_build.sh
+  - source scripts/utils.sh
+  - source scripts/prepare_build.sh
 
 stages:
 - prepare
@@ -68,6 +68,13 @@ stages:
     - //@gitlab-org/gitlab-ee
     - //@gitlab/gitlab-ee
 
+# Skip all jobs except the ones that begin with 'docs/'.
+# Used for commits including ONLY documentation changes.
+# https://docs.gitlab.com/ce/development/writing_documentation.html#testing
+.except-docs: &except-docs
+  except:
+    - /^docs\/.*/
+
 .rspec-knapsack: &rspec-knapsack
   stage: test
   <<: *dedicated-runner
@@ -91,11 +98,13 @@ stages:
 .rspec-knapsack-pg: &rspec-knapsack-pg
   <<: *rspec-knapsack
   <<: *use-pg
+  <<: *except-docs
 
 .rspec-knapsack-mysql: &rspec-knapsack-mysql
   <<: *rspec-knapsack
   <<: *use-mysql
   <<: *only-master-and-ee-or-mysql
+  <<: *except-docs
 
 .spinach-knapsack: &spinach-knapsack
   stage: test
@@ -120,16 +129,19 @@ stages:
 .spinach-knapsack-pg: &spinach-knapsack-pg
   <<: *spinach-knapsack
   <<: *use-pg
+  <<: *except-docs
 
 .spinach-knapsack-mysql: &spinach-knapsack-mysql
   <<: *spinach-knapsack
   <<: *use-mysql
   <<: *only-master-and-ee-or-mysql
+  <<: *except-docs
 
 # Prepare and merge knapsack tests
 knapsack:
   <<: *knapsack-state
   <<: *dedicated-runner
+  <<: *except-docs
   stage: prepare
   script:
     - mkdir -p knapsack/${CI_PROJECT_NAME}/
@@ -156,6 +168,7 @@ update-knapsack:
 setup-test-env:
   <<: *use-pg
   <<: *dedicated-runner
+  <<: *except-docs
   stage: prepare
   script:
     - node --version
@@ -243,6 +256,7 @@ spinach mysql 9 10: *spinach-knapsack-mysql
 .exec: &exec
   <<: *ruby-static-analysis
   <<: *dedicated-runner
+  <<: *except-docs
   stage: test
   script:
     - bundle exec $CI_JOB_NAME
@@ -250,6 +264,7 @@ spinach mysql 9 10: *spinach-knapsack-mysql
 rubocop:
   <<: *ruby-static-analysis
   <<: *dedicated-runner
+  <<: *except-docs
   stage: test
   script:
     - bundle exec "rubocop --require rubocop-rspec"
@@ -266,6 +281,7 @@ rake downtime_check:
     - master
     - tags
     - /^[\d-]+-stable(-ee)?$/
+    - /^docs\/*/
 
 rake ee_compat_check:
   <<: *exec
@@ -296,10 +312,12 @@ rake ee_compat_check:
 rake pg db:migrate:reset:
   <<: *db-migrate-reset
   <<: *use-pg
+  <<: *except-docs
 
 rake mysql db:migrate:reset:
   <<: *db-migrate-reset
   <<: *use-mysql
+  <<: *except-docs
 
 .db-rollback: &db-rollback
   stage: test
@@ -311,10 +329,12 @@ rake mysql db:migrate:reset:
 rake pg db:rollback:
   <<: *db-rollback
   <<: *use-pg
+  <<: *except-docs
 
 rake mysql db:rollback:
   <<: *db-rollback
   <<: *use-mysql
+  <<: *except-docs
 
 .db-seed_fu: &db-seed_fu
   stage: test
@@ -336,14 +356,17 @@ rake mysql db:rollback:
 rake pg db:seed_fu:
   <<: *db-seed_fu
   <<: *use-pg
+  <<: *except-docs
 
 rake mysql db:seed_fu:
   <<: *db-seed_fu
   <<: *use-mysql
+  <<: *except-docs
 
 rake gitlab:assets:compile:
   stage: test
   <<: *dedicated-runner
+  <<: *except-docs
   dependencies: []
   variables:
     NODE_ENV: "production"
@@ -367,6 +390,7 @@ rake karma:
   stage: test
   <<: *use-pg
   <<: *dedicated-runner
+  <<: *except-docs
   variables:
     BABEL_ENV: "coverage"
   script:
@@ -447,6 +471,7 @@ coverage:
   stage: post-test
   services: []
   <<: *dedicated-runner
+  <<: *except-docs
   variables:
     SETUP_DB: "false"
     USE_BUNDLE_INSTALL: "true"
@@ -462,6 +487,7 @@ coverage:
 
 lint:javascript:
   <<: *dedicated-runner
+  <<: *except-docs
   stage: test
   before_script: []
   script:
@@ -469,6 +495,7 @@ lint:javascript:
 
 lint:javascript:report:
   <<: *dedicated-runner
+  <<: *except-docs
   stage: post-test
   before_script: []
   script:
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 241d90a204b964f14b0fafd88f30e9b81ecabfcd..66e1e0e20b340d2095caa1e67191d4513a2b3e79 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -1,3 +1,17 @@
+Please read this!
+
+Before opening a new issue, make sure to search for keywords in the issues
+filtered by the "regression" or "bug" label:
+
+- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
+- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
+
+and verify the issue you're about to submit isn't a duplicate.
+
+Please remove this notice if you're confident your issue isn't a duplicate.
+
+------
+
 ### Summary
 
 (Summarize the bug encountered concisely)
@@ -56,3 +70,5 @@ logs, and code as it's very hard to read otherwise.)
 ### Possible fixes
 
 (If you can, link to the line of code that might be responsible for the problem)
+
+/label ~bug
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
index 2636010e2fb3bca23947aa890891d10c40348f64..d96c9ad59e0b5e0c2d148be157f79c8790243dc7 100644
--- a/.gitlab/issue_templates/Feature Proposal.md	
+++ b/.gitlab/issue_templates/Feature Proposal.md	
@@ -1,3 +1,16 @@
+Please read this!
+
+Before opening a new issue, make sure to search for keywords in the issues
+filtered by the "feature proposal" label:
+
+- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
+
+and verify the issue you're about to submit isn't a duplicate.
+
+Please remove this notice if you're confident your issue isn't a duplicate.
+
+------
+
 ### Description
 
 (Include problem, use cases, benefits, and/or goals)
@@ -15,3 +28,5 @@
 3. How does someone use this
 
 During implementation, this can then be copied and used as a starter for the documentation.)
+
+/label ~"feature proposal"
diff --git a/.rubocop.yml b/.rubocop.yml
index e73500be2a92666d3951f907284c10ab84fc82cd..8c43f6909cf24aaba4f65e5593e02b03b4f067e5 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -983,10 +983,12 @@ RSpec/ExpectActual:
 
 # Checks the file and folder naming of the spec file.
 RSpec/FilePath:
-  Enabled: false
-  CustomTransform:
-    RuboCop: rubocop
-    RSpec: rspec
+  Enabled: true
+  IgnoreMethods: true
+  Exclude:
+    - 'qa/**/*'
+    - 'spec/javascripts/fixtures/*'
+    - 'spec/requests/api/v3/*'
 
 # Checks if there are focused specs.
 RSpec/Focus:
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 1d0ba9ea182b0f7354f3daf12120744ec5e0c2f8..267577d47e497a0630bc454b3f74c4fd9a10ced4 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.4.0
+0.4.1
diff --git a/Gemfile.lock b/Gemfile.lock
index 527076287487696ded1ed1bcc77eaf3e1c238763..038d1f746b35d8d3002d6e2883109390c6297d51 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -330,7 +330,7 @@ GEM
     grape-entity (0.6.0)
       activesupport
       multi_json (>= 1.3.2)
-    grpc (1.1.2)
+    grpc (1.2.5)
       google-protobuf (~> 3.1)
       googleauth (~> 0.5.1)
     haml (4.0.7)
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index b2102d2fbc501f80720e8acfb83f7e6968c19b12..9159927ed8b3a415c6bbe151f4a9951b225e48ff 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -227,8 +227,8 @@
   .award-control-icon-positive,
   .award-control-icon-super-positive {
     position: absolute;
-    left: 7px;
-    bottom: 9px;
+    left: 11px;
+    bottom: 7px;
     opacity: 0;
     @include transition(opacity, transform);
   }
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index a95ce4cc963e96ba50bb17f00638e817b5948bd0..1dd0e5ab581b4d6bf8dcf38b8088a3865df3fbe0 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -61,11 +61,13 @@
   .file-content {
     background: $white-light;
 
-    &.image_file {
+    &.image_file,
+    &.video {
       background: $file-image-bg;
       text-align: center;
 
-      img {
+      img,
+      video {
         padding: 20px;
         max-width: 80%;
       }
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index a8c0937569c3f0fb152e9b3cadc648fa162a2b6a..be2e6c7f193ea807a29f23a3acfd941987401b1f 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -38,6 +38,7 @@ module ServiceParams
     :new_issue_url,
     :notify,
     :notify_only_broken_pipelines,
+    :notify_only_default_branch,
     :password,
     :priority,
     :project_key,
diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb
index fb2a483773542737f88289cc8e232783e57c08bc..1ff08cce8cba675a6e8603aa836060f74c75a377 100644
--- a/app/controllers/projects/settings/integrations_controller.rb
+++ b/app/controllers/projects/settings/integrations_controller.rb
@@ -5,7 +5,7 @@ module Projects
 
       before_action :authorize_admin_project!
       layout "project_settings"
-      
+
       def show
         @hooks = @project.hooks
         @hook = ProjectHook.new
diff --git a/app/controllers/unicorn_test_controller.rb b/app/controllers/unicorn_test_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7a1a046be0b785d7ec7b9b4cb0b1897ed381acd
--- /dev/null
+++ b/app/controllers/unicorn_test_controller.rb
@@ -0,0 +1,12 @@
+if Rails.env.test?
+  class UnicornTestController < ActionController::Base
+    def pid
+      render plain: Process.pid.to_s
+    end
+  
+    def kill
+      Process.kill(params[:signal], Process.pid)
+      render plain: 'Bye!'
+    end
+  end
+end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 5f5c76d3722e8ea6a9299585278e97f0e9f6800c..960111ca045439498959f5d8d04cd090ee61b2c5 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -10,11 +10,12 @@ module EventsHelper
     'deleted' => 'icon_trash_o'
   }.freeze
 
-  def link_to_author(event)
+  def link_to_author(event, self_added: false)
     author = event.author
 
     if author
-      link_to author.name, user_path(author.username), title: author.name
+      name = self_added ? 'You' : author.name
+      link_to name, user_path(author.username), title: name
     else
       event.author_name
     end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 0781874d7fc7323e9c8cbfe3e1daa40e76c46d4b..b241a14740b34707d0e61acb3c54cc876ed17bd1 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -74,7 +74,7 @@ module MarkupHelper
 
     context[:project] ||= @project
     html = markdown_unsafe(text, context)
-    banzai_postprocess(html, context)
+    prepare_for_rendering(html, context)
   end
 
   def markdown_field(object, field)
@@ -82,13 +82,13 @@ module MarkupHelper
     return '' unless object.present?
 
     html = Banzai.render_field(object, field)
-    banzai_postprocess(html, object.banzai_render_context(field))
+    prepare_for_rendering(html, object.banzai_render_context(field))
   end
 
   def markup(file_name, text, context = {})
     context[:project] ||= @project
     html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
-    banzai_postprocess(html, context)
+    prepare_for_rendering(html, context)
   end
 
   def render_wiki_content(wiki_page)
@@ -107,14 +107,14 @@ module MarkupHelper
         wiki_page.formatted_content.html_safe
       end
 
-    banzai_postprocess(html, context)
+    prepare_for_rendering(html, context)
   end
 
   def markup_unsafe(file_name, text, context = {})
     return '' unless text.present?
 
     if gitlab_markdown?(file_name)
-      Hamlit::RailsHelpers.preserve(markdown_unsafe(text, context))
+      markdown_unsafe(text, context)
     elsif asciidoc?(file_name)
       asciidoc_unsafe(text)
     elsif plain?(file_name)
@@ -225,8 +225,7 @@ module MarkupHelper
     Gitlab::OtherMarkup.render(file_name, text)
   end
 
-  # Calls Banzai.post_process with some common context options
-  def banzai_postprocess(html, context = {})
+  def prepare_for_rendering(html, context = {})
     return '' unless html.present?
 
     context.merge!(
@@ -239,7 +238,9 @@ module MarkupHelper
       requested_path: @path
     )
 
-    Banzai.post_process(html, context)
+    html = Banzai.post_process(html, context)
+
+    Hamlit::RailsHelpers.preserve(html)
   end
 
   extend self
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 4f5adf623f2f6ae11a40e602ce423cb4bde3736b..f19e2f9db9c7c0b934e2bc22cde71b69c511caf3 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -13,13 +13,13 @@ module TodosHelper
 
   def todo_action_name(todo)
     case todo.action
-    when Todo::ASSIGNED then 'assigned you'
-    when Todo::MENTIONED then 'mentioned you on'
+    when Todo::ASSIGNED then todo.self_added? ? 'assigned' : 'assigned you'
+    when Todo::MENTIONED then "mentioned #{todo_action_subject(todo)} on"
     when Todo::BUILD_FAILED then 'The build failed for'
     when Todo::MARKED then 'added a todo for'
-    when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
+    when Todo::APPROVAL_REQUIRED then "set #{todo_action_subject(todo)} as an approver for"
     when Todo::UNMERGEABLE then 'Could not merge'
-    when Todo::DIRECTLY_ADDRESSED then 'directly addressed you on'
+    when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on"
     end
   end
 
@@ -148,6 +148,10 @@ module TodosHelper
 
   private
 
+  def todo_action_subject(todo)
+    todo.self_added? ? 'yourself' : 'you'
+  end
+
   def show_todo_state?(todo)
     (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
   end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 1694f4e6dc9654b3b914e997b6278f853210e76e..934dd2b9a4f7cc83917b9864cd6a946be3e7540c 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -28,6 +28,8 @@ class Blob < SimpleDelegator
     BlobViewer::Sketch,
     BlobViewer::Balsamiq,
 
+    BlobViewer::Video,
+    
     BlobViewer::PDF,
 
     BlobViewer::BinarySTL,
diff --git a/app/models/blob_viewer/video.rb b/app/models/blob_viewer/video.rb
new file mode 100644
index 0000000000000000000000000000000000000000..057f9fe516f02a2e4da96ae4dca57fa9782efd3b
--- /dev/null
+++ b/app/models/blob_viewer/video.rb
@@ -0,0 +1,12 @@
+module BlobViewer
+  class Video < Base
+    include Rich
+    include ClientSide
+
+    self.partial_name = 'video'
+    self.extensions = UploaderHelper::VIDEO_EXT
+    self.binary = true
+    self.switcher_icon = 'film'
+    self.switcher_title = 'video'
+  end
+end
diff --git a/app/models/label.rb b/app/models/label.rb
index d8b0e250732731ac00c74cb44b112dc648cf43fd..ddddb6bdf8fa60471c77b295b8bd8755cdfc8dd0 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -34,6 +34,7 @@ class Label < ActiveRecord::Base
 
   scope :templates, -> { where(template: true) }
   scope :with_title, ->(title) { where(title: title) }
+  scope :on_project_boards, ->(project_id) { joins(lists: :board).merge(List.movable).where(boards: { project_id: project_id }) }
 
   def self.prioritized(project)
     joins(:priorities)
diff --git a/app/models/member.rb b/app/models/member.rb
index 97fba501759e95ff94b308974f97a04fdb7fcac6..7228e82e9781384bd6d232b5087d767dd774195a 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -154,6 +154,11 @@ class Member < ActiveRecord::Base
     def add_users(source, users, access_level, current_user: nil, expires_at: nil)
       return [] unless users.present?
 
+      # Collect all user ids into separate array
+      # so we can use single sql query to get user objects
+      user_ids = users.select { |user| user =~ /\A\d+\Z/ }
+      users = users - user_ids + User.where(id: user_ids)
+
       self.transaction do
         users.map do |user|
           add_user(
diff --git a/app/models/todo.rb b/app/models/todo.rb
index da3fa7277c2c54488dddec023703c24717f942cf..b011001b235e84177b3ee0373634ba02d203d377 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -84,6 +84,10 @@ class Todo < ActiveRecord::Base
     action == BUILD_FAILED
   end
 
+  def assigned?
+    action == ASSIGNED
+  end
+
   def action_name
     ACTION_NAMES[action]
   end
@@ -117,6 +121,14 @@ class Todo < ActiveRecord::Base
     end
   end
 
+  def self_added?
+    author == user
+  end
+
+  def self_assigned?
+    assigned? && self_added?
+  end
+
   private
 
   def keep_around_commit
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index d5735f13c1ea5b881156538e8de847cb745b63e0..e73b1a4361ac68c3fa24d854c875475ac4f59839 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -61,7 +61,7 @@ module Boards
           if moving_to_list.movable?
             moving_from_list.label_id
           else
-            project.boards.joins(:lists).merge(List.movable).pluck(:label_id)
+            Label.on_project_boards(project.id).pluck(:label_id)
           end
 
         Array(label_ids).compact
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 49d45ec9dbd3920bbd7baad50ab8fa1498174304..6aeebc26685aead3731abb1c8c580893c24e3bf7 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -330,6 +330,28 @@ module SlashCommands
       @updates[:target_branch] = branch_name if project.repository.branch_names.include?(branch_name)
     end
 
+    desc 'Move issue from one column of the board to another'
+    params '~"Target column"'
+    condition do
+      issuable.is_a?(Issue) &&
+        current_user.can?(:"update_#{issuable.to_ability_name}", issuable) &&
+        issuable.project.boards.count == 1
+    end
+    command :board_move do |target_list_name|
+      label_ids = find_label_ids(target_list_name)
+
+      if label_ids.size == 1
+        label_id = label_ids.first
+
+        # Ensure this label corresponds to a list on the board
+        next unless Label.on_project_boards(issuable.project_id).where(id: label_id).exists?
+
+        @updates[:remove_label_ids] =
+          issuable.labels.on_project_boards(issuable.project_id).where.not(id: label_id).pluck(:id)
+        @updates[:add_label_ids] = [label_id]
+      end
+    end
+
     def find_label_ids(labels_param)
       label_ids_by_reference = extract_references(labels_param, :label).map(&:id)
       labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id)
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index d0c12aa57aefbe593e332aaa639bf8b28b721309..38fd053ae657ac62715c00039b29d1a8df377296 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -9,7 +9,7 @@
 
         .title-item.author-name
           - if todo.author
-            = link_to_author(todo)
+            = link_to_author(todo, self_added: todo.self_added?)
           - else
             (removed)
 
@@ -22,6 +22,10 @@
         - else
           (removed)
 
+      - if todo.self_assigned?
+        .title-item.action-name
+          to yourself
+
       .title-item
         &middot;
 
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index f93b6b63426eca6a2335e03f9c870e162fc257b9..b20e3a22133372674102095e2fb240a295d547f3 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -27,8 +27,7 @@
 .row
   .col-md-8
     .documentation-index
-      = preserve do
-        = markdown(@help_index)
+      = markdown(@help_index)
   .col-md-4
     .panel.panel-default
       .panel-heading
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 41d42740f61a5f9fa5463187b871dfacc630d448..2bab22e125d96de5e4e6a71b18c69ff18966403d 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -2,8 +2,7 @@
   %div{ class: container_class }
     .wiki-holder.prepend-top-default.append-bottom-default
       .wiki
-        = preserve do
-          = render_wiki_content(@wiki_home)
+        = render_wiki_content(@wiki_home)
 - else
   - can_create_wiki = can?(current_user, :create_wiki, @project)
   .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
diff --git a/app/views/projects/blob/viewers/_video.html.haml b/app/views/projects/blob/viewers/_video.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..595a890a27dc40192d0a65a7f35a3e3d7a21ca68
--- /dev/null
+++ b/app/views/projects/blob/viewers/_video.html.haml
@@ -0,0 +1,2 @@
+.file-content.video
+  %video{ src: blob_raw_url, controls: true, data: { setup: '{}' } }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 4d56aa214e2f35f1922f7aec44e550a980402e78..2a871966aa832d4dd79bfc2166a9bec7ed09ccad 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -58,8 +58,7 @@
     - if @issue.description.present?
       .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
         .wiki
-          = preserve do
-            = markdown_field(@issue, :description)
+          = markdown_field(@issue, :description)
         %textarea.hidden.js-task-list-field
           = @issue.description
     = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 683cb8a5a27ae9db5896cee90be6073a7470d9a6..8a390cf870044973f247fd280a35d0ef4333c04c 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -6,8 +6,7 @@
     - if @merge_request.description.present?
       .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' }
         .wiki
-          = preserve do
-            = markdown_field(@merge_request, :description)
+          = markdown_field(@merge_request, :description)
         %textarea.hidden.js-task-list-field
           = @merge_request.description
 
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 33bbbd9a3f88ce4c0f6cd8bf8ac23c3a1371598a..4b692aba11cf9eec00508a80e8d38a338b040efd 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -43,8 +43,7 @@
       - if @milestone.description.present?
         .description
           .wiki
-            = preserve do
-              = markdown_field(@milestone, :description)
+            = markdown_field(@milestone, :description)
 
   - if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero?
     .alert.alert-success.prepend-top-default
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 7cf604bb77255019f6c9982db53e2b3b9e8fcfb7..7afccb3900ac91df0b35d98b859decf2711e5e7e 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -75,8 +75,7 @@
                   = icon('trash-o', class: 'danger-highlight')
       .note-body{ class: note_editable ? 'js-task-list-container' : '' }
         .note-text.md
-          = preserve do
-            = note.redacted_note_html
+          = note.redacted_note_html
         = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
         - if note_editable
           .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 451e011a4b8216d4ea253e4c8bc8aec2bd07957f..4c4f3655b97f9bf3b26dcb5d7faef5824f6b7d30 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -24,8 +24,7 @@
     - if release && release.description.present?
       .description.prepend-top-default
         .wiki
-          = preserve do
-            = markdown_field(release, :description)
+          = markdown_field(release, :description)
 
   .row-fixed-content.controls
     = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 1c4135c8a5400e4b2ca2e90cce19e7080a585ca8..e996ae3e4fc3198897d2ec904afceb4e144d96e3 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -38,7 +38,6 @@
     - if @release.description.present?
       .description
         .wiki
-          = preserve do
-            = markdown_field(@release, :description)
+          = markdown_field(@release, :description)
     - else
       This tag has no release notes.
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 3609461b721364f56939855741a82565da994bee..c00967546aa6f8a0475152632537285c42005048 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -27,7 +27,6 @@
 
   .wiki-holder.prepend-top-default.append-bottom-default
     .wiki
-      = preserve do
-        = render_wiki_content(@page)
+      = render_wiki_content(@page)
 
 = render 'sidebar'
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index fc4385865a46ef7565bbde185413a3350fd2be04..b4bc8982c05cceb4cbb38cd4ead8f755211ececc 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -8,7 +8,6 @@
     .pull-right ##{issue.iid}
   - if issue.description.present?
     .description.term
-      = preserve do
-        = search_md_sanitize(issue, :description)
+      = search_md_sanitize(issue, :description)
   %span.light
     #{issue.project.name_with_namespace}
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 9b583285d02d701b100330a84be46349102bdba0..1a5499e4d5864e6c068f78bf75dfd95feffa02ab 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -9,7 +9,6 @@
     .pull-right= merge_request.to_reference
   - if merge_request.description.present?
     .description.term
-      = preserve do
-        = search_md_sanitize(merge_request, :description)
+      = search_md_sanitize(merge_request, :description)
   %span.light
     #{merge_request.project.name_with_namespace}
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index 9664f65a36e016140be5de9f152991af07d3fa4f..2daa96e34d1d8f45bdc534dae9a98af7dc57d81e 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -5,5 +5,4 @@
 
   - if milestone.description.present?
     .description.term
-      = preserve do
-        = search_md_sanitize(milestone, :description)
+      = search_md_sanitize(milestone, :description)
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index f3701b89bb4709f76f3922800ddc56ae52aa97c9..a7e178dfa713157d84698a9dcfc982695e553847 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -22,5 +22,4 @@
 
   .note-search-result
     .term
-      = preserve do
-        = search_md_sanitize(note, :note)
+      = search_md_sanitize(note, :note)
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 9c5053dace5df1da26b7447adffab1dc17e7eb28..b200e5fc5287a423ff8c044aaee80b42ab4be7d2 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -4,8 +4,7 @@
   = render "projects/services/#{@service.to_param}/help", subject: subject
 - elsif @service.help.present?
   .well
-    = preserve do
-      = markdown @service.help
+    = markdown @service.help
 
 .service-settings
   .form-group
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 774d20fb5badacd341999bc043b67f1c9bca4baf..5e8a2a0f5d8107464956ef90c10f30deb2d3279e 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -64,7 +64,7 @@
             %span.remaining-days= remaining_days
 
     - if !project || can?(current_user, :read_issue, project)
-      .block
+      .block.issues
         .sidebar-collapsed-icon
           %strong
             = icon('hashtag', 'aria-hidden': 'true')
@@ -85,7 +85,7 @@
               Closed:
               = milestone.issues_visible_to_user(current_user).closed.count
 
-    .block
+    .block.merge-requests
       .sidebar-collapsed-icon
         %strong
           = icon('exclamation', 'aria-hidden': 'true')
diff --git a/changelogs/unreleased/28020-improve-todo-list-when-comes-from-yourself.yml b/changelogs/unreleased/28020-improve-todo-list-when-comes-from-yourself.yml
new file mode 100644
index 0000000000000000000000000000000000000000..14aecc35bd2ce679a8d3fc7c462f8c69919a587d
--- /dev/null
+++ b/changelogs/unreleased/28020-improve-todo-list-when-comes-from-yourself.yml
@@ -0,0 +1,4 @@
+---
+title: Improve text on todo list when the todo action comes from yourself
+merge_request: 10594
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/28457-slash-command-board-move.yml b/changelogs/unreleased/28457-slash-command-board-move.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cec0f89ed912acbaebde9e68721fbbde67a9208d
--- /dev/null
+++ b/changelogs/unreleased/28457-slash-command-board-move.yml
@@ -0,0 +1,4 @@
+---
+title: Add board_move slash command
+merge_request: 10433
+author: Alex Sanford
diff --git a/changelogs/unreleased/add_index_on_ci_runners_contacted_at.yml b/changelogs/unreleased/add_index_on_ci_runners_contacted_at.yml
new file mode 100644
index 0000000000000000000000000000000000000000..10c3206c2ff95ca86999b966636718d48c519f2c
--- /dev/null
+++ b/changelogs/unreleased/add_index_on_ci_runners_contacted_at.yml
@@ -0,0 +1,4 @@
+---
+title: Add index on ci_runners.contacted_at
+merge_request: 10876
+author: blackst0ne
diff --git a/changelogs/unreleased/dm-video-viewer.yml b/changelogs/unreleased/dm-video-viewer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1c42b16e9674da8ce9ca4ac644987626aaf9d90e
--- /dev/null
+++ b/changelogs/unreleased/dm-video-viewer.yml
@@ -0,0 +1,4 @@
+---
+title: Display video blobs in-line like images
+merge_request:
+author:
diff --git a/changelogs/unreleased/pages-0-4-1.yml b/changelogs/unreleased/pages-0-4-1.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fbc78a36cae716c7f5fb52a9b7799c56b09bfebe
--- /dev/null
+++ b/changelogs/unreleased/pages-0-4-1.yml
@@ -0,0 +1,4 @@
+---
+title: Use GitLab Pages v0.4.1
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-accept-default-branch-param.yml b/changelogs/unreleased/zj-accept-default-branch-param.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8f6fa8a63867f050c3b2350863f6a765aa9d4fbc
--- /dev/null
+++ b/changelogs/unreleased/zj-accept-default-branch-param.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure the chat notifications service properly saves the "Notify only default branch" setting
+merge_request: 10959
+author:
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index b909cc5b9a4fb775cee5716b9983524ee2fb24cd..a7efd74f09e93d9afdb62f1604807e54d3611432 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -36,10 +36,10 @@ if Rails.env.test?
       RspecProfiling::Collectors::PSQL.prepend(RspecProfilingExt::PSQL)
       config.collector = RspecProfiling::Collectors::PSQL
     end
-  end
 
-  if ENV.has_key?('CI') && ENV['GITLAB_DATABASE'] == 'postgresql'
-    RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
-    RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+    if ENV.key?('CI')
+      RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
+      RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+    end
   end
 end
diff --git a/config/routes.rb b/config/routes.rb
index 1da226a3b575b54aefbe8343b4c60261e55dabe8..2584981bb040e3448f462fe7516e9fd9b7d40301 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -99,5 +99,7 @@ Rails.application.routes.draw do
     end
   end
 
+  draw :test if Rails.env.test?
+
   get '*unmatched_route', to: 'application#route_not_found'
 end
diff --git a/config/routes/test.rb b/config/routes/test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac477cdbbbc2c69a892df513b05ed6665686569a
--- /dev/null
+++ b/config/routes/test.rb
@@ -0,0 +1,2 @@
+get '/unicorn_test/pid' => 'unicorn_test#pid'
+post '/unicorn_test/kill' => 'unicorn_test#kill'
diff --git a/db/migrate/20170426181740_add_index_on_ci_runners_contacted_at.rb b/db/migrate/20170426181740_add_index_on_ci_runners_contacted_at.rb
new file mode 100644
index 0000000000000000000000000000000000000000..879825a1934a227eaf2af36350407396aeefbbec
--- /dev/null
+++ b/db/migrate/20170426181740_add_index_on_ci_runners_contacted_at.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexOnCiRunnersContactedAt < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :ci_runners, :contacted_at
+  end
+
+  def down
+    remove_concurrent_index :ci_runners, :contacted_at if index_exists?(:ci_runners, :contacted_at)
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 49d7c9966614af13421b9b69a231df1b06fc23a3..b938657a186557b164103d34ecc6dc8407b4da68 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: 20170426175636) do
+ActiveRecord::Schema.define(version: 20170426181740) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -296,6 +296,7 @@ ActiveRecord::Schema.define(version: 20170426175636) do
     t.boolean "locked", default: false, null: false
   end
 
+  add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
   add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree
   add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
   add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index f6027b2f99eb1cd7455c8640edc480c3805b609e..725fc1f6076e02822ac346d4b8a8dbc979937b38 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -65,14 +65,14 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
   #
   # Example: 'Paris' or 'Acme, Ltd.'
   label: 'LDAP'
-  
+
   # Example: 'ldap.mydomain.com'
   host: '_your_ldap_server'
   # This port is an example, it is sometimes different but it is always an integer and not a string
   port: 389
-  uid: 'sAMAccountName'
+  uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
   method: 'plain' # "tls" or "ssl" or "plain"
-  
+
   # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
   bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
   password: '_the_password_of_the_bind_user'
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index 166a10293c37f4a23f8e29836f33f2f5003e5297..2814c18e0b63e1fc3b528ca7e9494c55d54a1ab9 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -70,3 +70,27 @@ All the docs follow the same [styleguide](doc_styleguide.md).
 ### Markdown
 
 Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future.
+
+## Testing
+
+We try to treat documentation as code, thus have implemented some testing.
+Currently, the following tests are in place:
+
+1. `docs:check:links`: Check that all internal (relative) links work correctly
+1. `docs:check:apilint`: Check that the API docs follow some conventions
+
+If your contribution contains **only** documentation changes, you can speed up
+the CI process by prepending to the name of your branch: `docs/`. For example,
+a valid name would be `docs/update-api-issues` and it will run only the docs
+tests. If the name is `docs-update-api-issues`, the whole test suite will run
+(including docs).
+
+---
+
+When you submit a merge request to GitLab Community Edition (CE), there is an
+additional job called `rake ee_compat_check` that runs against Enterprise
+Edition (EE) and checks if your changes can apply cleanly to the EE codebase.
+If that job fails, read the instructions in the job log for what to do next.
+Contributors do not need to submit their changes to EE, GitLab Inc. employees
+on the other hand need to make sure that their changes apply cleanly to both
+CE and EE.
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index a4935f66cbde76926879c80f113b2be2f566eabb..a954840b8a674fcd41b52a608cd2b9cddf144373 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -100,7 +100,7 @@ On failure, the endpoint will return a `500` HTTP status code. On success, the e
 will return a valid successful HTTP status code, and a `success` message. Ideally your
 uptime monitoring should look for the success message.
 
-[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
+[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416
 [ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
 [pingdom]: https://www.pingdom.com
 [nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 45176fde9db860568eae8c1e8fb75a84ced5c43d..08452ca75cd3d7a88b9aa5905fd5e35fc37ab8ab 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -36,3 +36,4 @@ do.
 | `/remove_time_spent`       | Remove time spent |
 | `/target_branch <Branch Name>` | Set target branch for current merge request |
 | `/award :emoji:`  | Toggle award for :emoji: |
+| `/board_move ~column`      | Move issue to column on the board |
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index de737cdc82364555fda2c0f09c77c19746a1cd1c..f19fa1c7600a8803ad25e191d41099c55f20d0e6 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
     expect(response_headers['Content-Type']).to have_content("application/atom+xml")
     expect(body).to have_selector("title", text: "#{@project.name}:master commits")
     expect(body).to have_selector("author email", text: commit.author_email)
-    expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r"))
+    expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r\n"))
   end
 
   step 'I click on tag link' do
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index 2301ec9b2288c16b0a32701e40c679a8f107dfcd..99b3168d9eb85c7c43073918f3222c9a410dda1c 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -2,7 +2,7 @@ desc 'Security check via brakeman'
 task :brakeman do
   # We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
   # requests are welcome!
-  if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
+  if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb,app/controllers/unicorn_test_controller.rb -w3 -z))
     puts 'Security check succeed'
   else
     puts 'Security check failed'
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
old mode 100755
new mode 100644
index fd173c0ba88dce6bcfae83cd38eb0b3210ca69df..c727a0e2d884e2d7b574771277a011195fc4352c
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 . scripts/utils.sh
 
 export SETUP_DB=${SETUP_DB:-true}
@@ -32,7 +30,7 @@ sed -i 's/localhost/redis/g' config/resque.yml
 cp config/gitlab.yml.example config/gitlab.yml
 
 if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
-    retry bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
+    bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
 fi
 
 # Only install knapsack after bundle install! Otherwise oddly some native
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
deleted file mode 100644
index 44e011fd3a85b5cbb0948c55a279d34617f2b65e..0000000000000000000000000000000000000000
--- a/spec/controllers/blob_controller_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BlobController do
-  let(:project) { create(:project, :repository) }
-  let(:user)    { create(:user) }
-
-  before do
-    sign_in(user)
-
-    project.team << [user, :master]
-
-    allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
-    allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
-    controller.instance_variable_set(:@project, project)
-  end
-
-  describe "GET show" do
-    render_views
-
-    before do
-      get(:show,
-          namespace_id: project.namespace,
-          project_id: project,
-          id: id)
-    end
-
-    context "valid branch, valid file" do
-      let(:id) { 'master/README.md' }
-      it { is_expected.to respond_with(:success) }
-    end
-
-    context "valid branch, invalid file" do
-      let(:id) { 'master/invalid-path.rb' }
-      it { is_expected.to respond_with(:not_found) }
-    end
-
-    context "invalid branch, valid file" do
-      let(:id) { 'invalid-branch/README.md' }
-      it { is_expected.to respond_with(:not_found) }
-    end
-
-    context "binary file" do
-      let(:id) { 'binary-encoding/encoding/binary-1.bin' }
-      it { is_expected.to respond_with(:success) }
-    end
-  end
-
-  describe 'GET show with tree path' do
-    render_views
-
-    before do
-      get(:show,
-          namespace_id: project.namespace,
-          project_id: project,
-          id: id)
-      controller.instance_variable_set(:@blob, nil)
-    end
-
-    context 'redirect to tree' do
-      let(:id) { 'markdown/doc' }
-      it 'redirects' do
-        expect(subject).
-          to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc")
-      end
-    end
-  end
-end
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d321bfcea9d81bd455ae3f73c3e91c0112a237b2
--- /dev/null
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Oauth::AuthorizationsController do
+  let(:user) { create(:user) }
+
+  let(:doorkeeper) do
+    Doorkeeper::Application.create(
+      name: "MyApp",
+      redirect_uri: 'http://example.com',
+      scopes: "")
+  end
+
+  let(:params) do
+    {
+      response_type: "code",
+      client_id: doorkeeper.uid,
+      redirect_uri: doorkeeper.redirect_uri,
+      state: 'state'
+    }
+  end
+
+  before do
+    sign_in(user)
+  end
+
+  describe 'GET #new' do
+    context 'without valid params' do
+      it 'returns 200 code and renders error view' do
+        get :new
+
+        expect(response).to have_http_status(200)
+        expect(response).to render_template('doorkeeper/authorizations/error')
+      end
+    end
+
+    context 'with valid params' do
+      it 'returns 200 code and renders view' do
+        get :new, params
+
+        expect(response).to have_http_status(200)
+        expect(response).to render_template('doorkeeper/authorizations/new')
+      end
+
+      it 'deletes session.user_return_to and redirects when skip authorization' do
+        request.session['user_return_to'] = 'http://example.com'
+        allow(controller).to receive(:skip_authorization?).and_return(true)
+
+        get :new, params
+
+        expect(request.session['user_return_to']).to be_nil
+        expect(response).to have_http_status(302)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/profiles/personal_access_tokens_spec.rb b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
similarity index 100%
rename from spec/controllers/profiles/personal_access_tokens_spec.rb
rename to spec/controllers/profiles/personal_access_tokens_controller_spec.rb
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 0fd09d156c49ea00dc20edc8a4cbbe70070b34d4..3b3caa9d3e63b4d4fdae61587dd3ae8b8fbe7487 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -3,6 +3,57 @@ require 'rails_helper'
 describe Projects::BlobController do
   let(:project) { create(:project, :public, :repository) }
 
+  describe "GET show" do
+    render_views
+
+    context 'with file path' do
+      before do
+        get(:show,
+            namespace_id: project.namespace,
+            project_id: project,
+            id: id)
+      end
+
+      context "valid branch, valid file" do
+        let(:id) { 'master/README.md' }
+        it { is_expected.to respond_with(:success) }
+      end
+
+      context "valid branch, invalid file" do
+        let(:id) { 'master/invalid-path.rb' }
+        it { is_expected.to respond_with(:not_found) }
+      end
+
+      context "invalid branch, valid file" do
+        let(:id) { 'invalid-branch/README.md' }
+        it { is_expected.to respond_with(:not_found) }
+      end
+
+      context "binary file" do
+        let(:id) { 'binary-encoding/encoding/binary-1.bin' }
+        it { is_expected.to respond_with(:success) }
+      end
+    end
+
+    context 'with tree path' do
+      before do
+        get(:show,
+            namespace_id: project.namespace,
+            project_id: project,
+            id: id)
+        controller.instance_variable_set(:@blob, nil)
+      end
+
+      context 'redirect to tree' do
+        let(:id) { 'markdown/doc' }
+        it 'redirects' do
+          expect(subject).
+            to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc")
+        end
+      end
+    end
+  end
+
   describe 'GET diff' do
     let(:user) { create(:user) }
 
diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
similarity index 100%
rename from spec/controllers/projects/todo_controller_spec.rb
rename to spec/controllers/projects/todos_controller_spec.rb
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 55e10a1a89bbe7d47e060fe36d7b80a7e8e6e6d8..7a2987e815d47dcbea0b2ebcab661349f7fd583b 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -53,7 +53,7 @@ describe "User Feed", feature: true  do
       end
 
       it 'has XHTML summaries in issue descriptions' do
-        expect(body).to match /we have a bug!<\/p>\n\n<hr ?\/>\n\n<p dir="auto">I guess/
+        expect(body).to match /<hr ?\/>/
       end
 
       it 'has XHTML summaries in notes' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index 5e19907eef9047b2bb638dd1b0a5118c880381ab..b4fc0edbde822b81001546eb0105d71bf5947d33 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -78,11 +78,10 @@ feature 'Project milestone', :feature do
 
       it 'shows the total MR and issue counts' do
         find('.milestone-sidebar .block', match: :first)
-        blocks = all('.milestone-sidebar .block')
 
         aggregate_failures 'MR and issue blocks' do
-          expect(blocks[3]).to have_content 1
-          expect(blocks[4]).to have_content 0
+          expect(find('.milestone-sidebar .block.issues')).to have_content 1
+          expect(find('.milestone-sidebar .block.merge-requests')).to have_content 0
         end
       end
     end
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb
index eb3cea775da835b69ed68992e17278ea60f4aaf3..d30e7947106cd98e7cd9f3866b52f231b1c2bc0f 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/features/protected_branches/access_control_ce_spec.rb
@@ -9,7 +9,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
         allowed_to_push_button = find(".js-allowed-to-push")
 
         unless allowed_to_push_button.text == access_type_name
-          allowed_to_push_button.click
+          allowed_to_push_button.trigger('click')
           within(".dropdown.open .dropdown-menu") { click_on access_type_name }
         end
       end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index acc5641f9302e5d6bc32291be1a5d07c3184ee1e..fc9b293c393c696ccdc886e26471b87bb0bb39d1 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -8,7 +8,7 @@ feature 'Projected Branches', feature: true, js: true do
   before { login_as(user) }
 
   def set_protected_branch_name(branch_name)
-    find(".js-protected-branch-select").click
+    find(".js-protected-branch-select").trigger('click')
     find(".dropdown-input-field").set(branch_name)
     click_on("Create wildcard #{branch_name}")
   end
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index cecb98641a6e945b05c7316825a5554b350b1be9..f32e70c2c3fdec4ad0ca497d331baa28d925033f 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -45,8 +45,8 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
 
       wait_for_ajax
 
-      expect(find('.todos-list')).to     have_content user_1.name
-      expect(find('.todos-list')).not_to have_content user_2.name
+      expect(find('.todos-list')).to     have_content 'merge request'
+      expect(find('.todos-list')).not_to have_content 'issue'
     end
 
     it "shows only authors of existing todos" do
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 50c207fb9cb8312732015563a57115c31c09ee94..be5b3af417f1f3fdc7ef3f223b26efa19e5c72a1 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -99,6 +99,83 @@ describe 'Dashboard Todos', feature: true do
       end
     end
 
+    context 'User created todos for themself' do
+      before do
+        login_as(user)
+      end
+
+      context 'issue assigned todo' do
+        before do
+          create(:todo, :assigned, user: user, project: project, target: issue, author: user)
+          visit dashboard_todos_path
+        end
+
+        it 'shows issue assigned to yourself message' do
+          page.within('.js-todos-all')  do
+            expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
+          end
+        end
+      end
+
+      context 'marked todo' do
+        before do
+          create(:todo, :marked, user: user, project: project, target: issue, author: user)
+          visit dashboard_todos_path
+        end
+
+        it 'shows you added a todo message' do
+          page.within('.js-todos-all')  do
+            expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+            expect(page).not_to have_content('to yourself')
+          end
+        end
+      end
+
+      context 'mentioned todo' do
+        before do
+          create(:todo, :mentioned, user: user, project: project, target: issue, author: user)
+          visit dashboard_todos_path
+        end
+
+        it 'shows you mentioned yourself message' do
+          page.within('.js-todos-all')  do
+            expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+            expect(page).not_to have_content('to yourself')
+          end
+        end
+      end
+
+      context 'directly_addressed todo' do
+        before do
+          create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user)
+          visit dashboard_todos_path
+        end
+
+        it 'shows you directly addressed yourself message' do
+          page.within('.js-todos-all')  do
+            expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+            expect(page).not_to have_content('to yourself')
+          end
+        end
+      end
+
+      context 'approval todo' do
+        let(:merge_request) { create(:merge_request) }
+
+        before do
+          create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
+          visit dashboard_todos_path
+        end
+
+        it 'shows you set yourself as an approver message' do
+          page.within('.js-todos-all')  do
+            expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+            expect(page).not_to have_content('to yourself')
+          end
+        end
+      end
+    end
+
     context 'User has done todos', js: true do
       before do
         create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index a7c3c281083fa79978fec90b6eac9f6e505e84e7..c3bd0cb35425f1f432f525b995666e110d72906b 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -56,7 +56,7 @@ describe EventsHelper do
 
     it 'preserves code color scheme' do
       input = "```ruby\ndef test\n  'hello world'\nend\n```"
-      expected = '<pre class="code highlight js-syntax-highlight ruby">' \
+      expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \
         "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \
         "</code></pre>"
       expect(helper.event_note(input)).to eq(expected)
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 7a84bbebd0231912f97ead4719a6ae2d9bd2e070..bc66ce83d4ae86cd7d33c18853c1851ae0ec8cb9 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,19 +1,19 @@
 require 'spec_helper'
 
-describe Gitlab::Checks::ChangeAccess, lib: true do
+describe Gitlab::Checks::ForcePush, lib: true do
   let(:project) { create(:project, :repository) }
 
   context "exit code checking" do
     it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
       allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
 
-      expect { Gitlab::Checks::ForcePush.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
+      expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
     end
 
     it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do
       allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
 
-      expect { Gitlab::Checks::ForcePush.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError)
+      expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError)
     end
   end
 end
diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
similarity index 100%
rename from spec/lib/git_ref_validator_spec.rb
rename to spec/lib/gitlab/git_ref_validator_spec.rb
diff --git a/spec/lib/gitlab/healthchecks/db_check_spec.rb b/spec/lib/gitlab/health_checks/db_check_spec.rb
similarity index 100%
rename from spec/lib/gitlab/healthchecks/db_check_spec.rb
rename to spec/lib/gitlab/health_checks/db_check_spec.rb
diff --git a/spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
similarity index 100%
rename from spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb
rename to spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
diff --git a/spec/lib/gitlab/healthchecks/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis_check_spec.rb
similarity index 100%
rename from spec/lib/gitlab/healthchecks/redis_check_spec.rb
rename to spec/lib/gitlab/health_checks/redis_check_spec.rb
diff --git a/spec/lib/gitlab/healthchecks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
similarity index 100%
rename from spec/lib/gitlab/healthchecks/simple_check_shared.rb
rename to spec/lib/gitlab/health_checks/simple_check_shared.rb
diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
similarity index 100%
rename from spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
rename to spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
similarity index 100%
rename from spec/lib/gitlab/backend/shell_spec.rb
rename to spec/lib/gitlab/shell_spec.rb
diff --git a/spec/lib/light_url_builder_spec.rb b/spec/lib/light_url_builder_spec.rb
deleted file mode 100644
index 3fe8cf43934d0e988ea475515c52ce4c74a4ea4e..0000000000000000000000000000000000000000
--- a/spec/lib/light_url_builder_spec.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::UrlBuilder, lib: true do
-  describe '.build' do
-    context 'when passing a Commit' do
-      it 'returns a proper URL' do
-        commit = build_stubbed(:commit)
-
-        url = described_class.build(commit)
-
-        expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.path_with_namespace}/commit/#{commit.id}"
-      end
-    end
-
-    context 'when passing an Issue' do
-      it 'returns a proper URL' do
-        issue = build_stubbed(:issue, iid: 42)
-
-        url = described_class.build(issue)
-
-        expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}"
-      end
-    end
-
-    context 'when passing a MergeRequest' do
-      it 'returns a proper URL' do
-        merge_request = build_stubbed(:merge_request, iid: 42)
-
-        url = described_class.build(merge_request)
-
-        expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}"
-      end
-    end
-
-    context 'when passing a Note' do
-      context 'on a Commit' do
-        it 'returns a proper URL' do
-          note = build_stubbed(:note_on_commit)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}"
-        end
-      end
-
-      context 'on a Commit Diff' do
-        it 'returns a proper URL' do
-          note = build_stubbed(:diff_note_on_commit)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}"
-        end
-      end
-
-      context 'on an Issue' do
-        it 'returns a proper URL' do
-          issue = create(:issue, iid: 42)
-          note = build_stubbed(:note_on_issue, noteable: issue)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}"
-        end
-      end
-
-      context 'on a MergeRequest' do
-        it 'returns a proper URL' do
-          merge_request = create(:merge_request, iid: 42)
-          note = build_stubbed(:note_on_merge_request, noteable: merge_request)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}"
-        end
-      end
-
-      context 'on a MergeRequest Diff' do
-        it 'returns a proper URL' do
-          merge_request = create(:merge_request, iid: 42)
-          note = build_stubbed(:diff_note_on_merge_request, noteable: merge_request)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}"
-        end
-      end
-
-      context 'on a ProjectSnippet' do
-        it 'returns a proper URL' do
-          project_snippet = create(:project_snippet)
-          note = build_stubbed(:note_on_project_snippet, noteable: project_snippet)
-
-          url = described_class.build(note)
-
-          expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}"
-        end
-      end
-
-      context 'on another object' do
-        it 'returns a proper URL' do
-          project = build_stubbed(:empty_project)
-
-          expect { described_class.build(project) }.
-            to raise_error(NotImplementedError, 'No URL builder defined for Project')
-        end
-      end
-    end
-
-    context 'when passing a WikiPage' do
-      it 'returns a proper URL' do
-        wiki_page = build(:wiki_page)
-        url = described_class.build(wiki_page)
-
-        expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_base_path}/#{wiki_page.slug}"
-      end
-    end
-  end
-end
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index e22858d1d8fbbdcb8c229e3e0dc786aaf73856f5..2ad572bb5c75f728828ce8c439b285131bc12811 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'email_spec'
 
-describe Notify, "merge request notifications" do
+describe Emails::MergeRequests do
   include EmailSpec::Matchers
 
   describe "#resolved_all_discussions_email" do
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index 5ca936f28f0d0603798681c0c222d0a0f193f59b..8c1c9bf135fa58412c0dd4c9b816fbc0ba2ecb55 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'email_spec'
 
-describe Notify do
+describe Emails::Profile do
   include EmailSpec::Matchers
   include_context 'gitlab email notification'
 
@@ -15,106 +15,104 @@ describe Notify do
     end
   end
 
-  describe 'profile notifications' do
-    describe 'for new users, the email' do
-      let(:example_site_path) { root_path }
-      let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
-      let(:token) { 'kETLwRaayvigPq_x3SNM' }
+  describe 'for new users, the email' do
+    let(:example_site_path) { root_path }
+    let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
+    let(:token) { 'kETLwRaayvigPq_x3SNM' }
 
-      subject { Notify.new_user_email(new_user.id, token) }
+    subject { Notify.new_user_email(new_user.id, token) }
 
-      it_behaves_like 'an email sent from GitLab'
-      it_behaves_like 'a new user email'
-      it_behaves_like 'it should not have Gmail Actions links'
-      it_behaves_like 'a user cannot unsubscribe through footer link'
+    it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'a new user email'
+    it_behaves_like 'it should not have Gmail Actions links'
+    it_behaves_like 'a user cannot unsubscribe through footer link'
 
-      it 'contains the password text' do
-        is_expected.to have_body_text /Click here to set your password/
-      end
+    it 'contains the password text' do
+      is_expected.to have_body_text /Click here to set your password/
+    end
 
-      it 'includes a link for user to set password' do
-        params = "reset_password_token=#{token}"
-        is_expected.to have_body_text(
-          %r{http://#{Gitlab.config.gitlab.host}(:\d+)?/users/password/edit\?#{params}}
-        )
-      end
+    it 'includes a link for user to set password' do
+      params = "reset_password_token=#{token}"
+      is_expected.to have_body_text(
+        %r{http://#{Gitlab.config.gitlab.host}(:\d+)?/users/password/edit\?#{params}}
+      )
+    end
 
-      it 'explains the reset link expiration' do
-        is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
-        is_expected.to have_body_text(new_user_password_url)
-        is_expected.to have_body_text(/\?user_email=.*%40.*/)
-      end
+    it 'explains the reset link expiration' do
+      is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
+      is_expected.to have_body_text(new_user_password_url)
+      is_expected.to have_body_text(/\?user_email=.*%40.*/)
     end
+  end
 
-    describe 'for users that signed up, the email' do
-      let(:example_site_path) { root_path }
-      let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
+  describe 'for users that signed up, the email' do
+    let(:example_site_path) { root_path }
+    let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
 
-      subject { Notify.new_user_email(new_user.id) }
+    subject { Notify.new_user_email(new_user.id) }
 
-      it_behaves_like 'an email sent from GitLab'
-      it_behaves_like 'a new user email'
-      it_behaves_like 'it should not have Gmail Actions links'
-      it_behaves_like 'a user cannot unsubscribe through footer link'
+    it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'a new user email'
+    it_behaves_like 'it should not have Gmail Actions links'
+    it_behaves_like 'a user cannot unsubscribe through footer link'
 
-      it 'does not contain the new user\'s password' do
-        is_expected.not_to have_body_text /password/
-      end
+    it 'does not contain the new user\'s password' do
+      is_expected.not_to have_body_text /password/
     end
+  end
 
-    describe 'user added ssh key' do
-      let(:key) { create(:personal_key) }
+  describe 'user added ssh key' do
+    let(:key) { create(:personal_key) }
 
-      subject { Notify.new_ssh_key_email(key.id) }
+    subject { Notify.new_ssh_key_email(key.id) }
 
-      it_behaves_like 'an email sent from GitLab'
-      it_behaves_like 'it should not have Gmail Actions links'
-      it_behaves_like 'a user cannot unsubscribe through footer link'
+    it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'it should not have Gmail Actions links'
+    it_behaves_like 'a user cannot unsubscribe through footer link'
 
-      it 'is sent to the new user' do
-        is_expected.to deliver_to key.user.email
-      end
+    it 'is sent to the new user' do
+      is_expected.to deliver_to key.user.email
+    end
 
-      it 'has the correct subject' do
-        is_expected.to have_subject /^SSH key was added to your account$/i
-      end
+    it 'has the correct subject' do
+      is_expected.to have_subject /^SSH key was added to your account$/i
+    end
 
-      it 'contains the new ssh key title' do
-        is_expected.to have_body_text /#{key.title}/
-      end
+    it 'contains the new ssh key title' do
+      is_expected.to have_body_text /#{key.title}/
+    end
 
-      it 'includes a link to ssh keys page' do
-        is_expected.to have_body_text /#{profile_keys_path}/
-      end
+    it 'includes a link to ssh keys page' do
+      is_expected.to have_body_text /#{profile_keys_path}/
+    end
 
-      context 'with SSH key that does not exist' do
-        it { expect { Notify.new_ssh_key_email('foo') }.not_to raise_error }
-      end
+    context 'with SSH key that does not exist' do
+      it { expect { Notify.new_ssh_key_email('foo') }.not_to raise_error }
     end
+  end
 
-    describe 'user added email' do
-      let(:email) { create(:email) }
+  describe 'user added email' do
+    let(:email) { create(:email) }
 
-      subject { Notify.new_email_email(email.id) }
+    subject { Notify.new_email_email(email.id) }
 
-      it_behaves_like 'it should not have Gmail Actions links'
-      it_behaves_like 'a user cannot unsubscribe through footer link'
+    it_behaves_like 'it should not have Gmail Actions links'
+    it_behaves_like 'a user cannot unsubscribe through footer link'
 
-      it 'is sent to the new user' do
-        is_expected.to deliver_to email.user.email
-      end
+    it 'is sent to the new user' do
+      is_expected.to deliver_to email.user.email
+    end
 
-      it 'has the correct subject' do
-        is_expected.to have_subject /^Email was added to your account$/i
-      end
+    it 'has the correct subject' do
+      is_expected.to have_subject /^Email was added to your account$/i
+    end
 
-      it 'contains the new email address' do
-        is_expected.to have_body_text /#{email.email}/
-      end
+    it 'contains the new email address' do
+      is_expected.to have_body_text /#{email.email}/
+    end
 
-      it 'includes a link to emails page' do
-        is_expected.to have_body_text /#{profile_emails_path}/
-      end
+    it 'includes a link to emails page' do
+      is_expected.to have_body_text /#{profile_emails_path}/
     end
   end
 end
diff --git a/spec/migrations/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
similarity index 100%
rename from spec/migrations/schema_spec.rb
rename to spec/migrations/active_record/schema_spec.rb
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index de791abdf3dbc66b26059b37db419a925a61922b..63ad3a3630b228b31a15512d16b5e8f07991d283 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -1,10 +1,12 @@
 require 'spec_helper'
 
-describe Issue, "Awardable" do
+describe Awardable do
   let!(:issue)        { create(:issue) }
   let!(:award_emoji)  { create(:award_emoji, :downvote, awardable: issue) }
 
   describe "Associations" do
+    subject { build(:issue) }
+
     it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
   end
 
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 0002a00770f48537f09c4bc13a13253e2d9f62c2..8571e85627ce123e58ce63ffddca627f94d785a5 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe DiffDiscussion, DiscussionOnDiff, model: true do
+describe DiscussionOnDiff, model: true do
   subject { create(:diff_note_on_merge_request).to_discussion }
 
   describe "#truncated_diff_lines" do
@@ -8,9 +8,9 @@ describe DiffDiscussion, DiscussionOnDiff, model: true do
 
     context "when diff is greater than allowed number of truncated diff lines " do
       it "returns fewer lines"  do
-        expect(subject.diff_lines.count).to be > described_class::NUMBER_OF_TRUNCATED_DIFF_LINES
+        expect(subject.diff_lines.count).to be > DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
 
-        expect(truncated_lines.count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES
+        expect(truncated_lines.count).to be <= DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
       end
     end
 
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 4522206fab15a3077ad3c5d4207b348ff424731c..3ecba2e96870bb14ab47656a7949b5ee606e3ffa 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -1,10 +1,13 @@
 require 'spec_helper'
 
-describe Issue, "Issuable" do
+describe Issuable do
+  let(:issuable_class) { Issue }
   let(:issue) { create(:issue) }
   let(:user) { create(:user) }
 
   describe "Associations" do
+    subject { build(:issue) }
+
     it { is_expected.to belong_to(:project) }
     it { is_expected.to belong_to(:author) }
     it { is_expected.to belong_to(:assignee) }
@@ -23,10 +26,14 @@ describe Issue, "Issuable" do
   end
 
   describe 'Included modules' do
+    let(:described_class) { issuable_class }
+
     it { is_expected.to include_module(Awardable) }
   end
 
   describe "Validation" do
+    subject { build(:issue) }
+
     before do
       allow(subject).to receive(:set_iid).and_return(false)
     end
@@ -39,9 +46,11 @@ describe Issue, "Issuable" do
   end
 
   describe "Scope" do
-    it { expect(described_class).to respond_to(:opened) }
-    it { expect(described_class).to respond_to(:closed) }
-    it { expect(described_class).to respond_to(:assigned) }
+    subject { build(:issue) }
+
+    it { expect(issuable_class).to respond_to(:opened) }
+    it { expect(issuable_class).to respond_to(:closed) }
+    it { expect(issuable_class).to respond_to(:assigned) }
   end
 
   describe 'author_name' do
@@ -115,16 +124,16 @@ describe Issue, "Issuable" do
     let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
 
     it 'returns notes with a matching title' do
-      expect(described_class.search(searchable_issue.title)).
+      expect(issuable_class.search(searchable_issue.title)).
         to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching title' do
-      expect(described_class.search('able')).to eq([searchable_issue])
+      expect(issuable_class.search('able')).to eq([searchable_issue])
     end
 
     it 'returns notes with a matching title regardless of the casing' do
-      expect(described_class.search(searchable_issue.title.upcase)).
+      expect(issuable_class.search(searchable_issue.title.upcase)).
         to eq([searchable_issue])
     end
   end
@@ -135,31 +144,31 @@ describe Issue, "Issuable" do
     end
 
     it 'returns notes with a matching title' do
-      expect(described_class.full_search(searchable_issue.title)).
+      expect(issuable_class.full_search(searchable_issue.title)).
         to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching title' do
-      expect(described_class.full_search('able')).to eq([searchable_issue])
+      expect(issuable_class.full_search('able')).to eq([searchable_issue])
     end
 
     it 'returns notes with a matching title regardless of the casing' do
-      expect(described_class.full_search(searchable_issue.title.upcase)).
+      expect(issuable_class.full_search(searchable_issue.title.upcase)).
         to eq([searchable_issue])
     end
 
     it 'returns notes with a matching description' do
-      expect(described_class.full_search(searchable_issue.description)).
+      expect(issuable_class.full_search(searchable_issue.description)).
         to eq([searchable_issue])
     end
 
     it 'returns notes with a partially matching description' do
-      expect(described_class.full_search(searchable_issue.description)).
+      expect(issuable_class.full_search(searchable_issue.description)).
         to eq([searchable_issue])
     end
 
     it 'returns notes with a matching description regardless of the casing' do
-      expect(described_class.full_search(searchable_issue.description.upcase)).
+      expect(issuable_class.full_search(searchable_issue.description.upcase)).
         to eq([searchable_issue])
     end
   end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 92cc8859a8cc74b64e75bbb052bf430eceafaa16..bdae742ff1def07b363159efbac8798369d9eb48 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequest, Noteable, model: true do
+describe Noteable, model: true do
   let!(:active_diff_note1) { create(:diff_note_on_merge_request) }
   let(:project) { active_diff_note1.project }
   subject { active_diff_note1.noteable }
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index 255b584a85e78ec4eb039e0c3c21b14c0365384f..494e6f1b6f65631794f3e3ba4331ee458276cac1 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Issue, 'RelativePositioning' do
+describe RelativePositioning do
   let(:project) { create(:empty_project) }
   let(:issue) { create(:issue, project: project) }
   let(:issue1) { create(:issue, project: project) }
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index fd3b830757148c01bc61563dc075b301d929af0d..e698207166c220289047c8f3ea245a5eb52b503d 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -1,9 +1,11 @@
 require 'spec_helper'
 
-describe Issue, 'Spammable' do
+describe Spammable do
   let(:issue) { create(:issue, description: 'Test Desc.') }
 
   describe 'Associations' do
+    subject { build(:issue) }
+
     it { is_expected.to have_one(:user_agent_detail).dependent(:destroy) }
   end
 
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
index c3af7a0960ffb28d42fe2b8e64992efe520c6182..8c945686b6663a95c0202d6490a63c34b08b26a9 100644
--- a/spec/models/concerns/strip_attribute_spec.rb
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Milestone, "StripAttribute" do
+describe StripAttribute do
   let(:milestone) { create(:milestone) }
 
   describe ".strip_attributes" do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index b0f3657d3b5fa769e63063df1ebf736ec4902739..ccc3deac199b0b17601a96ea8d861674cc37f3a6 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -390,13 +390,15 @@ describe Member, models: true do
     %w[project group].each do |source_type|
       context "when source is a #{source_type}" do
         let!(:source) { create(source_type, :public, :access_requestable) }
-        let!(:user) { create(:user) }
         let!(:admin) { create(:admin) }
+        let(:user1) { create(:user) }
+        let(:user2) { create(:user) }
 
         it 'returns a <Source>Member objects' do
-          members = described_class.add_users(source, [user], :master)
+          members = described_class.add_users(source, [user1, user2], :master)
 
           expect(members).to be_a Array
+          expect(members.size).to eq(2)
           expect(members.first).to be_a "#{source_type.classify}Member".constantize
           expect(members.first).to be_persisted
         end
diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
similarity index 100%
rename from spec/models/project_services/pipeline_email_service_spec.rb
rename to spec/models/project_services/pipelines_email_service_spec.rb
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 581305ad39f06e6f03b1a4382764ec42d990c1ee..3f80e1ac5343faece7081c5ec4405f2ca16d92d4 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -125,4 +125,50 @@ describe Todo, models: true do
       expect(subject.target_reference).to eq issue.to_reference(full: true)
     end
   end
+
+  describe '#self_added?' do
+    let(:user_1) { build(:user) }
+
+    before do
+      subject.user = user_1
+    end
+
+    it 'is true when the user is the author' do
+      subject.author = user_1
+
+      expect(subject).to be_self_added
+    end
+
+    it 'is false when the user is not the author' do
+      subject.author = build(:user)
+
+      expect(subject).not_to be_self_added
+    end
+  end
+
+  describe '#self_assigned?' do
+    let(:user_1) { build(:user) }
+
+    before do
+      subject.user = user_1
+      subject.author = user_1
+      subject.action = Todo::ASSIGNED
+    end
+
+    it 'is true when todo is ASSIGNED and self_added' do
+      expect(subject).to be_self_assigned
+    end
+
+    it 'is false when the todo is not ASSIGNED' do
+      subject.action = Todo::MENTIONED
+
+      expect(subject).not_to be_self_assigned
+    end
+
+    it 'is false when todo is not self_added' do
+      subject.author = build(:user)
+
+      expect(subject).not_to be_self_assigned
+    end
+  end
 end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 2905d5b26a5db0edf382ed0c3873b07d824e803a..9a870b7fda12f3e5a7f794941beeabfa2fc9f521 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -1,118 +1,192 @@
 require 'spec_helper'
 
 describe IssuePolicy, models: true do
-  let(:user) { create(:user) }
-
-  describe '#rules' do
-    context 'using a regular issue' do
-      let(:project) { create(:empty_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
+  let(:guest) { create(:user) }
+  let(:author) { create(:user) }
+  let(:assignee) { create(:user) }
+  let(:reporter) { create(:user) }
+  let(:group) { create(:group, :public) }
+  let(:reporter_from_group_link) { create(:user) }
+
+  def permissions(user, issue)
+    described_class.abilities(user, issue).to_set
+  end
+
+  context 'a private project' do
+    let(:non_member) { create(:user) }
+    let(:project) { create(:empty_project, :private) }
+    let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
+    let(:issue_no_assignee) { create(:issue, project: project) }
+
+    before do
+      project.team << [guest, :guest]
+      project.team << [author, :guest]
+      project.team << [assignee, :guest]
+      project.team << [reporter, :reporter]
+
+      group.add_reporter(reporter_from_group_link)
+
+      create(:project_group_link, group: group, project: project)
+    end
+
+    it 'does not allow non-members to read issues' do
+      expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+    end
+
+    it 'allows guests to read issues' do
+      expect(permissions(guest, issue)).to include(:read_issue)
+      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+
+      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
+
+    it 'allows reporters to read, update, and admin issues' do
+      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+    end
+
+    it 'allows reporters from group links to read, update, and admin issues' do
+      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+    end
+
+    it 'allows issue authors to read and update their issues' do
+      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
+      expect(permissions(author, issue)).not_to include(:admin_issue)
+
+      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
+
+    it 'allows issue assignees to read and update their issues' do
+      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
+      expect(permissions(assignee, issue)).not_to include(:admin_issue)
+
+      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
 
-      context 'with a user that is a project reporter' do
-        before do
-          project.team << [user, :reporter]
-        end
+    context 'with confidential issues' do
+      let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
+      let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
 
-        it 'includes the read_issue permission' do
-          expect(policies).to include(:read_issue)
-        end
+      it 'does not allow non-members to read confidential issues' do
+        expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+      end
+
+      it 'does not allow guests to read confidential issues' do
+        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+      end
 
-        it 'includes the admin_issue permission' do
-          expect(policies).to include(:admin_issue)
-        end
+      it 'allows reporters to read, update, and admin confidential issues' do
+        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      end
 
-        it 'includes the update_issue permission' do
-          expect(policies).to include(:update_issue)
-        end
+      it 'allows reporters from group links to read, update, and admin confidential issues' do
+        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
       end
 
-      context 'with a user that is a project guest' do
-        before do
-          project.team << [user, :guest]
-        end
+      it 'allows issue authors to read and update their confidential issues' do
+        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
+        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
 
-        it 'includes the read_issue permission' do
-          expect(policies).to include(:read_issue)
-        end
+        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+      end
 
-        it 'does not include the admin_issue permission' do
-          expect(policies).not_to include(:admin_issue)
-        end
+      it 'allows issue assignees to read and update their confidential issues' do
+        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
+        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
 
-        it 'does not include the update_issue permission' do
-          expect(policies).not_to include(:update_issue)
-        end
+        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
       end
     end
+  end
 
-    context 'using a confidential issue' do
-      let(:issue) { create(:issue, :confidential) }
+  context 'a public project' do
+    let(:project) { create(:empty_project, :public) }
+    let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
+    let(:issue_no_assignee) { create(:issue, project: project) }
 
-      context 'with a regular user' do
-        let(:policies) { described_class.abilities(user, issue).to_set }
+    before do
+      project.team << [guest, :guest]
+      project.team << [reporter, :reporter]
 
-        it 'does not include the read_issue permission' do
-          expect(policies).not_to include(:read_issue)
-        end
+      group.add_reporter(reporter_from_group_link)
 
-        it 'does not include the admin_issue permission' do
-          expect(policies).not_to include(:admin_issue)
-        end
+      create(:project_group_link, group: group, project: project)
+    end
 
-        it 'does not include the update_issue permission' do
-          expect(policies).not_to include(:update_issue)
-        end
-      end
+    it 'allows guests to read issues' do
+      expect(permissions(guest, issue)).to include(:read_issue)
+      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
+
+      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
+
+    it 'allows reporters to read, update, and admin issues' do
+      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+    end
+
+    it 'allows reporters from group links to read, update, and admin issues' do
+      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
+      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+    end
 
-      context 'with a user that is a project member' do
-        let(:policies) { described_class.abilities(user, issue).to_set }
+    it 'allows issue authors to read and update their issues' do
+      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
+      expect(permissions(author, issue)).not_to include(:admin_issue)
 
-        before do
-          issue.project.team << [user, :reporter]
-        end
+      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
+
+    it 'allows issue assignees to read and update their issues' do
+      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
+      expect(permissions(assignee, issue)).not_to include(:admin_issue)
 
-        it 'includes the read_issue permission' do
-          expect(policies).to include(:read_issue)
-        end
+      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
+      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
+    end
 
-        it 'includes the admin_issue permission' do
-          expect(policies).to include(:admin_issue)
-        end
+    context 'with confidential issues' do
+      let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
+      let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
 
-        it 'includes the update_issue permission' do
-          expect(policies).to include(:update_issue)
-        end
+      it 'does not allow guests to read confidential issues' do
+        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
       end
 
-      context 'without a user' do
-        let(:policies) { described_class.abilities(nil, issue).to_set }
+      it 'allows reporters to read, update, and admin confidential issues' do
+        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      end
+
+      it 'allows reporter from group links to read, update, and admin confidential issues' do
+        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
+        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
+      end
 
-        it 'does not include the read_issue permission' do
-          expect(policies).not_to include(:read_issue)
-        end
+      it 'allows issue authors to read and update their confidential issues' do
+        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
+        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
+
+        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
+      end
 
-        it 'does not include the admin_issue permission' do
-          expect(policies).not_to include(:admin_issue)
-        end
+      it 'allows issue assignees to read and update their confidential issues' do
+        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
+        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
 
-        it 'does not include the update_issue permission' do
-          expect(policies).not_to include(:update_issue)
-        end
+        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
       end
     end
   end
diff --git a/spec/policies/issues_policy_spec.rb b/spec/policies/issues_policy_spec.rb
deleted file mode 100644
index 2b7b6cad6547c70d0cdef934679debfc9470de72..0000000000000000000000000000000000000000
--- a/spec/policies/issues_policy_spec.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-require 'spec_helper'
-
-describe IssuePolicy, models: true do
-  let(:guest) { create(:user) }
-  let(:author) { create(:user) }
-  let(:assignee) { create(:user) }
-  let(:reporter) { create(:user) }
-  let(:group) { create(:group, :public) }
-  let(:reporter_from_group_link) { create(:user) }
-
-  def permissions(user, issue)
-    IssuePolicy.abilities(user, issue).to_set
-  end
-
-  context 'a private project' do
-    let(:non_member) { create(:user) }
-    let(:project) { create(:empty_project, :private) }
-    let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
-    let(:issue_no_assignee) { create(:issue, project: project) }
-
-    before do
-      project.team << [guest, :guest]
-      project.team << [author, :guest]
-      project.team << [assignee, :guest]
-      project.team << [reporter, :reporter]
-
-      group.add_reporter(reporter_from_group_link)
-
-      create(:project_group_link, group: group, project: project)
-    end
-
-    it 'does not allow non-members to read issues' do
-      expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-    end
-
-    it 'allows guests to read issues' do
-      expect(permissions(guest, issue)).to include(:read_issue)
-      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
-
-      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    it 'allows reporters to read, update, and admin issues' do
-      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-    end
-
-    it 'allows reporters from group links to read, update, and admin issues' do
-      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-    end
-
-    it 'allows issue authors to read and update their issues' do
-      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(author, issue)).not_to include(:admin_issue)
-
-      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    it 'allows issue assignees to read and update their issues' do
-      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(assignee, issue)).not_to include(:admin_issue)
-
-      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    context 'with confidential issues' do
-      let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
-      let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
-
-      it 'does not allow non-members to read confidential issues' do
-        expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'does not allow guests to read confidential issues' do
-        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows reporters to read, update, and admin confidential issues' do
-        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows reporters from group links to read, update, and admin confidential issues' do
-        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows issue authors to read and update their confidential issues' do
-        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
-
-        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows issue assignees to read and update their confidential issues' do
-        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
-
-        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-    end
-  end
-
-  context 'a public project' do
-    let(:project) { create(:empty_project, :public) }
-    let(:issue) { create(:issue, project: project, assignee: assignee, author: author) }
-    let(:issue_no_assignee) { create(:issue, project: project) }
-
-    before do
-      project.team << [guest, :guest]
-      project.team << [reporter, :reporter]
-
-      group.add_reporter(reporter_from_group_link)
-
-      create(:project_group_link, group: group, project: project)
-    end
-
-    it 'allows guests to read issues' do
-      expect(permissions(guest, issue)).to include(:read_issue)
-      expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue)
-
-      expect(permissions(guest, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    it 'allows reporters to read, update, and admin issues' do
-      expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-    end
-
-    it 'allows reporters from group links to read, update, and admin issues' do
-      expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue)
-      expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-    end
-
-    it 'allows issue authors to read and update their issues' do
-      expect(permissions(author, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(author, issue)).not_to include(:admin_issue)
-
-      expect(permissions(author, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    it 'allows issue assignees to read and update their issues' do
-      expect(permissions(assignee, issue)).to include(:read_issue, :update_issue)
-      expect(permissions(assignee, issue)).not_to include(:admin_issue)
-
-      expect(permissions(assignee, issue_no_assignee)).to include(:read_issue)
-      expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue)
-    end
-
-    context 'with confidential issues' do
-      let(:confidential_issue) { create(:issue, :confidential, project: project, assignee: assignee, author: author) }
-      let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
-
-      it 'does not allow guests to read confidential issues' do
-        expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows reporters to read, update, and admin confidential issues' do
-        expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows reporter from group links to read, update, and admin confidential issues' do
-        expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue)
-        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows issue authors to read and update their confidential issues' do
-        expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(author, confidential_issue)).not_to include(:admin_issue)
-
-        expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-
-      it 'allows issue assignees to read and update their confidential issues' do
-        expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue)
-        expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue)
-
-        expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue)
-      end
-    end
-  end
-end
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index b5897b2e34618aee00d6589aaa7583c64677c8e5..868fef65c1ca2b1a1387426e6f80fb85bd2eca47 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API do
+describe 'doorkeeper access' do
   let!(:user) { create(:user) }
   let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
   let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
diff --git a/spec/requests/api/api_internal_helpers_spec.rb b/spec/requests/api/helpers/internal_helpers_spec.rb
similarity index 100%
rename from spec/requests/api/api_internal_helpers_spec.rb
rename to spec/requests/api/helpers/internal_helpers_spec.rb
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 819df1059600158e23714e6d48bc71d8f8f34ace..0d56e1f732ed6f9bdd7bf8c96d6695eac02d4c16 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API do
+describe 'OAuth tokens' do
   context 'Resource Owner Password Credentials' do
     def request_oauth_token(user)
       post '/oauth/token', username: user.username, password: user.password, grant_type: 'password'
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index ba124de70bb195fcbdee6b9974d778c1e7732b0e..624f3c43f0a8cf9ba5a9b348aa1b2a7e2e198f58 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::EnvironmentsController, :routing do
+describe 'environments routing', :routing do
   let(:project) { create(:empty_project) }
 
   let(:environment) do
diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb
index 24592942a967e77b20d38dc6391550af2890239d..54ed87b5520b1c58159d336c5d7c6267c06ff866 100644
--- a/spec/routing/notifications_routing_spec.rb
+++ b/spec/routing/notifications_routing_spec.rb
@@ -1,13 +1,11 @@
 require "spec_helper"
 
-describe Profiles::NotificationsController do
-  describe "routing" do
-    it "routes to #show" do
-      expect(get("/profile/notifications")).to route_to("profiles/notifications#show")
-    end
+describe "notifications routing" do
+  it "routes to #show" do
+    expect(get("/profile/notifications")).to route_to("profiles/notifications#show")
+  end
 
-    it "routes to #update" do
-      expect(put("/profile/notifications")).to route_to("profiles/notifications#update")
-    end
+  it "routes to #update" do
+    expect(put("/profile/notifications")).to route_to("profiles/notifications#update")
   end
 end
diff --git a/spec/serializers/analytics_generic_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
similarity index 100%
rename from spec/serializers/analytics_generic_entity_spec.rb
rename to spec/serializers/analytics_issue_entity_spec.rb
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index 4a4929daefc9638b219d21d3c08e5b12ffa32257..c3b4c2176eeba2934fe7f94018ae328100051eee 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -1,15 +1,15 @@
 require 'spec_helper.rb'
 
-class DummyService < Issues::BaseService
-  include ::Issues::ResolveDiscussions
+describe Issues::ResolveDiscussions, services: true do
+  class DummyService < Issues::BaseService
+    include ::Issues::ResolveDiscussions
 
-  def initialize(*args)
-    super
-    filter_resolve_discussion_params
+    def initialize(*args)
+      super
+      filter_resolve_discussion_params
+    end
   end
-end
 
-describe DummyService, services: true do
   let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
 
@@ -23,7 +23,7 @@ describe DummyService, services: true do
     let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "other") }
 
     describe "#merge_request_for_resolving_discussion" do
-      let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
+      let(:service) { DummyService.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
 
       it "finds the merge request" do
         expect(service.merge_request_to_resolve_discussions_of).to eq(merge_request)
@@ -43,7 +43,7 @@ describe DummyService, services: true do
 
     describe "#discussions_to_resolve" do
       it "contains a single discussion when matching merge request and discussion are passed" do
-        service = described_class.new(
+        service = DummyService.new(
           project,
           user,
           discussion_to_resolve: discussion.id,
@@ -61,7 +61,7 @@ describe DummyService, services: true do
                                                   noteable: merge_request,
                                                   project: merge_request.target_project,
                                                   line_number: 15)])
-        service = described_class.new(
+        service = DummyService.new(
           project,
           user,
           merge_request_to_resolve_discussions_of: merge_request.iid
@@ -79,7 +79,7 @@ describe DummyService, services: true do
                                                    project: merge_request.target_project,
                                                    line_number: 15,
                                                    )])
-        service = described_class.new(
+        service = DummyService.new(
           project,
           user,
           merge_request_to_resolve_discussions_of: merge_request.iid
@@ -92,7 +92,7 @@ describe DummyService, services: true do
       end
 
       it "is empty when a discussion and another merge request are passed" do
-        service = described_class.new(
+        service = DummyService.new(
           project,
           user,
           discussion_to_resolve: discussion.id,
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
similarity index 100%
rename from spec/services/merge_requests/resolved_discussion_notification_service.rb
rename to spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index a63281f0eab7c8057e9985806dd8f7188bb59729..29e65fe7ce6da861c4c34b57e8e5cc54b7cd3039 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -52,7 +52,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'unassign command' do
       it 'populates assignee_id: nil if content contains /unassign' do
-        issuable.update(assignee_id: developer.id)
+        issuable.update!(assignee_id: developer.id)
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(assignee_id: nil)
@@ -70,7 +70,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'remove_milestone command' do
       it 'populates milestone_id: nil if content contains /remove_milestone' do
-        issuable.update(milestone_id: milestone.id)
+        issuable.update!(milestone_id: milestone.id)
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(milestone_id: nil)
@@ -108,7 +108,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'unlabel command' do
       it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
-        issuable.update(label_ids: [inprogress.id]) # populate the label
+        issuable.update!(label_ids: [inprogress.id]) # populate the label
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(remove_label_ids: [inprogress.id])
@@ -117,7 +117,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'multiple unlabel command' do
       it 'fetches label ids and populates remove_label_ids if content contains  mutiple /unlabel' do
-        issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label
+        issuable.update!(label_ids: [inprogress.id, bug.id]) # populate the label
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id])
@@ -126,7 +126,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'unlabel command with no argument' do
       it 'populates label_ids: [] if content contains /unlabel with no arguments' do
-        issuable.update(label_ids: [inprogress.id]) # populate the label
+        issuable.update!(label_ids: [inprogress.id]) # populate the label
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(label_ids: [])
@@ -135,7 +135,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'relabel command' do
       it 'populates label_ids: [] if content contains /relabel' do
-        issuable.update(label_ids: [bug.id]) # populate the label
+        issuable.update!(label_ids: [bug.id]) # populate the label
         inprogress # populate the label
         _, updates = service.execute(content, issuable)
 
@@ -187,7 +187,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'remove_due_date command' do
       it 'populates due_date: nil if content contains /remove_due_date' do
-        issuable.update(due_date: Date.today)
+        issuable.update!(due_date: Date.today)
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(due_date: nil)
@@ -204,7 +204,7 @@ describe SlashCommands::InterpretService, services: true do
 
     shared_examples 'unwip command' do
       it 'returns wip_event: "unwip" if content contains /wip' do
-        issuable.update(title: issuable.wip_title)
+        issuable.update!(title: issuable.wip_title)
         _, updates = service.execute(content, issuable)
 
         expect(updates).to eq(wip_event: 'unwip')
@@ -727,5 +727,75 @@ describe SlashCommands::InterpretService, services: true do
         end
       end
     end
+
+    context '/board_move command' do
+      let(:todo) { create(:label, project: project, title: 'To Do') }
+      let(:inreview) { create(:label, project: project, title: 'In Review') }
+      let(:content) { %{/board_move ~"#{inreview.title}"} }
+
+      let!(:board) { create(:board, project: project) }
+      let!(:todo_list) { create(:list, board: board, label: todo) }
+      let!(:inreview_list) { create(:list, board: board, label: inreview) }
+      let!(:inprogress_list) { create(:list, board: board, label: inprogress) }
+
+      it 'populates remove_label_ids for all current board columns' do
+        issue.update!(label_ids: [todo.id, inprogress.id])
+
+        _, updates = service.execute(content, issue)
+
+        expect(updates[:remove_label_ids]).to match_array([todo.id, inprogress.id])
+      end
+
+      it 'populates add_label_ids with the id of the given label' do
+        _, updates = service.execute(content, issue)
+
+        expect(updates[:add_label_ids]).to eq([inreview.id])
+      end
+
+      it 'does not include the given label id in remove_label_ids' do
+        issue.update!(label_ids: [todo.id, inreview.id])
+
+        _, updates = service.execute(content, issue)
+
+        expect(updates[:remove_label_ids]).to match_array([todo.id])
+      end
+
+      it 'does not remove label ids that are not lists on the board' do
+        issue.update!(label_ids: [todo.id, bug.id])
+
+        _, updates = service.execute(content, issue)
+
+        expect(updates[:remove_label_ids]).to match_array([todo.id])
+      end
+
+      context 'if the project has multiple boards' do
+        let(:issuable) { issue }
+        before { create(:board, project: project) }
+        it_behaves_like 'empty command'
+      end
+
+      context 'if the given label does not exist' do
+        let(:issuable) { issue }
+        let(:content) { '/board_move ~"Fake Label"' }
+        it_behaves_like 'empty command'
+      end
+
+      context 'if multiple labels are given' do
+        let(:issuable) { issue }
+        let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} }
+        it_behaves_like 'empty command'
+      end
+
+      context 'if the given label is not a list on the board' do
+        let(:issuable) { issue }
+        let(:content) { %{/board_move ~"#{bug.title}"} }
+        it_behaves_like 'empty command'
+      end
+
+      context 'if issuable is not an Issue' do
+        let(:issuable) { merge_request }
+        it_behaves_like 'empty command'
+      end
+    end
   end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e67ad8f345593e9bd433cd475db6b9324d58884a..e2d5928e5b2420962dd101421bf17ce4a9d4356a 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -13,8 +13,9 @@ rspec_profiling_is_configured =
   ENV['RSPEC_PROFILING_POSTGRES_URL'] ||
   ENV['RSPEC_PROFILING']
 branch_can_be_profiled =
-  ENV['CI_COMMIT_REF_NAME'] == 'master' ||
-  ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/
+  ENV['GITLAB_DATABASE'] == 'postgresql' &&
+  (ENV['CI_COMMIT_REF_NAME'] == 'master' ||
+    ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/)
 
 if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
   require 'rspec_profiling/rspec'
diff --git a/spec/unicorn/unicorn_spec.rb b/spec/unicorn/unicorn_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8518c047a470e573aafad5f185d3eaa931bd444e
--- /dev/null
+++ b/spec/unicorn/unicorn_spec.rb
@@ -0,0 +1,98 @@
+require 'fileutils'
+
+require 'excon'
+
+require 'spec_helper'
+
+describe 'Unicorn' do
+  before(:all) do
+    config_lines = File.read('config/unicorn.rb.example').split("\n")
+
+    # Remove these because they make setup harder.
+    config_lines = config_lines.reject do |line|
+      %w[
+        working_directory
+        worker_processes
+        listen
+        pid
+        stderr_path
+        stdout_path
+      ].any? { |prefix| line.start_with?(prefix) }
+    end
+
+    config_lines << "working_directory '#{Rails.root}'"
+
+    # We want to have exactly 1 worker process because that makes it
+    # predictable which process will handle our requests.
+    config_lines << 'worker_processes 1'
+
+    @socket_path = File.join(Dir.pwd, 'tmp/tests/unicorn.socket')
+    config_lines << "listen '#{@socket_path}'"
+
+    ready_file = 'tmp/tests/unicorn-worker-ready'
+    FileUtils.rm_f(ready_file)
+    after_fork_index = config_lines.index { |l| l.start_with?('after_fork') }
+    config_lines.insert(after_fork_index + 1, "File.write('#{ready_file}', Process.pid)")
+
+    config_path = 'tmp/tests/unicorn.rb'
+    File.write(config_path, config_lines.join("\n") + "\n")
+
+    cmd = %W[unicorn -E test -c #{config_path} #{Rails.root.join('config.ru')}]
+    @unicorn_master_pid = spawn(*cmd)
+    wait_unicorn_boot!(@unicorn_master_pid, ready_file)
+    WebMock.allow_net_connect!
+  end
+
+  %w[SIGQUIT SIGTERM SIGKILL].each do |signal|
+    it "has a worker that self-terminates on signal #{signal}" do
+      response = Excon.get('unix:///unicorn_test/pid', socket: @socket_path)
+      expect(response.status).to eq(200)
+
+      worker_pid = response.body.to_i
+      expect(worker_pid).to be > 0
+
+      begin
+        Excon.post('unix:///unicorn_test/kill', socket: @socket_path, body: "signal=#{signal}")
+      rescue Excon::Error::Socket
+        # The connection may be closed abruptly
+      end
+
+      expect(pid_gone?(worker_pid)).to eq(true)
+    end
+  end
+
+  after(:all) do
+    WebMock.disable_net_connect!(allow_localhost: true)
+    Process.kill('TERM', @unicorn_master_pid)
+  end
+
+  def wait_unicorn_boot!(master_pid, ready_file)
+    # Unicorn should boot in under 60 seconds so 120 seconds seems like a good timeout.
+    timeout = 120
+    timeout.times do
+      return if File.exist?(ready_file)
+      pid = Process.waitpid(master_pid, Process::WNOHANG)
+      raise "unicorn failed to boot: #{$?}" unless pid.nil?
+
+      sleep 1
+    end
+
+    raise "unicorn boot timed out after #{timeout} seconds"
+  end
+
+  def pid_gone?(pid)
+    # Worker termination should take less than a second. That makes 10
+    # seconds a generous timeout.
+    10.times do
+      begin
+        Process.kill(0, pid)
+      rescue Errno::ESRCH
+        return true
+      end
+
+      sleep 1
+    end
+
+    false
+  end
+end
diff --git a/spec/workers/pipeline_proccess_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
similarity index 100%
rename from spec/workers/pipeline_proccess_worker_spec.rb
rename to spec/workers/pipeline_process_worker_spec.rb