diff --git a/.eslintignore b/.eslintignore
index d9c2233c9d784085598b00d48593355adf9e2b56..93de4b10dfee9ebf31933d514ae3cb66e034ad32 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
+/coverage/
 /coverage-javascript/
 /public/
 /tmp/
diff --git a/.eslintrc b/.eslintrc
index 788a88487d8c8aa66bf56845f4a85500192807e0..b80dcec9d1d3265bfbd33e51780bb1fec792b91b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,15 +1,14 @@
 {
   "env": {
+    "jquery": true,
     "browser": true,
     "es6": true
   },
-  "extends": "airbnb",
+  "extends": "airbnb-base",
   "globals": {
-    "$": false,
     "_": false,
     "gl": false,
-    "gon": false,
-    "jQuery": false
+    "gon": false
   },
   "plugins": [
     "filenames"
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3c357c489f8f77b98a1915dbc76a579cd580ccf2..c7528029c89a6a9c1cc9dda2c967d572ce2356fe 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -229,7 +229,6 @@ rake ee_compat_check:
   <<: *exec
   only:
     - branches@gitlab-org/gitlab-ce
-    - branches@gitlab/gitlabhq
   except:
     - master
     - tags
diff --git a/.scss-lint.yml b/.scss-lint.yml
index aae8d9b6dbe023bf2522e8372a6198711db176f2..83c68309fa820ed30920f37b84803bc2267e18f9 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -30,7 +30,7 @@ linters:
   # variable declarations. They should be referred to via variables everywhere
   # else.
   ColorVariable:
-    enabled: false
+    enabled: true
   
   # Which form of comments to prefer in CSS.
   Comment:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 549336e4dffec590693efba4ff86a450e5e68b20..86b30d2832dc7242f01599a405c11401a43958c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,46 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 8.14.2 (2016-12-01)
+
+- Remove caching of events data. !6578
+- Rephrase some system notes to be compatible with new system note style. !7692
+- Pass tag SHA to post-receive hook when tag is created via UI. !7700
+- Prevent error when submitting a merge request and pipeline is not defined. !7707
+- Fixes system note style in commit discussion. !7721
+- Use a Redis lease for updating authorized projects. !7733
+- Refactor JiraService by moving code out of JiraService#execute method. !7756
+- Update GitLab Workhorse to v1.0.1. !7759
+- Fix pipelines info being hidden in merge request widget. !7808
+- Fixed commit timeago not rendering after initial page.
+- Fix for error thrown in cycle analytics events if build has not started.
+- Fixed issue boards issue sorting when dragging issue into list.
+- Allow access to the wiki with git when repository feature disabled.
+- Fixed timeago not rendering when resolving a discussion.
+- Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1.
+- Timeout creating and viewing merge request for binary file.
+- Gracefully recover from Redis connection failures in Sidekiq initializer.
+
+## 8.14.1 (2016-11-28)
+
+- Fix deselecting calendar days on contribution graph. !6453 (ClemMakesApps)
+- Update grape entity to 0.6.0. !7491
+- If Build running change accept merge request when build succeeds button from orange to blue. !7577
+- Changed import sources buttons to checkboxes. !7598 (Luke "Jared" Bennett)
+- Last minute CI Style tweaks for 8.14. !7643
+- Fix exceptions when loading build trace. !7658
+- Fix wrong template rendered when CI/CD settings aren't update successfully. !7665
+- fixes last_deployment call environment is nil. !7671
+- Sort builds by name within pipeline graph. !7681
+- Correctly determine mergeability of MR with no discussions.
+- Sidekiq stats in the admin area will now show correctly on different platforms. (blackst0ne)
+- Fixed issue boards dragging card removing random issues.
+- Fix information disclosure in `Projects::BlobController#update`.
+- Fix missing access checks on issue lookup using IssuableFinder.
+- Replace issue access checks with use of IssuableFinder.
+- Non members cannot create labels through the API.
+- Fix cycle analytics plan stage when commits are missing.
+
 ## 8.14.0 (2016-11-22)
 
 - Use separate email-token for incoming email and revert back the inactive feature. !5914
@@ -202,6 +242,15 @@ entry.
 - Fix "Without projects" filter. !6611 (Ben Bodenmiller)
 - Fix 404 when visit /projects page
 
+## 8.13.7 (2016-11-28)
+
+- fixes 500 error on project show when user is not logged in and project is still empty. !7376
+- Update grape entity to 0.6.0. !7491
+- Fix information disclosure in `Projects::BlobController#update`.
+- Fix missing access checks on issue lookup using IssuableFinder.
+- Replace issue access checks with use of IssuableFinder.
+- Non members cannot create labels through the API.
+
 ## 8.13.6 (2016-11-17)
 
 - Omniauth auto link LDAP user falls back to find by DN when user cannot be found by UID. !7002
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 3eefcb9dd5b38e2c1dc061052455dd97bcd51e6c..9084fa2f716a7117829f3f32a5f4cef400e02903 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-1.0.0
+1.1.0
diff --git a/Gemfile b/Gemfile
index 9e815925a1fd50516087b5b4a486878f0d45ab20..83edf420798f184172e0d5db2d3896d6dae845a2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -68,7 +68,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist'
 
 # API
 gem 'grape',        '~> 0.15.0'
-gem 'grape-entity', '~> 0.4.2'
+gem 'grape-entity', '~> 0.6.0'
 gem 'rack-cors',    '~> 0.4.0', require: 'rack/cors'
 
 # Pagination
@@ -85,10 +85,8 @@ gem 'dropzonejs-rails', '~> 0.7.1'
 
 # for backups
 gem 'fog-aws', '~> 0.9'
-gem 'fog-azure', '~> 0.0'
 gem 'fog-core', '~> 1.40'
 gem 'fog-local', '~> 0.3'
-gem 'fog-google', '~> 0.3'
 gem 'fog-openstack', '~> 0.1'
 gem 'fog-rackspace', '~> 0.1.1'
 
@@ -135,7 +133,7 @@ gem 'acts-as-taggable-on', '~> 4.0'
 
 # Background jobs
 gem 'sidekiq', '~> 4.2'
-gem 'sidekiq-cron', '~> 0.4.0'
+gem 'sidekiq-cron', '~> 0.4.4'
 gem 'redis-namespace', '~> 1.5.2'
 gem 'sidekiq-limit_fetch', '~> 3.4'
 
@@ -311,6 +309,8 @@ group :development, :test do
   gem 'knapsack', '~> 1.11.0'
 
   gem 'activerecord_sane_schema_dumper', '0.2'
+
+  gem 'stackprof', '~> 0.2.10'
 end
 
 group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index bdc6055248063828dd58e1ad1a624bb324520724..5a14ed6fedec6465e180e1c9aeaa717eb2bf2e51 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -66,21 +66,6 @@ GEM
       descendants_tracker (~> 0.0.4)
       ice_nine (~> 0.11.0)
       thread_safe (~> 0.3, >= 0.3.1)
-    azure (0.7.5)
-      addressable (~> 2.3)
-      azure-core (~> 0.1)
-      faraday (~> 0.9)
-      faraday_middleware (~> 0.10)
-      json (~> 1.8)
-      mime-types (>= 1, < 3.0)
-      nokogiri (~> 1.6)
-      systemu (~> 2.6)
-      thor (~> 0.19)
-      uuid (~> 2.0)
-    azure-core (0.1.2)
-      faraday (~> 0.9)
-      faraday_middleware (~> 0.10)
-      nokogiri (~> 1.6)
     babel-source (5.8.35)
     babel-transpiler (0.7.0)
       babel-source (>= 4.0, < 6)
@@ -217,19 +202,10 @@ GEM
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
       ipaddress (~> 0.8)
-    fog-azure (0.0.2)
-      azure (~> 0.6)
-      fog-core (~> 1.27)
-      fog-json (~> 1.0)
-      fog-xml (~> 0.1)
     fog-core (1.42.0)
       builder
       excon (~> 0.49)
       formatador (~> 0.2)
-    fog-google (0.3.2)
-      fog-core
-      fog-json
-      fog-xml
     fog-json (1.0.2)
       fog-core (~> 1.0)
       multi_json (~> 1.10)
@@ -316,7 +292,7 @@ GEM
       rack-accept
       rack-mount
       virtus (>= 1.0.0)
-    grape-entity (0.4.8)
+    grape-entity (0.6.0)
       activesupport
       multi_json (>= 1.3.2)
     haml (4.0.7)
@@ -397,8 +373,6 @@ GEM
       rb-inotify (>= 0.9)
     loofah (2.0.3)
       nokogiri (>= 1.5.9)
-    macaddr (1.7.1)
-      systemu (~> 2.6.2)
     mail (2.6.4)
       mime-types (>= 1.16, < 4)
     mail_room (0.9.0)
@@ -676,10 +650,10 @@ GEM
       connection_pool (~> 2.2, >= 2.2.0)
       rack-protection (~> 1.5)
       redis (~> 3.2, >= 3.2.1)
-    sidekiq-cron (0.4.0)
+    sidekiq-cron (0.4.4)
       redis-namespace (>= 1.5.2)
       rufus-scheduler (>= 2.0.24)
-      sidekiq (>= 4.0.0)
+      sidekiq (>= 4.2.1)
     sidekiq-limit_fetch (3.4.0)
       sidekiq (>= 4)
     simplecov (0.12.0)
@@ -717,6 +691,7 @@ GEM
       actionpack (>= 4.0)
       activesupport (>= 4.0)
       sprockets (>= 3.0.0)
+    stackprof (0.2.10)
     state_machines (0.4.0)
     state_machines-activemodel (0.4.0)
       activemodel (>= 4.1, < 5.1)
@@ -728,7 +703,6 @@ GEM
     sys-filesystem (1.1.6)
       ffi
     sysexits (1.2.0)
-    systemu (2.6.5)
     teaspoon (1.1.5)
       railties (>= 3.2.5, < 6)
     teaspoon-jasmine (2.2.0)
@@ -768,8 +742,6 @@ GEM
       get_process_mem (~> 0)
       unicorn (>= 4, < 6)
     uniform_notifier (1.10.0)
-    uuid (2.3.8)
-      macaddr (~> 1.0)
     version_sorter (2.1.0)
     virtus (1.0.5)
       axiom-types (~> 0.1)
@@ -849,9 +821,7 @@ DEPENDENCIES
   ffaker (~> 2.0.0)
   flay (~> 2.6.1)
   fog-aws (~> 0.9)
-  fog-azure (~> 0.0)
   fog-core (~> 1.40)
-  fog-google (~> 0.3)
   fog-local (~> 0.3)
   fog-openstack (~> 0.1)
   fog-rackspace (~> 0.1.1)
@@ -869,7 +839,7 @@ DEPENDENCIES
   gollum-rugged_adapter (~> 0.4.2)
   gon (~> 6.1.0)
   grape (~> 0.15.0)
-  grape-entity (~> 0.4.2)
+  grape-entity (~> 0.6.0)
   haml_lint (~> 0.18.2)
   hamlit (~> 2.6.1)
   health_check (~> 2.2.0)
@@ -956,7 +926,7 @@ DEPENDENCIES
   sham_rack (~> 1.3.6)
   shoulda-matchers (~> 2.8.0)
   sidekiq (~> 4.2)
-  sidekiq-cron (~> 0.4.0)
+  sidekiq-cron (~> 0.4.4)
   sidekiq-limit_fetch (~> 3.4)
   simplecov (= 0.12.0)
   slack-notifier (~> 1.2.0)
@@ -968,6 +938,7 @@ DEPENDENCIES
   spring-commands-teaspoon (~> 0.0.2)
   sprockets (~> 3.7.0)
   sprockets-es6 (~> 0.9.2)
+  stackprof (~> 0.2.10)
   state_machines-activerecord (~> 0.4.0)
   sys-filesystem (~> 1.1.6)
   teaspoon (~> 1.1.0)
diff --git a/README.md b/README.md
index f63543ca39d67b22028c735a7992dece5a8f6dab..61204630fd2b5ab07ee0da1e4fcc56f6fa426171 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # GitLab
 
 [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
-[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
+[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
 [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
 [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
 
@@ -84,7 +84,7 @@ For more information please see the [architecture documentation](https://docs.gi
 
 ## UX design
 
-Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code.
+Please adhere to the [UX Guide](doc/development/ux_guide/index.md) when creating designs and implementing code.
 
 ## Third-party applications
 
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt
deleted file mode 100644
index df187637e18d79f330914cec9b12c594670c79a7..0000000000000000000000000000000000000000
--- a/app/assets/fonts/OFL.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-
-This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded, 
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff b/app/assets/fonts/SourceSansPro-Black.ttf.woff
deleted file mode 100644
index b7e86200927ce40f4237bc02cfc0cf0c094e19e2..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Black.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2
deleted file mode 100644
index c90d078406c7ab07d454a933c76b61f62daf10b1..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff
deleted file mode 100644
index c3314b1ef06c16b5639801dec2029fec8763abd0..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2
deleted file mode 100644
index b87e22c41b5ef4fca17e91da8e40996b60cf25c9..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff b/app/assets/fonts/SourceSansPro-Bold.ttf.woff
deleted file mode 100644
index d1d40f840f8671981fa6930ddfa8510b3cf148c3..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Bold.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2
deleted file mode 100644
index 0f46f3e833af794f142f15af5dc4475c526b8d92..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff
deleted file mode 100644
index ef6ff514d3a9ea7ae3459d118dc6c9362ca143cb..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2
deleted file mode 100644
index 8007df6df327f2d025a2f8334ac7475b33592a48..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff
deleted file mode 100644
index 1e6c94d9eb38ff0a3eebd6c27d1949d222ec1368..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2
deleted file mode 100644
index b715f27408281e692bf11516adcff6073eb090a9..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff
deleted file mode 100644
index 7a408b1ec736851ef2867467750d00e49e588e79..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2
deleted file mode 100644
index d8f9d29d4aa2d56dc081c82a69bb7c82040a4744..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff b/app/assets/fonts/SourceSansPro-It.ttf.woff
deleted file mode 100644
index 4d54bc95718bc0c3ae67de2a28eea641739262ec..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-It.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2
deleted file mode 100644
index a00852641f807544be66f86b82a7cd9fe9019683..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-It.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff b/app/assets/fonts/SourceSansPro-Light.ttf.woff
deleted file mode 100644
index 1706d57d3c5ff9a5deb4fb3995200538925eb9ee..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Light.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2
deleted file mode 100644
index d8b610ad76eee1b15ef10d6f3593a75e732cc975..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff
deleted file mode 100644
index 87378d6c6094a8fa985117f77bee27abda3c745c..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2
deleted file mode 100644
index e0eebac8273f1c172e550b1e9e3b50cd6c49fdd8..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff b/app/assets/fonts/SourceSansPro-Regular.ttf.woff
deleted file mode 100644
index 460ab12a638f6e6aa81b0a6d0e6066f318515e66..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Regular.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2
deleted file mode 100644
index 0dd3464c74be96d2285b3910222aec5750cc57fd..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff
deleted file mode 100644
index 43379631b2da4cc061f2dbf5069740d3500a20f0..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2
deleted file mode 100644
index 2526d2e1b602bc40c347df3f78ce757a9b5adc0a..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff
deleted file mode 100644
index 232c2048ae7dcaaacba217843a450d31037097c0..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff and /dev/null differ
diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2
deleted file mode 100644
index 606935af0897f35259bb02ab8258e5de1e5525d3..0000000000000000000000000000000000000000
Binary files a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 and /dev/null differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 76f3c6506eddc2f166a9fb6ede7199f1bfac5faf..b7c4673c8e39f8a8dacc2a6e88ab15e0c71e9634 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -56,33 +56,18 @@
 /*= require es6-promise.auto */
 
 (function () {
-  document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
-  window.addEventListener('hashchange', gl.utils.shiftWindow);
-
-  // automatically adjust scroll position for hash urls taking the height of the navbar into account
-  // https://github.com/twitter/bootstrap/issues/1768
-  window.adjustScroll = function() {
-    var navbar = document.querySelector('.navbar-gitlab');
-    var subnav = document.querySelector('.layout-nav');
-    var fixedTabs = document.querySelector('.js-tabs-affix');
-
-    adjustment = 0;
-    if (navbar) adjustment -= navbar.offsetHeight;
-    if (subnav) adjustment -= subnav.offsetHeight;
-    if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
-
-    return scrollBy(0, adjustment);
-  };
+  document.addEventListener('page:fetch', function () {
+    // Unbind scroll events
+    $(document).off('scroll');
+    // Close any open tooltips
+    $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
+  });
 
-  window.addEventListener("hashchange", adjustScroll);
-
-  window.onload = function () {
-    // Scroll the window to avoid the topnav bar
-    // https://github.com/twitter/bootstrap/issues/1768
-    if (location.hash) {
-      return setTimeout(adjustScroll, 100);
-    }
-  };
+  window.addEventListener('hashchange', gl.utils.handleLocationHash);
+  window.addEventListener('load', function onLoad() {
+    window.removeEventListener('load', onLoad, false);
+    gl.utils.handleLocationHash();
+  }, false);
 
   $(function () {
     var $body = $('body');
@@ -97,7 +82,15 @@
     // Set the default path for all cookies to GitLab's root directory
     Cookies.defaults.path = gon.relative_url_root || '/';
 
-    gl.utils.preventDisabledButtons();
+    // prevent default action for disabled buttons
+    $('.btn').click(function(e) {
+      if ($(this).hasClass('disabled')) {
+        e.preventDefault();
+        e.stopImmediatePropagation();
+        return false;
+      }
+    });
+
     $('.nav-sidebar').niceScroll({
       cursoropacitymax: '0.4',
       cursorcolor: '#FFF',
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 8e91cbfac757033cadcbb86fe892bf44151f2f6e..43ebeef39c4898abc57788be94920cd965d00a28 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -80,6 +80,7 @@
     },
     mounted () {
       const options = gl.issueBoards.getBoardSortableDefaultOptions({
+        scroll: document.querySelectorAll('.boards-list')[0],
         group: 'issues',
         sort: false,
         disabled: this.disabled,
@@ -88,13 +89,13 @@
           const card = this.$refs.issue[e.oldIndex];
 
           card.showDetail = false;
-          Store.moving.issue = card.issue;
           Store.moving.list = card.list;
+          Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId);
 
           gl.issueBoards.onStart();
         },
         onAdd: (e) => {
-          gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
+          gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex);
 
           this.$nextTick(() => {
             e.item.remove();
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6
index d5cb6164e0bf3eee20d885ab71b49567a2cc0a9e..1644a7727378f369b57963d0233ec3c90e37c010 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js.es6
+++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6
@@ -47,7 +47,7 @@
       new gl.DueDateSelectors();
       new LabelsSelect();
       new Sidebar();
-      new Subscription('.subscription');
+      gl.Subscription.bindAll('.subscription');
     }
   });
 })();
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index 8a7dc67409ee07f5e5668a0e74981d97fca7182c..429bd27c3fbd3ae19561e087bdea6fe75b09c167 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -106,9 +106,13 @@ class List {
     });
   }
 
-  addIssue (issue, listFrom) {
+  addIssue (issue, listFrom, newIndex) {
     if (!this.findIssue(issue.id)) {
-      this.issues.push(issue);
+      if (newIndex !== undefined) {
+        this.issues.splice(newIndex, 0, issue);
+      } else {
+        this.issues.push(issue);
+      }
 
       if (this.label) {
         issue.addLabel(this.label);
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 6bc95aa60f241c7000c864ee790e6b07032e25ff..bb2a4de8b18b05306504b7172ce8d0ad1e21e73a 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -89,14 +89,14 @@
       });
       listFrom.update();
     },
-    moveIssueToList (listFrom, listTo, issue) {
+    moveIssueToList (listFrom, listTo, issue, newIndex) {
       const issueTo = listTo.findIssue(issue.id),
             issueLists = issue.getLists(),
             listLabels = issueLists.map( listIssue => listIssue.label );
 
       // Add to new lists issues if it doesn't already exist
       if (!issueTo) {
-        listTo.addIssue(issue, listFrom);
+        listTo.addIssue(issue, listFrom, newIndex);
       }
 
       if (listTo.type === 'done' && listFrom.type !== 'backlog') {
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 951fb338f9d41545bdb780fc261ca56535b4f94e..3627aaf5080e624650ec3157b346f4210f4c0f9a 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */
 (function() {
   this.CommitsList = (function() {
     function CommitsList() {}
@@ -13,7 +13,9 @@
           return false;
         }
       });
-      Pager.init(limit, false);
+      Pager.init(limit, false, false, function() {
+        gl.utils.localTimeAgo($('.js-timeago'));
+      });
       this.content = $("#commits-list");
       this.searchField = $("#commits-search");
       return this.initSearch();
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6
index b9675f50e312a6cba678d65b81145feea92385ca..0d85e1a46781b4b7bcf9e2231635243d7862dee9 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6
@@ -10,10 +10,15 @@
     },
     template: `
       <span class="total-time">
-        <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
-        <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
-        <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
-        <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
+        <template v-if="Object.keys(time).length">
+          <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
+          <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
+          <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
+          <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
+        </template>
+        <template v-else>
+          --
+        </template>
       </span>
     `,
   });
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
index 9b90587416745cb1dd0ad3fc0be7ed2f60f74f54..be732971c7f1467d7753750c791cf40093f62a50 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
@@ -62,9 +62,11 @@
       this.state.events = this.decorateEvents(events);
     },
     decorateEvents(events) {
-      const newEvents = events;
+      const newEvents = [];
+
+      events.forEach((item) => {
+        if (!item) return;
 
-      newEvents.forEach((item) => {
         item.totalTime = item.total_time;
         item.author.webUrl = item.author.web_url;
         item.author.avatarUrl = item.author.avatar_url;
@@ -79,6 +81,8 @@
         delete item.created_at;
         delete item.short_sha;
         delete item.commit_url;
+
+        newEvents.push(item);
       });
 
       return newEvents;
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6
index 439f55520ef6922f4d21d3235a997d4596a8b54e..badcdccc840138200076c8e561e6620616c534a4 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6
@@ -57,14 +57,17 @@ class DiscussionModel {
   }
 
   updateHeadline (data) {
-    const $discussionHeadline = $(`.discussion[data-discussion-id="${this.id}"] .js-discussion-headline`);
+    const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`;
+    const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`);
 
     if (data.discussion_headline_html) {
       if ($discussionHeadline.length) {
         $discussionHeadline.replaceWith(data.discussion_headline_html);
       } else {
-        $(`.discussion[data-discussion-id="${this.id}"] .discussion-header`).append(data.discussion_headline_html);
+        $(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
       }
+
+      gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
     } else {
        $discussionHeadline.remove();
     }
@@ -74,7 +77,7 @@ class DiscussionModel {
     if (!this.canResolve) {
       return false;
     }
-    
+
     for (const noteId in this.notes) {
       const note = this.notes[noteId];
 
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index c2d4670b7e9be191b36aafd951a4bf41867b780b..ab521c6c1fcd920b501068215480171a6659c016 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -135,8 +135,18 @@
             new TreeView();
           }
           break;
+        case 'projects:pipelines:builds':
         case 'projects:pipelines:show':
-          new gl.Pipelines();
+          const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
+
+          new gl.Pipelines({
+            initTabs: true,
+            tabsOptions: {
+              action: controllerAction,
+              defaultAction: 'pipelines',
+              parentEl: '.pipelines-tabs',
+            },
+          });
           break;
         case 'groups:activity':
           new gl.Activities();
@@ -208,6 +218,9 @@
           new gl.ProtectedBranchCreate();
           new gl.ProtectedBranchEditList();
           break;
+        case 'projects:variables:index':
+          new gl.ProjectVariables();
+          break;
       }
       switch (path.first()) {
         case 'admin':
@@ -259,7 +272,7 @@
               new NotificationsDropdown();
               break;
             case 'wikis':
-              new Wikis();
+              new gl.Wikis();
               shortcut_handler = new ShortcutsNavigation();
               new ZenMode();
               new GLForm($('.wiki-form'));
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index 35e183a9086ebe2533eeee307293e3e3ac0a933b..84faabf938afa1a2581f8107967bf5868e14cbfc 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -181,7 +181,7 @@
 
         <div class="environments-container">
           <div class="environments-list-loading text-center" v-if="isLoading">
-            <i class="fa fa-spinner spin"></i>
+            <i class="fa fa-spinner fa-spin"></i>
           </div>
 
           <div class="blank-state blank-state-no-icon"
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index 07f49cce3dc53ad70fcdd86a69b143b9d1df68f4..6ed14261fc30af5772abcc1a93cb3a83406114bb 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -23,6 +23,7 @@
 
   window.gl = window.gl || {};
   window.gl.environmentsList = window.gl.environmentsList || {};
+  window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line
 
   gl.environmentsList.EnvironmentItem = Vue.component('environment-item', {
 
@@ -147,15 +148,26 @@
           this.model.last_deployment.deployable;
       },
 
+      /**
+       * Verifies if the date to be shown is present.
+       *
+       * @returns {Boolean|Undefined}
+       */
+      canShowDate() {
+        return this.model.last_deployment &&
+          this.model.last_deployment.deployable &&
+          this.model.last_deployment.deployable !== undefined;
+      },
+
       /**
        * Human readable date.
        *
        * @returns {String}
        */
       createdDate() {
-        const timeagoInstance = new timeago(); // eslint-disable-line
-
-        return timeagoInstance.format(this.model.created_at);
+        return window.gl.environmentsList.timeagoInstance.format(
+          this.model.last_deployment.deployable.created_at,
+        );
       },
 
       /**
@@ -439,7 +451,7 @@
           <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component">
             <commit-component
               :tag="commitTag"
-              :ref="commitRef"
+              :commit_ref="commitRef"
               :commit_url="commitUrl"
               :short_sha="commitShortSha"
               :title="commitTitle"
@@ -453,7 +465,7 @@
 
         <td>
           <span
-            v-if="!isFolder && model.last_deployment"
+            v-if="!isFolder && canShowDate"
             class="environment-created-date-timeago">
             {{createdDate}}
           </span>
diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js
deleted file mode 100644
index fc6c130113ddd08ebe3c8b8cdd956b3c3ccc2aca..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/extensions/array.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */
-Array.prototype.first = function() {
-  return this[0];
-}
-
-Array.prototype.last = function() {
-  return this[this.length-1];
-}
diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..717566a47153a9797399497332154f04c71cb1cb
--- /dev/null
+++ b/app/assets/javascripts/extensions/array.js.es6
@@ -0,0 +1,24 @@
+/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */
+Array.prototype.first = function() {
+  return this[0];
+}
+
+Array.prototype.last = function() {
+  return this[this.length-1];
+}
+
+Array.prototype.find = Array.prototype.find || function(predicate, ...args) {
+  if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
+  if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
+
+  const list = Object(this);
+  const thisArg = args[1];
+  let value = {};
+
+  for (let i = 0; i < list.length; i += 1) {
+    value = list[i];
+    if (predicate.call(thisArg, value, i, list)) return value;
+  }
+
+  return undefined;
+};
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6
index 6d9b0c4bc3ecab8f58e5adf4286a27c882818262..3f12ad9ff9f1c933995e17514b39ae5191b1afa5 100644
--- a/app/assets/javascripts/extensions/element.js.es6
+++ b/app/assets/javascripts/extensions/element.js.es6
@@ -1,9 +1,20 @@
 /* global Element */
-/* eslint-disable consistent-return, max-len */
-
-Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
+/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */
 
 Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
   if (!selectedElement) return;
   return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
 };
+
+Element.prototype.matches = Element.prototype.matches ||
+  Element.prototype.matchesSelector ||
+  Element.prototype.mozMatchesSelector ||
+  Element.prototype.msMatchesSelector ||
+  Element.prototype.oMatchesSelector ||
+  Element.prototype.webkitMatchesSelector ||
+  function (s) {
+    const matches = (this.document || this.ownerDocument).querySelectorAll(s);
+    let i = matches.length;
+    while (--i >= 0 && matches.item(i) !== this) {}
+    return i > -1;
+  };
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 5d9ac4d350ace3a0a0d42c00884305eab3d8c8aa..6f9d62830710e00559cedbd248d612b6e2a4a411 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -5,6 +5,10 @@
     window.GitLab = {};
   }
 
+  function sanitize(str) {
+    return str.replace(/<(?:.|\n)*?>/gm, '');
+  }
+
   GitLab.GfmAutoComplete = {
     dataLoading: false,
     dataLoaded: false,
@@ -53,6 +57,27 @@
         } else {
           return value;
         }
+      },
+      matcher: function (flag, subtext) {
+        // The below is taken from At.js source
+        // Tweaked to commands to start without a space only if char before is a non-word character
+        // https://github.com/ichord/At.js
+        var _a, _y, regexp, match;
+        subtext = subtext.split(' ').pop();
+        flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+
+        _a = decodeURI("%C3%80");
+        _y = decodeURI("%C3%BF");
+
+        regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi');
+
+        match = regexp.exec(subtext);
+
+        if (match) {
+          return match[2] || match[1];
+        } else {
+          return null;
+        }
       }
     },
     setup: _.debounce(function(input) {
@@ -91,10 +116,12 @@
         })(this),
         insertTpl: ':${name}:',
         data: ['loading'],
+        startWithSpace: false,
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
-          beforeInsert: this.DefaultOptions.beforeInsert
+          beforeInsert: this.DefaultOptions.beforeInsert,
+          matcher: this.DefaultOptions.matcher
         }
       });
       // Team Members
@@ -112,11 +139,13 @@
         insertTpl: '${atwho-at}${username}',
         searchKey: 'search',
         data: ['loading'],
+        startWithSpace: false,
         alwaysHighlightFirst: true,
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
           beforeInsert: this.DefaultOptions.beforeInsert,
+          matcher: this.DefaultOptions.matcher,
           beforeSave: function(members) {
             return $.map(members, function(m) {
               let title = '';
@@ -135,8 +164,8 @@
               return {
                 username: m.username,
                 avatarTag: autoCompleteAvatar.length === 1 ?  txtAvatar : imgAvatar,
-                title: gl.utils.sanitize(title),
-                search: gl.utils.sanitize(m.username + " " + m.name)
+                title: sanitize(title),
+                search: sanitize(m.username + " " + m.name)
               };
             });
           }
@@ -157,10 +186,12 @@
         })(this),
         data: ['loading'],
         insertTpl: '${atwho-at}${id}',
+        startWithSpace: false,
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
           beforeInsert: this.DefaultOptions.beforeInsert,
+          matcher: this.DefaultOptions.matcher,
           beforeSave: function(issues) {
             return $.map(issues, function(i) {
               if (i.title == null) {
@@ -168,7 +199,7 @@
               }
               return {
                 id: i.iid,
-                title: gl.utils.sanitize(i.title),
+                title: sanitize(i.title),
                 search: i.iid + " " + i.title
               };
             });
@@ -190,7 +221,9 @@
         })(this),
         insertTpl: '${atwho-at}"${title}"',
         data: ['loading'],
+        startWithSpace: false,
         callbacks: {
+          matcher: this.DefaultOptions.matcher,
           sorter: this.DefaultOptions.sorter,
           beforeSave: function(milestones) {
             return $.map(milestones, function(m) {
@@ -199,7 +232,7 @@
               }
               return {
                 id: m.iid,
-                title: gl.utils.sanitize(m.title),
+                title: sanitize(m.title),
                 search: "" + m.title
               };
             });
@@ -220,11 +253,13 @@
           };
         })(this),
         data: ['loading'],
+        startWithSpace: false,
         insertTpl: '${atwho-at}${id}',
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
           beforeInsert: this.DefaultOptions.beforeInsert,
+          matcher: this.DefaultOptions.matcher,
           beforeSave: function(merges) {
             return $.map(merges, function(m) {
               if (m.title == null) {
@@ -232,7 +267,7 @@
               }
               return {
                 id: m.iid,
-                title: gl.utils.sanitize(m.title),
+                title: sanitize(m.title),
                 search: m.iid + " " + m.title
               };
             });
@@ -245,15 +280,17 @@
         searchKey: 'search',
         displayTpl: this.Labels.template,
         insertTpl: '${atwho-at}${title}',
+        startWithSpace: false,
         callbacks: {
+          matcher: this.DefaultOptions.matcher,
           sorter: this.DefaultOptions.sorter,
           beforeSave: function(merges) {
             var sanitizeLabelTitle;
             sanitizeLabelTitle = function(title) {
               if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
-                return "\"" + (gl.utils.sanitize(title)) + "\"";
+                return "\"" + (sanitize(title)) + "\"";
               } else {
-                return gl.utils.sanitize(title);
+                return sanitize(title);
               }
             };
             return $.map(merges, function(m) {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 812d5cde685b4e39ba80b6b23bbd116f66fed6a6..f334f35594da739e2fc476fd1d931b6c6657eb4e 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */
+/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */
 (function() {
   this.LabelsSelect = (function() {
     function LabelsSelect() {
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..e810ee85bd318e41c2a8ad881239d24dd9b804b4
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6
@@ -0,0 +1,113 @@
+/**
+ * Linked Tabs
+ *
+ * Handles persisting and restores the current tab selection and content.
+ * Reusable component for static content.
+ *
+ * ### Example Markup
+ *
+ *  <ul class="nav-links tab-links">
+ *    <li class="active">
+ *      <a data-action="tab1" data-target="#tab1" data-toggle="tab" href="/path/tab1">
+ *        Tab 1
+ *      </a>
+ *    </li>
+ *    <li class="groups-tab">
+ *      <a data-action="tab2" data-target="#tab2" data-toggle="tab" href="/path/tab2">
+ *        Tab 2
+ *      </a>
+ *    </li>
+ *
+ *
+ *  <div class="tab-content">
+ *    <div class="tab-pane" id="tab1">
+ *      Tab 1 Content
+ *    </div>
+ *    <div class="tab-pane" id="tab2">
+ *      Tab 2 Content
+ *    </div>
+ * </div>
+ *
+ *
+ * ### How to use
+ *
+ *  new window.gl.LinkedTabs({
+ *    action: "#{controller.action_name}",
+ *    defaultAction: 'tab1',
+ *    parentEl: '.tab-links'
+ *  });
+ */
+
+(() => {
+  window.gl = window.gl || {};
+
+  window.gl.LinkedTabs = class LinkedTabs {
+    /**
+     * Binds the events and activates de default tab.
+     *
+     * @param  {Object} options
+     */
+    constructor(options) {
+      this.options = options || {};
+
+      this.defaultAction = this.options.defaultAction;
+      this.action = this.options.action || this.defaultAction;
+
+      if (this.action === 'show') {
+        this.action = this.defaultAction;
+      }
+
+      this.currentLocation = window.location;
+
+      const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
+
+      // since this is a custom event we need jQuery :(
+      $(document)
+        .off('shown.bs.tab', tabSelector)
+        .on('shown.bs.tab', tabSelector, e => this.tabShown(e));
+
+      this.activateTab(this.action);
+    }
+
+    /**
+     * Handles the `shown.bs.tab` event to set the currect url action.
+     *
+     * @param  {type} evt
+     * @return {Function}
+     */
+    tabShown(evt) {
+      const source = evt.target.getAttribute('href');
+
+      return this.setCurrentAction(source);
+    }
+
+    /**
+     * Updates the URL with the path that matched the given action.
+     *
+     * @param  {String} source
+     * @return {String}
+     */
+    setCurrentAction(source) {
+      const copySource = source;
+
+      copySource.replace(/\/+$/, '');
+
+      const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
+
+      history.replaceState({
+        turbolinks: true,
+        url: newState,
+      }, document.title, newState);
+      return newState;
+    }
+
+    /**
+     * Given the current action activates the correct tab.
+     * http://getbootstrap.com/javascript/#tab-show
+     * Note: Will trigger `shown.bs.tab`
+     */
+    activateTab() {
+      return $(`${this.options.parentEl} a[data-action='${this.action}']`).tab('show');
+    }
+  };
+})();
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index d83c41fae9d7a664a2cf956012793b5f829216ef..29cba1a49dd0efee2756407651a560f3d4e3bf46 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */
 (function() {
   (function(w) {
     var base;
@@ -33,10 +33,6 @@
       });
     };
 
-    w.gl.utils.split = function(val) {
-      return val.split(/,\s*/);
-    };
-
     w.gl.utils.extractLast = function(term) {
       return this.split(term).pop();
     };
@@ -67,64 +63,39 @@
       });
     };
 
-    w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
-      var closest_submit, updateButtons;
-      closest_submit = form.find(button_selector);
-      updateButtons = function() {
-        var filled;
-        filled = true;
-        form.find('input').filter(form_selector).each(function() {
-          return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required');
-        });
-        if (filled) {
-          return closest_submit.enable();
-        } else {
-          return closest_submit.disable();
-        }
-      };
-      updateButtons();
-      return form.keyup(updateButtons);
-    };
-
-    w.gl.utils.sanitize = function(str) {
-      return str.replace(/<(?:.|\n)*?>/gm, '');
-    };
-
-    w.gl.utils.unbindEvents = function() {
-      return $(document).off('scroll');
-    };
+    // automatically adjust scroll position for hash urls taking the height of the navbar into account
+    // https://github.com/twitter/bootstrap/issues/1768
+    w.gl.utils.handleLocationHash = function() {
+      var hash = w.gl.utils.getLocationHash();
+      if (!hash) return;
 
-    w.gl.utils.shiftWindow = function() {
-      return w.scrollBy(0, -100);
-    };
+      var navbar = document.querySelector('.navbar-gitlab');
+      var subnav = document.querySelector('.layout-nav');
+      var fixedTabs = document.querySelector('.js-tabs-affix');
 
+      var adjustment = 0;
+      if (navbar) adjustment -= navbar.offsetHeight;
+      if (subnav) adjustment -= subnav.offsetHeight;
 
-    gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
-      return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
-    };
-    gl.utils.preventDisabledButtons = function() {
-      return $('.btn').click(function(e) {
-        if ($(this).hasClass('disabled')) {
-          e.preventDefault();
-          e.stopImmediatePropagation();
-          return false;
+      // scroll to user-generated markdown anchor if we cannot find a match
+      if (document.getElementById(hash) === null) {
+        var target = document.getElementById('user-content-' + hash);
+        if (target && target.scrollIntoView) {
+          target.scrollIntoView(true);
+          window.scrollBy(0, adjustment);
         }
-      });
+      } else {
+        // only adjust for fixedTabs when not targeting user-generated content
+        if (fixedTabs) {
+          adjustment -= fixedTabs.offsetHeight;
+        }
+        window.scrollBy(0, adjustment);
+      }
     };
+
     gl.utils.getPagePath = function() {
       return $('body').data('page').split(':')[0];
     };
-    gl.utils.parseUrl = function (url) {
-      var parser = document.createElement('a');
-      parser.href = url;
-      return parser;
-    };
-    gl.utils.cleanupBeforeFetch = function() {
-      // Unbind scroll events
-      $(document).off('scroll');
-      // Close any open tooltips
-      $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
-    };
 
     gl.utils.isMetaKey = function(e) {
       return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index b1928f8d279a459f3f5b9a138d08ff42915943c4..4a192ca5796f4ef7c4999ee721a9873ef6bf626f 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -220,7 +220,8 @@
 
       // We extract pathname for the current Changes tab anchor href
       // some pages like MergeRequestsController#new has query parameters on that anchor
-      var url = gl.utils.parseUrl(source);
+      var url = document.createElement('a');
+      url.href = source;
 
       return this._get({
         url: (url.pathname + ".json") + this._location.search,
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index a84c514dac75eb62078a157ad18135533496dd46..0ca0e2555951efe44b9c87c006a3f0cd98220de0 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */
+/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */
 
 /*= require autosave */
 /*= require autosize */
@@ -333,7 +333,7 @@
         gl.diffNotesCompileComponents();
       }
 
-      gl.utils.localTimeAgo($('.js-timeago', note_html), false);
+      gl.utils.localTimeAgo($('.js-timeago'), false);
       return this.updateNotesCount(1);
     };
 
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index a84db9c02334fd5e62f325598f7b4e1305c443dc..72c6c4a1fcd393847262ee7322a8cec5a9a109ab 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -1,8 +1,15 @@
+//= require lib/utils/bootstrap_linked_tabs
+
 /* eslint-disable */
 ((global) => {
 
   class Pipelines {
-    constructor() {
+    constructor(options) {
+
+      if (options.initTabs && options.tabsOptions) {
+        new global.LinkedTabs(options.tabsOptions);
+      }
+
       this.addMarginToBuildColumns();
     }
 
diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..4ee2e49306dba4039c0e477327eab367b30f5e14
--- /dev/null
+++ b/app/assets/javascripts/project_variables.js.es6
@@ -0,0 +1,43 @@
+(() => {
+  const HIDDEN_VALUE_TEXT = '******';
+
+  class ProjectVariables {
+    constructor() {
+      this.$revealBtn = $('.js-btn-toggle-reveal-values');
+      this.$revealBtn.on('click', this.toggleRevealState.bind(this));
+    }
+
+    toggleRevealState(e) {
+      e.preventDefault();
+
+      const oldStatus = this.$revealBtn.attr('data-status');
+      let newStatus = 'hidden';
+      let newAction = 'Reveal Values';
+
+      if (oldStatus === 'hidden') {
+        newStatus = 'revealed';
+        newAction = 'Hide Values';
+      }
+
+      this.$revealBtn.attr('data-status', newStatus);
+
+      const $variables = $('.variable-value');
+
+      $variables.each((_, variable) => {
+        const $variable = $(variable);
+        let newText = HIDDEN_VALUE_TEXT;
+
+        if (newStatus === 'revealed') {
+          newText = $variable.attr('data-value');
+        }
+
+        $variable.text(newText);
+      });
+
+      this.$revealBtn.text(newAction);
+    }
+  }
+
+  window.gl = window.gl || {};
+  window.gl.ProjectVariables = ProjectVariables;
+})();
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
deleted file mode 100644
index 6d75688deeb9e6249c762d8cfb7b1f9ad9e04593..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/subscription.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, one-var, one-var-declaration-per-line, camelcase, consistent-return, no-undef, padded-blocks, max-len */
-(function() {
-  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
-  this.Subscription = (function() {
-    function Subscription(container) {
-      this.toggleSubscription = bind(this.toggleSubscription, this);
-      var $container;
-      this.$container = $(container);
-      this.url = this.$container.attr('data-url');
-      this.subscribe_button = this.$container.find('.js-subscribe-button');
-      this.subscription_status = this.$container.find('.subscription-status');
-      this.subscribe_button.unbind('click').click(this.toggleSubscription);
-    }
-
-    Subscription.prototype.toggleSubscription = function(event) {
-      var action, btn, current_status;
-      btn = $(event.currentTarget);
-      action = btn.find('span').text();
-      current_status = this.subscription_status.attr('data-status');
-      btn.addClass('disabled');
-
-      if ($('html').hasClass('issue-boards-page')) {
-        this.url = this.$container.attr('data-url');
-      }
-
-      return $.post(this.url, (function(_this) {
-        return function() {
-          var status;
-          btn.removeClass('disabled');
-
-          if ($('html').hasClass('issue-boards-page')) {
-            Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed);
-          } else {
-            status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
-            _this.subscription_status.attr('data-status', status);
-            action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
-            btn.find('span').text(action);
-            _this.subscription_status.find('>div').toggleClass('hidden');
-            if (btn.attr('data-original-title')) {
-              return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
-            }
-          }
-        };
-      })(this));
-    };
-
-    return Subscription;
-
-  })();
-
-}).call(this);
diff --git a/app/assets/javascripts/subscription.js.es6 b/app/assets/javascripts/subscription.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..62d1604fe9eaabbe42747fa82eae1dea63f6ac48
--- /dev/null
+++ b/app/assets/javascripts/subscription.js.es6
@@ -0,0 +1,50 @@
+/* global Vue */
+
+(() => {
+  class Subscription {
+    constructor(containerElm) {
+      this.containerElm = containerElm;
+
+      const subscribeButton = containerElm.querySelector('.js-subscribe-button');
+      if (subscribeButton) {
+        // remove class so we don't bind twice
+        subscribeButton.classList.remove('js-subscribe-button');
+        subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
+      }
+    }
+
+    toggleSubscription(event) {
+      const button = event.currentTarget;
+      const buttonSpan = button.querySelector('span');
+      if (!buttonSpan || button.classList.contains('disabled')) {
+        return;
+      }
+      button.classList.add('disabled');
+
+      const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
+      const toggleActionUrl = this.containerElm.dataset.url;
+
+      $.post(toggleActionUrl, () => {
+        button.classList.remove('disabled');
+
+        // hack to allow this to work with the issue boards Vue object
+        if (document.querySelector('html').classList.contains('issue-boards-page')) {
+          Vue.set(
+            gl.issueBoards.BoardsStore.detail.issue,
+            'subscribed',
+            !gl.issueBoards.BoardsStore.detail.issue.subscribed,
+          );
+        } else {
+          buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
+        }
+      });
+    }
+
+    static bindAll(selector) {
+      [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
+    }
+  }
+
+  window.gl = window.gl || {};
+  window.gl.Subscription = Subscription;
+})();
diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6
index 1bc68c1ba2f210a7d4e44ae425d91937ba8078bf..2ef2959cbf46ef09f7debff0c1bd65c70c95168d 100644
--- a/app/assets/javascripts/vue_common_component/commit.js.es6
+++ b/app/assets/javascripts/vue_common_component/commit.js.es6
@@ -23,7 +23,7 @@
        * name
        * ref_url
        */
-      ref: {
+      commit_ref: {
         type: Object,
         required: false,
         default: () => ({}),
@@ -79,8 +79,8 @@
        *
        * @returns {Boolean}
        */
-      hasRef() {
-        return this.ref && this.ref.name && this.ref.ref_url;
+      hasCommitRef() {
+        return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url;
       },
 
       /**
@@ -131,15 +131,15 @@
     template: `
       <div class="branch-commit">
 
-        <div v-if="hasRef" class="icon-container">
+        <div v-if="hasCommitRef" class="icon-container">
           <i v-if="tag" class="fa fa-tag"></i>
           <i v-if="!tag" class="fa fa-code-fork"></i>
         </div>
 
-        <a v-if="hasRef"
+        <a v-if="hasCommitRef"
           class="monospace branch-name"
-          :href="ref.ref_url">
-          {{ref.name}}
+          :href="commit_ref.ref_url">
+          {{commit_ref.name}}
         </a>
 
         <div class="icon-container commit-icon commit-icon-container"></div>
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
deleted file mode 100644
index 5dd853389c2d09df73090f97201304ee57b01c74..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/wikis.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, consistent-return, one-var, one-var-declaration-per-line, no-undef, prefer-template, padded-blocks, max-len */
-
-/*= require latinise */
-
-(function() {
-  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
-  this.Wikis = (function() {
-    function Wikis() {
-      this.slugify = bind(this.slugify, this);
-      $('.new-wiki-page').on('submit', (function(_this) {
-        return function(e) {
-          var field, path, slug;
-          $('[data-error~=slug]').addClass('hidden');
-          field = $('#new_wiki_path');
-          slug = _this.slugify(field.val());
-          if (slug.length > 0) {
-            path = field.attr('data-wikis-path');
-            location.href = path + '/' + slug;
-            return e.preventDefault();
-          }
-        };
-      })(this));
-    }
-
-    Wikis.prototype.dasherize = function(value) {
-      return value.replace(/[_\s]+/g, '-');
-    };
-
-    Wikis.prototype.slugify = function(value) {
-      return this.dasherize(value.trim().toLowerCase().latinise());
-    };
-
-    return Wikis;
-
-  })();
-
-}).call(this);
diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..ecff5fd5bf4a302b6cf37b26ec6af90785f48f22
--- /dev/null
+++ b/app/assets/javascripts/wikis.js.es6
@@ -0,0 +1,73 @@
+/* eslint-disable no-param-reassign */
+/* global Breakpoints */
+
+/*= require latinise */
+/*= require breakpoints */
+/*= require jquery.nicescroll */
+
+((global) => {
+  const dasherize = str => str.replace(/[_\s]+/g, '-');
+  const slugify = str => dasherize(str.trim().toLowerCase().latinise());
+
+  class Wikis {
+    constructor() {
+      this.bp = Breakpoints.get();
+      this.sidebarEl = document.querySelector('.js-wiki-sidebar');
+      this.sidebarExpanded = false;
+      $(this.sidebarEl).niceScroll();
+
+      const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
+      for (let i = 0; i < sidebarToggles.length; i += 1) {
+        sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
+      }
+
+      this.newWikiForm = document.querySelector('form.new-wiki-page');
+      if (this.newWikiForm) {
+        this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
+      }
+
+      window.addEventListener('resize', () => this.renderSidebar());
+      this.renderSidebar();
+    }
+
+    handleNewWikiSubmit(e) {
+      if (!this.newWikiForm) return;
+
+      const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
+      const slug = slugify(slugInput.value);
+
+      if (slug.length > 0) {
+        const wikisPath = slugInput.getAttribute('data-wikis-path');
+        window.location.href = `${wikisPath}/${slug}`;
+        e.preventDefault();
+      }
+    }
+
+    handleToggleSidebar(e) {
+      e.preventDefault();
+      this.sidebarExpanded = !this.sidebarExpanded;
+      this.renderSidebar();
+    }
+
+    sidebarCanCollapse() {
+      const bootstrapBreakpoint = this.bp.getBreakpointSize();
+      return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+    }
+
+    renderSidebar() {
+      if (!this.sidebarEl) return;
+      const { classList } = this.sidebarEl;
+      if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+        if (!classList.contains('right-sidebar-expanded')) {
+          classList.remove('right-sidebar-collapsed');
+          classList.add('right-sidebar-expanded');
+        }
+      } else if (classList.contains('right-sidebar-expanded')) {
+        classList.add('right-sidebar-collapsed');
+        classList.remove('right-sidebar-expanded');
+      }
+    }
+  }
+
+  global.Wikis = Wikis;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 7c7f991dd870864cdba8acf576c5675f0ab15687..4aaff7d04f16cc7a444033229e1c15182cb3eba3 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,4 +1,3 @@
-@import "framework/fonts";
 @import "framework/variables";
 @import "framework/mixins";
 @import 'framework/tw_bootstrap_variables';
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index ad0d387067fc17dbc5ae0881530450bee6afe59d..000e591e09c8f62a3653f1b9354d0e4e442a05a1 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -8,7 +8,7 @@
   float: left;
   margin-right: 15px;
   border-radius: $avatar_radius;
-  border: 1px solid rgba(0, 0, 0, .1);
+  border: 1px solid $avatar-border;
   &.s16 { @include avatar-size(16px, 6px); }
   &.s20 { @include avatar-size(20px, 7px); }
   &.s24 { @include avatar-size(24px, 8px); }
@@ -80,6 +80,7 @@
     border-radius: 0;
     border: none;
     height: auto;
+    width: 100%;
     margin: 0;
     align-self: center;
   }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 77ae9e9a6e7ddb76aecf0e478c02ff3e84612751..57db5eaa2b351ffd92dc8e458113ad26b0e07a4e 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -41,7 +41,7 @@
   }
 
   &.white {
-    background-color: white;
+    background-color: $white-light;
   }
 
   &.top-block {
@@ -158,7 +158,7 @@
 
     p {
       padding: 0 $gl-padding;
-      color: #5c5d5e;
+      color: $gl-text-color-dark;
     }
   }
 
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 4a9aa0f8717729c4c155aeecf1c8b1db5131647c..8da3da2ad08b11c08d1e11437330771551f800ec 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -15,7 +15,7 @@
   @include btn-default;
 }
 
-@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) {
+@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) {
   background-color: $background;
   color: $text;
   border-color: $border;
@@ -23,8 +23,14 @@
   &:hover,
   &:focus {
     background-color: $hover-background;
-    color: $hover-text;
     border-color: $hover-border;
+    color: $hover-text;
+  }
+
+  &:active {
+    background-color: $active-background;
+    border-color: $active-border;
+    color: $hover-text;
   }
 }
 
@@ -62,31 +68,31 @@
 }
 
 @mixin btn-green {
-  @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #fff);
+  @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, $white-light);
 }
 
 @mixin btn-blue {
-  @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #fff);
+  @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, $white-light);
 }
 
 @mixin btn-blue-medium {
-  @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #fff);
+  @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, $white-light);
 }
 
 @mixin btn-orange {
-  @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #fff);
+  @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, $white-light);
 }
 
 @mixin btn-red {
-  @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #fff);
+  @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, $white-light);
 }
 
 @mixin btn-gray {
-  @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark);
+  @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark);
 }
 
 @mixin btn-white {
-  @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
+  @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $gl-text-color);
 }
 
 @mixin btn-with-margin {
@@ -139,11 +145,11 @@
     &.btn-new,
     &.btn-create,
     &.btn-save {
-      @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light);
+      @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal);
     }
 
     &.btn-remove {
-      @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
+      @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal);
     }
   }
 
@@ -165,11 +171,11 @@
   }
 
   &.btn-close {
-    @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
+    @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal);
   }
 
   &.btn-spam {
-    @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
+    @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal);
   }
 
   &.btn-danger,
@@ -199,7 +205,7 @@
   }
 
   .fa-caret-down,
-  .fa-caret-up {
+  .fa-chevron-down {
     margin-left: 5px;
   }
 
@@ -283,8 +289,8 @@
   .active {
     box-shadow: $gl-btn-active-background;
 
-    border: 1px solid #c6cacf !important;
-    background-color: #e4e7ed !important;
+    border: 1px solid $border-white-dark !important;
+    background-color: $btn-active-gray-light !important;
   }
 }
 
@@ -339,19 +345,19 @@
 
 .btn-static {
   background-color: $background-color !important;
-  border: 1px solid lightgrey;
+  border: 1px solid $border-gray-light;
   cursor: default;
 
   &:active {
-    -moz-box-shadow: inset 0 0 0 white;
-    -webkit-box-shadow: inset 0 0 0 white;
-    box-shadow: inset 0 0 0 white;
+    -moz-box-shadow: inset 0 0 0 $white-light;
+    -webkit-box-shadow: inset 0 0 0 $white-light;
+    box-shadow: inset 0 0 0 $white-light;
   }
 }
 
 .btn-inverted {
   &-secondary {
-    @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light);
+    @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal);
   }
 }
 
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 8642b7530e25ca386058cb2a585700a092394d5d..ef921a8c6a94953c5b01eb65d323b56993bf6443 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -2,7 +2,7 @@
   padding-left: 0;
   padding-right: 0;
 
-  @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
+  @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
     overflow-x: scroll;
   }
 }
@@ -28,13 +28,13 @@
 .user-contrib-cell {
   &:hover {
     cursor: pointer;
-    stroke: #000;
+    stroke: $black;
   }
 }
 
 .user-contrib-text {
   font-size: 12px;
-  fill: #959494;
+  fill: $calendar-user-contrib-text;
 }
 
 .calendar-hint {
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index f3b6ad88ad6f7bf084fcfa04efefe10168c465a3..2a100980aca8a169f1a3344cbdddee8e862fa788 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -25,25 +25,25 @@
 
 /* Variations */
 .bs-callout-danger {
-  background-color: #fdf7f7;
-  border-color: #eed3d7;
-  color: #b94a48;
+  background-color: $callout-danger-bg;
+  border-color: $callout-danger-border;
+  color: $callout-danger-color;
 }
 
 .bs-callout-warning {
-  background-color: #faf8f0;
-  border-color: #faebcc;
-  color: #8a6d3b;
+  background-color: $callout-warning-bg;
+  border-color: $callout-warning-border;
+  color: $callout-warning-color;
 }
 
 .bs-callout-info {
-  background-color: #f4f8fa;
-  border-color: #bce8f1;
-  color: #34789a;
+  background-color: $callout-info-bg;
+  border-color: $callout-info-border;
+  color: $callout-info-color;
 }
 
 .bs-callout-success {
-  background-color: #dff0d8;
-  border-color: #5ca64d;
-  color: #3c763d;
+  background-color: $callout-success-bg;
+  border-color: $callout-success-border;
+  color: $callout-success-color;
 }
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index b24fce6f0c21facebbba58c4055505d12b06c5ca..cdeef6fcc9ed1f352b99c779e0dc912e5dc05c30 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -1,9 +1,9 @@
 /** COLORS **/
-.cgray { color: $gl-gray; }
-.clgray { color: #bbb; }
-.cred { color: $gl-text-red; }
-.cgreen { color: $gl-text-green; }
-.cdark { color: #444; }
+.cgray { color: $common-gray; }
+.clgray { color: $common-gray-light; }
+.cred { color: $common-red; }
+.cgreen { color: $common-green; }
+.cdark { color: $common-gray-dark; }
 
 /** COMMON CLASSES **/
 .prepend-top-0 { margin-top: 0; }
@@ -28,11 +28,11 @@
 .center { text-align: center; }
 
 .underlined-link { text-decoration: underline; }
-.hint { font-style: italic; color: #999; }
-.light { color: $gl-gray; }
+.hint { font-style: italic; color: $hint-color; }
+.light { color: $common-gray; }
 
 .slead {
-  color: $gl-gray;
+  color: $common-gray;
   font-size: 15px;
   margin-bottom: 12px;
   font-weight: normal;
@@ -52,10 +52,10 @@ pre {
   }
 
   &.well-pre {
-    border: 1px solid #eee;
+    border: 1px solid $well-pre-bg;
     background: $gray-light;
     border-radius: 0;
-    color: #555;
+    color: $well-pre-color;
   }
 }
 
@@ -87,14 +87,14 @@ table a code {
 .loading {
   margin: 20px auto;
   height: 40px;
-  color: #555;
+  color: $loading-color;
   font-size: 32px;
   text-align: center;
 }
 
 span.update-author {
   display: block;
-  color: #999;
+  color: $update-author-color;
   font-weight: normal;
   font-style: italic;
 
@@ -105,7 +105,7 @@ span.update-author {
 }
 
 .user-mention {
-  color: #2fa0bb;
+  color: $user-mention-color;
   font-weight: bold;
 }
 
@@ -114,7 +114,7 @@ span.update-author {
 }
 
 p.time {
-  color: #999;
+  color: $time-color;
   font-size: 90%;
   margin: 30px 3px 3px 2px;
 }
@@ -150,7 +150,7 @@ li.note {
 
 .project_member_show {
   td:first-child {
-    color: #aaa;
+    color: $project-member-show-color;
   }
 }
 
@@ -176,7 +176,7 @@ li.note {
   margin-top: 40px;
 
   pre {
-    background: white;
+    background: $white-light;
     border: none;
     font-size: 12px;
   }
@@ -184,12 +184,12 @@ li.note {
 
 .error-message {
   padding: 10px;
-  background: #c67;
+  background: $error-bg;
   margin: 0;
-  color: #fff;
+  color: $white-light;
 
   a {
-    color: #fff;
+    color: $white-light;
     text-decoration: underline;
   }
 }
@@ -197,22 +197,22 @@ li.note {
 .browser-alert {
   padding: 10px;
   text-align: center;
-  background: #c67;
-  color: #fff;
+  background: $error-bg;
+  color: $white-light;
   font-weight: bold;
 
   a {
-    color: #fff;
+    color: $white-light;
     text-decoration: underline;
   }
 }
 
 .warning_message {
-  border-left: 4px solid #ed9;
-  color: #b90;
+  border-left: 4px solid $warning-message-border;
+  color: $warning-message-color;
   padding: 10px;
   margin-bottom: 10px;
-  background: #ffffe6;
+  background: $warning-message-bg;
   padding-left: 20px;
 
   &.centered {
@@ -222,7 +222,7 @@ li.note {
 
 .gitlab-promo {
   a {
-    color: #aaa;
+    color: $gl-promo-color;
     margin-right: 30px;
   }
 }
@@ -245,7 +245,7 @@ li.note {
         position: relative;
         top: 2px;
         left: 5px;
-        color: #666;
+        color: $control-group-descr-color;
       }
     }
   }
@@ -270,7 +270,7 @@ img.emoji {
 
 table {
   td.permission-x {
-    background: #d9edf7 !important;
+    background: $table-permission-x-bg !important;
     text-align: center;
   }
 }
@@ -323,13 +323,13 @@ table {
 
   .username {
     font-size: 18px;
-    color: #666;
+    color: $username-color;
     margin-top: 8px;
   }
 
   .description {
     font-size: $gl-font-size;
-    color: #666;
+    color: $description-color;
     margin-top: 8px;
   }
 }
@@ -339,7 +339,7 @@ table {
 
   .profiler-button,
   .profiler-controls {
-    border-color: #eee !important;
+    border-color: $profiler-border !important;
   }
 }
 
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 583c17e4a839e515a55c7fe6ebd0c8c46f240715..e6229a35b8822210d65779781bb9b90571439d9b 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -8,6 +8,12 @@
   }
 }
 
+@mixin chevron-active {
+  .fa-chevron-down {
+    color: $dropdown-toggle-hover-icon-color;
+  }
+}
+
 .open {
   .dropdown-menu,
   .dropdown-menu-nav {
@@ -19,53 +25,27 @@
     }
   }
 
+  .dropdown-toggle,
   .dropdown-menu-toggle {
+    @include chevron-active;
     border-color: $dropdown-toggle-hover-border-color;
-
-    .fa {
-      color: $dropdown-toggle-hover-icon-color;
-    }
   }
 }
 
-.dropdown-menu-toggle {
-  position: relative;
-  width: 160px;
-  padding: 6px 20px 6px 10px;
+.dropdown-toggle {
+  padding: 6px 8px 6px 10px;
   background-color: $dropdown-toggle-bg;
   color: $dropdown-toggle-color;
   font-size: 15px;
   text-align: left;
   border: 1px solid $border-color;
   border-radius: $border-radius-base;
-  text-overflow: ellipsis;
   white-space: nowrap;
-  overflow: hidden;
-
-  .fa {
-    position: absolute;
-    top: 10px;
-    right: 8px;
-    color: $dropdown-toggle-icon-color;
-
-    &.fa-spinner {
-      font-size: 16px;
-      margin-top: -8px;
-    }
-  }
 
   &.no-outline {
     outline: 0;
   }
 
-  &:hover, {
-    border-color: $dropdown-toggle-hover-border-color;
-
-    .fa {
-      color: $dropdown-toggle-hover-icon-color;
-    }
-  }
-
   &.large {
     width: 200px;
   }
@@ -86,6 +66,51 @@
     max-width: 100%;
     padding-right: 25px;
   }
+
+  .fa {
+    color: $dropdown-toggle-icon-color;
+  }
+
+  .fa-chevron-down {
+    font-size: $dropdown-chevron-size;
+    position: relative;
+    top: -3px;
+    margin-left: 5px;
+  }
+
+  &:hover {
+    @include chevron-active;
+    border-color: $dropdown-toggle-hover-border-color;
+  }
+
+  &:focus:active {
+    @include chevron-active;
+    border-color: $dropdown-toggle-active-border-color;
+  }
+}
+
+.dropdown-menu-toggle {
+  @extend .dropdown-toggle;
+  padding-right: 20px;
+  position: relative;
+  width: 160px;
+  text-overflow: ellipsis;
+  overflow: hidden;
+
+  .fa {
+    position: absolute;
+
+    &.fa-spinner {
+      font-size: 16px;
+      margin-top: -8px;
+    }
+  }
+
+  .fa-chevron-down {
+    position: absolute;
+    top: 11px;
+    right: 8px;
+  }
 }
 
 .dropdown-menu,
@@ -351,7 +376,7 @@
     position: absolute;
     top: 10px;
     right: 20px;
-    color: #c7c7c7;
+    color: $dropdown-input-fa-color;
     font-size: 12px;
     pointer-events: none;
   }
@@ -504,7 +529,7 @@
     .ui-datepicker-calendar {
       .ui-state-hover,
       .ui-state-active {
-        color: #fff;
+        color: $white-light;
         border: 0;
       }
     }
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index f49d7b92a00923d8f3bedf9f2ec03df6e6ff842e..ab0b81f77f7569f3614a15489ce5fbc6b51abe0f 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -59,10 +59,10 @@
   }
 
   .file-content {
-    background: #fff;
+    background: $white-light;
 
     &.image_file {
-      background: #eee;
+      background: $file-image-bg;
       text-align: center;
 
       img {
@@ -84,8 +84,8 @@
     }
 
     &.blob-no-preview {
-      background: #eee;
-      text-shadow: 0 1px 2px #fff;
+      background: $blob-bg;
+      text-shadow: 0 1px 2px $white-light;
       padding: 100px 0;
     }
 
@@ -99,7 +99,7 @@
       }
 
       tr {
-        border-bottom: 1px solid #eee;
+        border-bottom: 1px solid $blame-border;
       }
 
       td {
@@ -120,7 +120,7 @@
 
       td.line-numbers {
         float: none;
-        border-left: 1px solid #ddd;
+        border-left: 1px solid $blame-line-numbers-border;
 
         i {
           float: none;
@@ -134,7 +134,7 @@
     }
 
     &.logs {
-      background: #eee;
+      background: $logs-bg;
       max-height: 700px;
       overflow-y: auto;
 
@@ -143,14 +143,14 @@
         padding: 10px 0;
         border-left: 1px solid $border-color;
         margin-bottom: 0;
-        background: white;
+        background: $white-light;
 
         li {
-          color: #888;
+          color: $logs-li-color;
 
           p {
             margin: 0;
-            color: #333;
+            color: $logs-p-color;
             line-height: 24px;
             padding-left: 10px;
           }
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index a9006de6d3ee4c36a13b9b6d692120b7a5ca4a7b..eadb9409fee692d45d7967ceaf6b1175e173d34e 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -38,7 +38,7 @@
   }
 }
 
-@media (max-width: $screen-md-min) {
+@media (max-width: $screen-sm-max) {
   ul.notes {
     .flash-container.timeline-content {
       margin-left: 0;
diff --git a/app/assets/stylesheets/framework/fonts.scss b/app/assets/stylesheets/framework/fonts.scss
deleted file mode 100644
index 5f9685bc71a05cbf32e78f98d9a288264455686c..0000000000000000000000000000000000000000
--- a/app/assets/stylesheets/framework/fonts.scss
+++ /dev/null
@@ -1,45 +0,0 @@
-// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like
-// the way the `src` property is formatted in this file.
-// scss-lint:disable SpaceAfterPropertyColon
-
-/* latin-ext */
-@font-face {
-  font-family: 'Source Sans Pro';
-  font-style: normal;
-  font-weight: 300;
-  src:
-    local('Source Sans Pro Light'),
-    local('SourceSansPro-Light'),
-    font-url('SourceSansPro-Light.ttf.woff2') format('woff2'),
-    font-url('SourceSansPro-Light.ttf.woff') format('woff');
-}
-@font-face {
-  font-family: 'Source Sans Pro';
-  font-style: normal;
-  font-weight: 400;
-  src:
-    local('Source Sans Pro'),
-    local('SourceSansPro-Regular'),
-    font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'),
-    font-url('SourceSansPro-Regular.ttf.woff') format('woff');
-}
-@font-face {
-  font-family: 'Source Sans Pro';
-  font-style: normal;
-  font-weight: 600;
-  src:
-    local('Source Sans Pro Semibold'),
-    local('SourceSansPro-Semibold'),
-    font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'),
-    font-url('SourceSansPro-Semibold.ttf.woff') format('woff');
-}
-@font-face {
-  font-family: 'Source Sans Pro';
-  font-style: normal;
-  font-weight: 700;
-  src:
-    local('Source Sans Pro Bold'),
-    local('SourceSansPro-Bold'),
-    font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'),
-    font-url('SourceSansPro-Bold.ttf.woff') format('woff');
-}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index e83a1f7ad687256323924acf1abad4bcde57dc68..25a2b38baaadf19bd244beee5c05f7b34aeeff20 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -7,9 +7,9 @@ input {
 }
 
 input[type='text'].danger {
-  background: #f2dede!important;
-  border-color: #d66;
-  text-shadow: 0 1px 1px #fff;
+  background: $input-danger-bg !important;
+  border-color: $input-danger-border;
+  text-shadow: 0 1px 1px $white-light;
 }
 
 .datetime-controls {
@@ -98,7 +98,7 @@ label {
     }
   }
 
-  @media(max-width: $screen-sm-min) {
+  @media(max-width: $screen-xs-max) {
     padding: 0 $gl-padding;
 
     .control-label,
@@ -159,7 +159,7 @@ label {
   }
 
   .input-group-addon {
-    background-color: #f7f8fa;
+    background-color: $input-group-addon-bg;
   }
 
   .input-group-addon:not(:first-child):not(:last-child) {
@@ -181,7 +181,7 @@ label {
     border: 1px solid $green-normal;
 
     &:focus {
-      box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
+      box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-normal;
       border: 0 none;
     }
   }
@@ -190,7 +190,7 @@ label {
     border: 1px solid $red-normal;
 
     &:focus {
-      box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
+      box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error;
       border: 0 none;
     }
   }
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 91ab15034395312c179c0f5e79bd0a1680900547..5cd242af91df45b6661b5e510ea8f5f4cebf92de 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -21,7 +21,6 @@
       background: $color-darker;
     }
 
-    .sidebar-header,
     .sidebar-action-buttons {
       color: $color-light;
       background-color: lighten($color-darker, 5%);
@@ -86,37 +85,57 @@
 }
 
 $theme-charcoal: #3d454d;
+$theme-charcoal-light: #485157;
 $theme-charcoal-dark: #383f45;
 $theme-charcoal-text: #b9bbbe;
 
+$theme-blue-light: #becde9;
 $theme-blue: #2980b9;
+$theme-blue-dark: #1970a9;
+$theme-blue-darker: #096099;
+
+$theme-graphite-lighter: #ccc;
+$theme-graphite-light: #777;
 $theme-graphite: #666;
+$theme-graphite-dark: #555;
+
+$theme-gray-light: #979797;
 $theme-gray: #373737;
+$theme-gray-dark: #272727;
+$theme-gray-darker: #222;
+
+$theme-green-light: #adc;
 $theme-green: #019875;
+$theme-green-dark: #018865;
+$theme-green-darker: #017855;
+
+$theme-violet-light: #98c;
 $theme-violet: #548;
+$theme-violet-dark: #436;
+$theme-violet-darker: #325;
 
 body {
   &.ui_blue {
-    @include gitlab-theme(#becde9, $theme-blue, #1970a9, #096099);
+    @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker);
   }
 
   &.ui_charcoal {
-    @include gitlab-theme($theme-charcoal-text, #485157, $theme-charcoal, $theme-charcoal-dark);
+    @include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark);
   }
 
   &.ui_graphite {
-    @include gitlab-theme(#ccc, #777, $theme-graphite, #555);
+    @include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark);
   }
 
   &.ui_gray {
-    @include gitlab-theme(#979797, $theme-gray, #272727, #222);
+    @include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker);
   }
 
   &.ui_green {
-    @include gitlab-theme(#adc, $theme-green, #018865, #017855);
+    @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker);
   }
 
   &.ui_violet {
-    @include gitlab-theme(#98c, $theme-violet, #436, #325);
+    @include gitlab-theme($theme-violet-light, $theme-violet, $theme-violet-dark, $theme-violet-darker);
   }
 }
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 16ecf466931181d8e7c4cf69d0a8e27e3bede172..e40ff4d468808c5aacdbf7fd378ea4a0222e8593 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -8,7 +8,7 @@ header {
 
   &.navbar-empty {
     height: $header-height;
-    background: #fff;
+    background: $white-light;
     border-bottom: 1px solid $btn-gray-hover;
 
     .center-logo {
@@ -76,7 +76,7 @@ header {
       }
 
       .navbar-toggle {
-        color: #666;
+        color: $nav-toggle-gray;
         margin: 6px 0;
         border-radius: 0;
         position: absolute;
@@ -228,7 +228,7 @@ header {
 }
 
 .page-sidebar-pinned.right-sidebar-expanded {
-  @media (max-width: $screen-lg-min) {
+  @media (max-width: $screen-md-max) {
     .header-content .title {
       width: 300px;
     }
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index ff6f316d576c5d2724c7c23af6f1bb81efae0453..44834a84234a7b39909e2561c0d38b09227c26fb 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -20,7 +20,7 @@
   display: block;
   float: left;
   margin-right: 10px;
-  color: #fff;
+  color: $white-light;
   font-size: $gl-font-size;
   line-height: 25px;
 
@@ -37,10 +37,10 @@
   }
 
   &.status-box-expired {
-    background: #cea61b;
+    background-color: $issue-status-expired;
   }
 
   &.status-box-upcoming {
-    background: #8f8f8f;
+    background: $issue-box-upcoming-bg;
   }
 }
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 30a5b837d696b69ebefe7988a5d0e9f7b5709503..18f2f316f02124b3eb2a95466cf6316943151605 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -4,13 +4,13 @@
 
   &.ui-datepicker,
   &.ui-datepicker-inline {
-    border: 1px solid #ddd;
+    border: 1px solid $jq-ui-border;
     padding: 10px;
     width: 270px;
 
     .ui-datepicker-header {
-      background: #fff;
-      border-color: #ddd;
+      background: $white-light;
+      border-color: $jq-ui-border;
 
       .ui-datepicker-prev,
       .ui-datepicker-next {
@@ -39,7 +39,7 @@
   }
 
   &.ui-autocomplete {
-    border-color: #ddd;
+    border-color: $jq-ui-border;
     padding: 0;
     margin-top: 2px;
     z-index: 1001;
@@ -50,9 +50,9 @@
   }
 
   .ui-state-default {
-    border: 1px solid #fff;
-    background: #fff;
-    color: #777;
+    border: 1px solid $white-light;
+    background: $white-light;
+    color: $jq-ui-default-color;
   }
 
   .ui-state-highlight {
@@ -66,7 +66,7 @@
     .ui-state-focus {
       border: 1px solid $gl-primary;
       background: $gl-primary;
-      color: #fff;
+      color: $white-light;
     }
   }
 }
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 7baa4296abf6e29883228a3f682cbd82b7e2ddac..dfaf2f7f1d369857b9eadc8822db627db85f2a7d 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -6,7 +6,7 @@ html {
 
 body {
   &.navless {
-    background-color: white !important;
+    background-color: $white-light !important;
   }
 }
 
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index bc0610cc4174f86f6aecafbba48c162fc5fbc518..db8677433bbcc0f17944e646c7074ca10e836336 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -11,8 +11,8 @@
   > li {
     padding: 10px 15px;
     min-height: 20px;
-    border-bottom: 1px solid #eee;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+    border-bottom: 1px solid $list-border-light;
+    border-bottom: 1px solid $list-border;
 
     &::after {
       content: " ";
@@ -21,7 +21,7 @@
     }
 
     &.disabled {
-      color: #888;
+      color: $list-text-disabled-color;
     }
 
     &.unstyled {
@@ -31,9 +31,9 @@
     }
 
     &.warning-row {
-      background-color: #fcf8e3;
-      border-color: #faebcc;
-      color: #8a6d3b;
+      background-color: $list-warning-row-bg;
+      border-color: $list-warning-row-border;
+      color: $list-warning-row-color;
     }
 
     &.smoke { background-color: $background-color; }
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 42087c915308a4d7ce326976ee44e6e26f298c0e..59a30d31ac7d22c2ebaae20aa8c7d8b9de1a7956 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -56,16 +56,9 @@
 
 .md-header {
   .nav-links {
-    .active {
-      a {
-        border-bottom-color: #000;
-      }
-    }
-
     a {
       padding-top: 0;
       line-height: 19px;
-      border-bottom: 1px solid $border-color;
 
       &.btn.btn-xs {
         padding: 2px 5px;
@@ -80,7 +73,7 @@
 }
 
 .referenced-users {
-  color: #4c4e54;
+  color: $gl-header-color;
   padding-top: 10px;
 }
 
@@ -92,8 +85,8 @@
 
 .markdown-area {
   border-radius: 0;
-  background: #fff;
-  border: 1px solid #ddd;
+  background: $white-light;
+  border: 1px solid $md-area-border;
   min-height: 140px;
   max-height: 500px;
   padding: 5px;
@@ -115,7 +108,7 @@
 
   hr {
     // Darken 'whitesmoke' a bit to make it more visible in note bodies
-    border-color: darken(#f5f5f5, 8%);
+    border-color: darken($gray-normal, 8%);
     margin: 10px 0;
   }
 
@@ -142,7 +135,7 @@
 .toolbar-btn {
   float: left;
   padding: 0 5px;
-  color: #959494;
+  color: $note-toolbar-color;
   background: transparent;
   border: 0;
   outline: 0;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index f84ca36d10f5d797640497b1c5e2f77db071504b..4f2ac77f228b2762ccf45163f7a239c0953b0485 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -24,7 +24,7 @@
     @include clearfix;
 
     padding: 10px 0;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid $list-border-light;
     display: block;
     margin: 0;
 
@@ -67,8 +67,8 @@
 }
 
 @mixin dark-diff-match-line {
-  color: rgba(255, 255, 255, 0.3);
-  background: rgba(255, 255, 255, 0.1);
+  color: $dark-diff-match-bg;
+  background: $dark-diff-match-color;
 }
 
 @mixin webkit-prefix($property, $value) {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 9391661a595650b30dc53f864cafe91e42712f71..abfdd7a759d1e4bb0262aed86545b253bd69930c 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -133,9 +133,9 @@
   right: 0;
   top: 30%;
   padding: 5px 15px;
-  background: #eee;
+  background: $show-aside-bg;
   font-size: 20px;
-  color: #777;
+  color: $show-aside-color;
   z-index: 100;
-  box-shadow: 0 1px 2px #ddd;
+  box-shadow: 0 1px 2px $show-aside-shadow;
 }
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index ce864c2de5ef784f81fd96235e7d0be72eb0cee5..d2d3fc23b6cf0b0ec95f915fece0311468e06371 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -51,24 +51,31 @@
       margin-bottom: -1px;
       font-size: 15px;
       line-height: 28px;
-      color: #959494;
+      color: $note-toolbar-color;
       border-bottom: 2px solid transparent;
 
       &:hover,
       &:active,
       &:focus {
         text-decoration: none;
+        border-bottom: 2px solid $gray-darkest;
+        color: $black;
+
+        .badge {
+          color: $black;
+        }
       }
     }
 
     &.active a {
       border-bottom: 2px solid $link-underline-blue;
       color: $black;
+      font-weight: 600;
     }
 
     .badge {
       font-weight: normal;
-      background-color: #eee;
+      background-color: $nav-badge-bg;
       color: $btn-transparent-color;
       vertical-align: baseline;
     }
@@ -85,14 +92,20 @@
 
     li {
 
+      &.active a {
+        border-bottom: none;
+        color: $link-underline-blue;
+      }
+
       a {
         margin: 0;
         padding: 11px 10px 9px;
-      }
 
-      &.active a {
-        border-bottom: none;
-        color: $link-underline-blue;
+        &:hover,
+        &:active,
+        &:focus {
+          border-bottom: none;
+        }
       }
     }
   }
@@ -110,7 +123,7 @@
     line-height: 28px;
 
     /* Small devices (phones, tablets, 768px and lower) */
-    @media (max-width: $screen-sm-min) {
+    @media (max-width: $screen-xs-max) {
       width: 100%;
     }
   }
@@ -310,37 +323,9 @@
     height: 51px;
 
     li {
-
       a {
         padding-top: 10px;
       }
-
-      a,
-      i {
-        color: $layout-link-gray;
-      }
-
-      &.active {
-
-        a,
-        i {
-          color: $black;
-        }
-
-        svg {
-          path,
-          polygon {
-            fill: $black;
-          }
-        }
-      }
-
-      &:hover {
-        a,
-        i {
-          color: $black;
-        }
-      }
     }
   }
 }
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index cb2c351c3688506162c967e5e6d062718086ceb1..b37c1d0d670a39c3edef7099b76d6924b789656c 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -43,7 +43,7 @@
 /**
  * Small screen pagination
  */
-@media (max-width: $screen-xs) {
+@media (max-width: $screen-xs-min) {
   .gl-pagination {
     .pagination li a {
       padding: 6px 10px;
@@ -62,7 +62,7 @@
 /**
  * Medium screen pagination
  */
-@media (min-width: $screen-xs) and (max-width: $screen-md-max) {
+@media (min-width: $screen-xs-min) and (max-width: $screen-md-max) {
   .gl-pagination {
     .page {
       display: none;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 920ce249b9a35cdb10ab86d3d392133840924e73..fde1431b13e42147742d419636c977e514958dde 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -6,7 +6,7 @@
 .select2-container,
 .select2-container.select2-drop-above {
   .select2-choice {
-    background: #fff;
+    background: $white-light;
     border-color: $input-border;
     height: 35px;
     padding: $gl-vert-padding $gl-input-padding;
@@ -47,7 +47,7 @@
 }
 
 .select2-drop {
-  box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
+  box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0;
   border-radius: $border-radius-default;
   border: none;
   min-width: 175px;
@@ -59,7 +59,7 @@
 }
 
 .select2-drop {
-  color: #7f8fa4;
+  color: $gl-grayish-blue;
 }
 
 .select2-highlighted {
@@ -156,7 +156,7 @@
 
 .select2-search input {
   padding: 2px 25px 2px 5px;
-  background: #fff image-url('select2.png');
+  background: $white-light image-url('select2.png');
   background-repeat: no-repeat;
   background-position: right 0 bottom 6px;
   border: 1px solid $input-border;
@@ -169,7 +169,7 @@
 }
 
 .select2-search input.select2-active {
-  background-color: #fff;
+  background-color: $white-light;
   background-image: image-url('select2-spinner.gif') !important;
   background-repeat: no-repeat;
   background-position: right 5px center !important;
@@ -206,7 +206,7 @@
 .select2-highlighted {
   .group-result {
     .group-path {
-      color: #fff;
+      color: $white-light;
     }
   }
 }
@@ -221,7 +221,7 @@
   }
 
   .group-path {
-    color: #999;
+    color: $group-path-color;
   }
 }
 
@@ -241,7 +241,7 @@
 
 .namespace-result {
   .namespace-kind {
-    color: #aaa;
+    color: $namespace-kind-color;
     font-weight: normal;
   }
 
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 44c445c0543950e4c6c6e521c4d9913faba07011..0aa609b8dd55b660e189a8e396b524a8a9bfb135 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -36,7 +36,7 @@
   transition: padding $sidebar-transition-duration;
 
   .container-fluid {
-    background: #fff;
+    background: $white-light;
     padding: 0 $gl-padding;
 
     &.container-blank {
@@ -59,11 +59,6 @@
     padding: 0 !important;
   }
 
-  .sidebar-header {
-    padding: 11px 22px 12px;
-    font-size: 20px;
-  }
-
   li {
     &.separate-item {
       padding-top: 10px;
@@ -220,7 +215,7 @@ header.header-sidebar-pinned {
   padding-right: 0;
 
   @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
-    &:not(.build-sidebar) {
+    &:not(.build-sidebar):not(.wiki-sidebar) {
       padding-right: $sidebar_collapsed_width;
     }
   }
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 9a90d3794fd93ccf72ad98b231fa8dfed940ab37..a5f36c177fc07a3494398c6caae5391c31e7f3ce 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -14,11 +14,11 @@ table {
     .warning,
     .danger,
     .info {
-      color: #fff;
+      color: $white-light;
 
       a:not(.btn) {
         text-decoration: underline;
-        color: #fff;
+        color: $white-light;
       }
     }
 
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 59f4594bb83d0f3df43c946b9a18247dd51ace13..55bc325b858c35c251b211006cdda4135ce48771 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -97,13 +97,13 @@
   display: inline-block;
 
   &.label-gray {
-    background-color: #f8fafc;
+    background-color: $label-gray-bg;
     color: $gl-gray;
     text-shadow: none;
   }
 
   &.label-inverse {
-    background-color: #333;
+    background-color: $label-inverse-bg;
   }
 }
 
@@ -158,7 +158,7 @@
       font-weight: normal;
 
       a {
-        color: #777;
+        color: $panel-heading-link-color;
       }
     }
   }
@@ -172,7 +172,7 @@
 .alert {
   a:not(.btn) {
     @extend .alert-link;
-    color: #fff;
+    color: $white-light;
     text-decoration: underline;
   }
 }
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 44fe37d3a4a7628ef308863e0d7e6a9df80fe1e2..c731a8f222f8d0fbf076d88adddef081c980c05c 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -48,7 +48,7 @@ $font-size-base: $gl-font-size;
 
 $padding-base-vertical: $gl-vert-padding;
 $padding-base-horizontal: $gl-padding;
-$component-active-color: #fff;
+$component-active-color: $white-light;
 $component-active-bg: $brand-info;
 
 //== Forms
@@ -66,7 +66,7 @@ $legend-color: $text-color;
 //##
 
 $pagination-color: $gl-gray;
-$pagination-bg: #fff;
+$pagination-bg: $white-light;
 $pagination-border: $border-color;
 
 $pagination-hover-color: $gl-gray;
@@ -74,7 +74,7 @@ $pagination-hover-bg: $row-hover;
 $pagination-hover-border: $border-color;
 
 $pagination-active-color: $blue-dark;
-$pagination-active-bg: #fff;
+$pagination-active-bg: $white-light;
 $pagination-active-border: $border-color;
 
 $pagination-disabled-color: #cdcdcd;
@@ -86,19 +86,19 @@ $pagination-disabled-border: $border-color;
 //
 //## Define colors for form feedback states and, by default, alerts.
 
-$state-success-text: #fff;
+$state-success-text: $white-light;
 $state-success-bg: $brand-success;
 $state-success-border: $brand-success;
 
-$state-info-text: #fff;
+$state-info-text: $white-light;
 $state-info-bg: $brand-info;
 $state-info-border: $brand-info;
 
-$state-warning-text: #fff;
+$state-warning-text: $white-light;
 $state-warning-bg: $brand-warning;
 $state-warning-border: $brand-warning;
 
-$state-danger-text: #fff;
+$state-danger-text: $white-light;
 $state-danger-bg: $brand-danger;
 $state-danger-border: $brand-danger;
 
@@ -135,14 +135,14 @@ $well-border: #eee;
 $code-color: #c7254e;
 $code-bg: #f9f2f4;
 
-$kbd-color: #fff;
+$kbd-color: $white-light;
 $kbd-bg: #333;
 
 //== Buttons
 //
 //##
 $btn-default-color: $gl-text-color;
-$btn-default-bg: #fff;
+$btn-default-bg: $white-light;
 $btn-default-border: #e7e9ed;
 
 //== Nav
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 070e42d63d2e7b33b47b397a2320300aa0a87ed7..d906d26bba923941e3b5ab72ed15005cfa63e3bf 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -33,15 +33,15 @@
     padding: 3px 5px;
     font-size: 11px;
     line-height: 10px;
-    color: #555;
+    color: $kdb-color;
     vertical-align: middle;
-    background-color: #fcfcfc;
+    background-color: $kdb-bg;
     border-width: 1px;
     border-style: solid;
-    border-color: #ccc #ccc #bbb;
+    border-color: $kdb-border $kdb-border $kdb-border-bottom;
     border-image: none;
     border-radius: 3px;
-    box-shadow: 0 -1px 0 #bbb inset;
+    box-shadow: 0 -1px 0 $kdb-shadow inset;
   }
 
   h1 {
@@ -81,7 +81,7 @@
   }
 
   blockquote {
-    color: #7f8fa4;
+    color: $gl-grayish-blue;
     font-size: inherit;
     padding: 8px 21px;
     margin: 12px 0;
@@ -94,13 +94,13 @@
   }
 
   blockquote p {
-    color: #7f8fa4 !important;
+    color: $gl-grayish-blue !important;
     font-size: inherit;
     line-height: 1.5;
   }
 
   p {
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
     margin: 6px 0 0;
   }
 
@@ -108,10 +108,10 @@
     @extend .table;
     @extend .table-bordered;
     margin: 12px 0;
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
 
     th {
-      background: #f8fafc;
+      background: $label-gray-bg;
     }
   }
 
@@ -182,6 +182,7 @@
       left: -16px;
       position: absolute;
       text-decoration: none;
+      outline: none;
 
       &::after {
         content: image-url('icon_anchor.svg');
@@ -201,7 +202,7 @@
  *
  */
 body {
-  -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
+  -webkit-text-shadow: $body-text-shadow 0 0 1px;
 }
 
 .page-title {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 750d99ebabef5e55ae4405aca3860437cbb5f3d0..edfb2c33f4a75fef5aee202d5fcd3f5d7652f8be 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px;
 /*
  * Color schema
  */
+$darken-normal-factor: 7%;
+$darken-dark-factor: 10%;
+$darken-border-factor: 5%;
+
 $white-light: #fff;
-$white-normal: #ededed;
-$white-dark: #ececec;
+$white-normal: darken($white-light, $darken-normal-factor);
+$white-dark: darken($white-light, $darken-dark-factor);
 
 $gray-lightest: #fdfdfd;
 $gray-light: #fafafa;
 $gray-lighter: #f9f9f9;
-$gray-normal: #f5f5f5;
-$gray-dark: #ededed;
+$gray-normal: darken($gray-light, $darken-normal-factor);
+$gray-dark: darken($gray-light, $darken-dark-factor);
 $gray-darker: #eee;
 $gray-darkest: #c9c9c9;
 
-$green-light: #38ae67;
-$green-normal: #2faa60;
-$green-dark: #2ca05b;
+$green-light: #3cbd70;
+$green-normal: darken($green-light, $darken-normal-factor);
+$green-dark: darken($green-light, $darken-dark-factor);
 
 $blue-light: #2ea8e5;
-$blue-normal: #2d9fd8;
-$blue-dark: #2897ce;
+$blue-normal: darken($blue-light, $darken-normal-factor);
+$blue-dark: darken($blue-light, $darken-dark-factor);
 
 $blue-medium-light: #3498cb;
-$blue-medium: #2f8ebf;
-$blue-medium-dark: #2d86b4;
+$blue-medium: darken($blue-medium-light, $darken-normal-factor);
+$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor);
 
 $blue-light-transparent: rgba(44, 159, 216, 0.05);
 
 $orange-light: #fc8a51;
-$orange-normal: #e75e40;
-$orange-dark: #ce5237;
+$orange-normal: darken($orange-light, $darken-normal-factor);
+$orange-dark: darken($orange-light, $darken-dark-factor);
 
 $red-light: #e52c5a;
-$red-normal: #d22852;
-$red-dark: darken($red-normal, 5%);
+$red-normal: darken($red-light, $darken-normal-factor);
+$red-dark: darken($red-light, $darken-dark-factor);
 
 $black: #000;
 $black-transparent: rgba(0, 0, 0, 0.3);
 
-$border-white-light: #f1f2f4;
-$border-white-normal: #d6dae2;
-$border-white-dark: #c6cacf;
+$border-white-light: darken($white-light, $darken-border-factor);
+$border-white-normal: darken($white-normal, $darken-border-factor);
+$border-white-dark: darken($white-dark, $darken-border-factor);
 
-$border-gray-light: #dcdcdc;
-$border-gray-normal: #d7d7d7;
-$border-gray-dark: #c6cacf;
+$border-gray-light: darken($gray-light, $darken-border-factor);
+$border-gray-normal: darken($gray-normal, $darken-border-factor);
+$border-gray-dark: darken($gray-dark, $darken-border-factor);
 
 $border-green-extra-light: #9adb84;
-$border-green-light: #2faa60;
-$border-green-normal: #2ca05b;
-$border-green-dark: #279654;
+$border-green-light: darken($green-light, $darken-border-factor);
+$border-green-normal: darken($green-normal, $darken-border-factor);
+$border-green-dark: darken($green-dark, $darken-border-factor);
 
-$border-blue-light: #2d9fd8;
-$border-blue-normal: #2897ce;
-$border-blue-dark: #258dc1;
+$border-blue-light: darken($blue-light, $darken-border-factor);
+$border-blue-normal: darken($blue-normal, $darken-border-factor);
+$border-blue-dark: darken($blue-dark, $darken-border-factor);
 
-$border-orange-light: #fc6d26;
-$border-orange-normal: #ce5237;
-$border-orange-dark: #c14e35;
+$border-orange-light: darken($orange-light, $darken-border-factor);
+$border-orange-normal: darken($orange-normal, $darken-border-factor);
+$border-orange-dark: darken($orange-dark, $darken-border-factor);
 
-$border-red-light: #d22852;
-$border-red-normal: #ca264f;
-$border-red-dark: darken($border-red-normal, 5%);
+$border-red-light: darken($red-light, $darken-border-factor);
+$border-red-normal: darken($red-normal, $darken-border-factor);
+$border-red-dark: darken($red-dark, $darken-border-factor);
 
 $help-well-bg: $gray-light;
 $help-well-border: #e5e5e5;
@@ -92,6 +96,8 @@ $dark-background-color: #f5f5f5;
 $table-text-gray: #8f8f8f;
 $well-expand-item: #e8f2f7;
 $well-inner-border: #eef0f2;
+$well-light-border: #f1f1f1;
+$well-light-text-color: #5b6169;
 
 /*
  * Text
@@ -99,11 +105,13 @@ $well-inner-border: #eef0f2;
 $gl-font-size: 15px;
 $gl-title-color: #333;
 $gl-text-color: #5c5c5c;
+$gl-text-color-dark: #5c5d5e;
 $gl-text-color-light: #8c8c8c;
 $gl-text-green: #4a2;
 $gl-text-red: #d12f19;
 $gl-text-orange: #d90;
 $gl-link-color: #3777b0;
+$gl-diff-text-color: #555;
 $gl-dark-link-color: #333;
 $gl-placeholder-color: #8f8f8f;
 $gl-icon-color: $gl-placeholder-color;
@@ -119,13 +127,20 @@ $gl-header-color: #4c4e54;
 $list-font-size: $gl-font-size;
 $list-title-color: $gl-title-color;
 $list-text-color: $gl-text-color;
+$list-text-disabled-color: #888;
+$list-border-light: #eee;
+$list-border: rgba(0, 0, 0, 0.05);
 $list-text-height: 42px;
+$list-warning-row-bg: #fcf8e3;
+$list-warning-row-border: #faebcc;
+$list-warning-row-color: #8a6d3b;
 
 /*
  * Markdown
  */
 $md-text-color: $gl-text-color;
 $md-link-color: $gl-link-color;
+$md-area-border: #ddd;
 
 /*
  * Code
@@ -149,10 +164,8 @@ $gl-sidebar-padding: 22px;
 $row-hover: #f7faff;
 $row-hover-border: #b2d7ff;
 $progress-color: #c0392b;
-$avatar_radius: 50%;
 $header-height: 50px;
 $fixed-layout-width: 1280px;
-$gl-avatar-size: 40px;
 $error-exclamation-point: #e62958;
 $border-radius-default: 2px;
 $btn-transparent-color: #8f8f8f;
@@ -162,10 +175,47 @@ $provider-btn-not-active-color: #4688f1;
 $link-underline-blue: #4a8bee;
 $active-item-blue: #4a8bee;
 $layout-link-gray: #7e7c7c;
-$todo-alert-blue: #428bca;
 $btn-side-margin: 10px;
 $btn-sm-side-margin: 7px;
 $btn-xs-side-margin: 5px;
+$issue-status-expired: #cea61b;
+$issuable-sidebar-color: #999;
+$issuable-avatar-hover-border: #999;
+$issuable-clipboard-color: #999;
+$show-aside-bg: #eee;
+$show-aside-color: #777;
+$show-aside-shadow: #ddd;
+$group-path-color: #999;
+$namespace-kind-color: #aaa;
+$panel-heading-link-color: #777;
+$graph-author-email-color: #777;
+$count-arrow-border: #dce0e5;
+$save-project-loader-color: #555;
+$divergence-graph-bar-bg: #ccc;
+$divergence-graph-separator-bg: #ccc;
+$issue-box-upcoming-bg: #8f8f8f;
+
+/*
+* Common component specific colors
+*/
+$hint-color: #999;
+$well-pre-bg: #eee;
+$well-pre-color: #555;
+$loading-color: #555;
+$update-author-color: #999;
+$user-mention-color: #2fa0bb;
+$time-color: #999;
+$project-member-show-color: #aaa;
+$gl-promo-color: #aaa;
+$error-bg: #c67;
+$warning-message-bg: #ffffe6;
+$warning-message-border: #ed9;
+$warning-message-color: #b90;
+$control-group-descr-color: #666;
+$table-permission-x-bg: #d9edf7;
+$username-color: #666;
+$description-color: #666;
+$profiler-border: #eee;
 
 /* tanuki logo colors */
 $tanuki-red: #e24329;
@@ -201,12 +251,22 @@ $table-border-gray: #f0f0f0;
 $line-target-blue: #f6faff;
 $line-select-yellow: #fcf8e7;
 $line-select-yellow-dark: #f0e2bd;
+$dark-diff-match-bg: rgba(255, 255, 255, 0.3);
+$dark-diff-match-color: rgba(255, 255, 255, 0.1);
+$file-mode-changed: #777;
+$file-mode-changed: #777;
+$diff-image-bg: #ddd;
+$diff-image-info-color: grey;
+$diff-image-img-bg: #e5e5e5;
+$diff-swipe-border: #999;
+$diff-view-modes-color: grey;
+$diff-view-modes-border: #c1c1c1;
 
 /*
  * Fonts
  */
 $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
+$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
 
 /*
 * Dropdowns
@@ -216,27 +276,31 @@ $dropdown-bg: #fff;
 $dropdown-link-color: #555;
 $dropdown-link-hover-bg: $row-hover;
 $dropdown-empty-row-bg: rgba(#000, .04);
-$dropdown-border-color: rgba(#000, .1);
+$dropdown-border-color: $border-color;
 $dropdown-shadow-color: rgba(#000, .1);
 $dropdown-divider-color: rgba(#000, .1);
 $dropdown-header-color: #959494;
 $dropdown-title-btn-color: #bfbfbf;
 $dropdown-input-color: #555;
+$dropdown-input-fa-color: #c7c7c7;
 $dropdown-input-focus-border: $focus-border-color;
 $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
 $dropdown-loading-bg: rgba(#fff, .6);
+$dropdown-chevron-size: 10px;
 
 $dropdown-toggle-bg: #fff;
-$dropdown-toggle-color: #626262;
-$dropdown-toggle-border-color: #eaeaea;
-$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%);
+$dropdown-toggle-color: #5c5c5c;
+$dropdown-toggle-border-color: #e5e5e5;
+$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 13%);
+$dropdown-toggle-active-border-color: darken($dropdown-toggle-border-color, 14%);
 $dropdown-toggle-icon-color: #c4c4c4;
-$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
+$dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%);
 
 /*
 * Buttons
 */
 $btn-active-gray: #ececec;
+$btn-active-gray-light: e4e7ed;
 $btn-placeholder-gray: #c7c7c7;
 $btn-white-active: #848484;
 $btn-gray-hover: #eee;
@@ -246,6 +310,7 @@ $btn-gray-hover: #eee;
  */
 $award-emoji-menu-bg: #fff;
 $award-emoji-menu-border: #f1f2f4;
+$award-emoji-menu-shadow: rgba(0,0,0,.175);
 $award-emoji-new-btn-icon-color: #dcdcdc;
 
 /*
@@ -255,7 +320,7 @@ $search-input-border-color: rgba(#4688f1, .8);
 $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
 $search-input-width: 220px;
 $location-badge-color: #aaa;
-$location-badge-bg: $gray-normal;
+$location-badge-bg: $dark-background-color;
 $location-badge-active-bg: #4f91f8;
 $location-icon-color: #e7e9ed;
 $location-icon-active-color: #807e7e;
@@ -267,17 +332,28 @@ $notes-light-color: #8e8e8e;
 $notes-action-color: #c3c3c3;
 $notes-role-color: #8e8e8e;
 $notes-role-border-color: #e4e4e4;
-
 $note-disabled-comment-color: #b2b2b2;
 $note-form-border-color: #e5e5e5;
 $note-toolbar-color: #959494;
+$note-targe3-outside: #fffff0;
+$note-targe3-inside: #ffffd3;
+$note-line2-border: #ddd;
+
 
+/*
+* Zen
+*/
+$zen-control-color: #555;
 $zen-control-hover-color: #111;
 
+/*
+* Calendar
+*/
 $calendar-header-color: #b8b8b8;
 $calendar-hover-bg: #ecf3fe;
 $calendar-border-color: rgba(#000, .1);
 $calendar-unselectable-bg: $gray-light;
+$calendar-user-contrib-text: #959494;
 
 /*
  *  Cycle Analytics
@@ -287,13 +363,217 @@ $cycle-analytics-box-text-color: #8c8c8c;
 $cycle-analytics-big-font: 19px;
 $cycle-analytics-dark-text: $gl-title-color;
 $cycle-analytics-light-gray: #bfbfbf;
+$cycle-analytics-dismiss-icon-color: #b2b2b2;
 
 /*
  * Personal Access Tokens
  */
 $personal-access-tokens-disabled-label-color: #bbb;
 
+/*
+* CI
+*/
 $ci-output-bg: #1d1f21;
 $ci-text-color: #c5c8c6;
+$ci-skipped-color: #888;
 
+/*
+* Boards
+*/
 $issue-boards-font-size: 15px;
+$issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
+
+/*
+* Avatar
+*/
+$avatar_radius: 50%;
+$avatar-border: rgba(0, 0, 0, .1);
+$gl-avatar-size: 40px;
+
+/*
+* Builds
+*/
+$builds-trace-bg: #111;
+
+/*
+* Callout
+*/
+$callout-danger-bg: #fdf7f7;
+$callout-danger-border: #eed3d7;
+$callout-danger-color: #b94a48;
+$callout-warning-bg: #faf8f0;
+$callout-warning-border: #faebcc;
+$callout-warning-color: #8a6d3b;
+$callout-info-bg: #f4f8fa;
+$callout-info-border: #bce8f1;
+$callout-info-color: #34789a;
+$callout-success-bg: #dff0d8;
+$callout-success-border: #5ca64d;
+$callout-success-color: #3c763d;
+
+/*
+* Commit Page
+*/
+$commit-committer-color: #999;
+$commit-max-width-marker-color: rgba(0, 0, 0, 0.0);
+$commit-message-text-area-bg: rgba(0, 0, 0, 0.0);
+
+/*
+* Common
+*/
+$common-gray: $gl-gray;
+$common-gray-light: #bbb;
+$common-gray-dark: #444;
+$common-red: $gl-text-red;
+$common-green: $gl-text-green;
+
+
+/*
+* Dashboard
+*/
+$dashboard-project-access-icon-color: #888;
+
+/*
+* Editor
+*/
+$editor-cancel-color: #b94a48;
+
+/*
+* Events
+*/
+$events-pre-color: #777;
+$events-note-icon-color: #777;
+$events-body-border: #ddd;
+
+/*
+* Files
+*/
+$file-image-bg: #eee;
+$blob-bg: #eee;
+$blame-border: #eee;
+$blame-line-numbers-border: #ddd;
+$logs-bg: #eee;
+$logs-li-color: #888;
+$logs-p-color: #333;
+
+/*
+* Forms
+*/
+$input-danger-bg: #f2dede;
+$input-danger-border: #d66;
+$input-group-addon-bg: #f7f8fa;
+$gl-field-focus-shadow: rgba(0, 0, 0, 0.075);
+$gl-field-focus-shadow-error: rgba(210, 40, 82, 0.6);
+
+/*
+* Help
+*/
+$document-index-color: #888;
+$help-shortcut-color: #999;
+$help-shortcut-mapping-color: #555;
+$help-shortcut-header-color: #333;
+
+/*
+* Issues
+*/
+$issues-border: #e5e5e5;
+$issues-today-bg: #f3fff2;
+$issues-today-border: #e1e8d5;
+
+/*
+* jQuery UI
+*/
+$jq-ui-border: #ddd;
+$jq-ui-default-color: #777;
+
+/*
+* Label
+*/
+$label-gray-bg: #f8fafc;
+$label-inverse-bg: #333;
+$label-remove-border: rgba(0, 0, 0, .1);
+
+/*
+* Lint
+*/
+$lint-incorrect-color: red;
+$lint-correct-color: #47a447;
+
+/*
+* Login
+*/
+$login-brand-holder-color: #888;
+$login-devise-error-color: #a00;
+
+/*
+* Nav
+*/
+$nav-link-gray: #959494;
+$nav-badge-bg: #eee;
+$nav-toggle-gray: #666;
+
+/*
+* Notify
+*/
+$notify-details: #777;
+$notify-footer: #777;
+$notify-new-file: #090;
+$notify-deleted-file: #b00;
+
+/*
+* Projects
+*/
+$project-option-descr-color: #54565b;
+$project-breadcrumb-color: #999;
+$project-private-forks-notice-odd: #2aa056;
+$project-network-controls-color: #888;
+$project-limit-message-bg: #f28d35;
+
+/*
+* Runners
+*/
+$runner-state-shared-bg: #32b186;
+$runner-state-specific-bg: #3498db;
+$runner-status-online-color: green;
+$runner-status-offline-color: gray;
+$runner-status-paused-color: red;
+
+/*
+Stat Graph
+*/
+$stat-graph-common-bg: #f3f3f3;
+$stat-graph-area-fill: #1db34f;
+$stat-graph-axis-fill: #aaa;
+$stat-graph-orange-fill: #f17f49;
+$stat-graph-selection-fill: #333;
+$stat-graph-selection-stroke: #333;
+
+/*
+* Selects
+*/
+$select2-drop-shadow1: rgba(76, 86, 103, 0.247059);
+$select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
+
+
+/*
+* Todo
+*/
+$todo-alert-blue: #428bca;
+$todo-body-pre-color: #777;
+$todo-body-border: #ddd;
+
+/*
+* Typography
+*/
+$kdb-bg: #fcfcfc;
+$kdb-color: #555;
+$kdb-border: #ccc;
+$kdb-border-bottom: #bbb;
+$kdb-shadow: #bbb;
+$body-text-shadow: rgba(255,255,255,0.01);
+
+/*
+* UI Dev Kit
+*/
+$ui-dev-kit-example-color: #bbb;
+$ui-dev-kit-example-border: #ddd;
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index ff02ebdd34ca8cf30fd790394cc5c1442ac23552..e5c7d70d45a098b971f4380e76b93ec1b1873e99 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -1,6 +1,6 @@
 .zen-backdrop {
   &.fullscreen {
-    background-color: white;
+    background-color: $white-light;
     position: fixed;
     top: 0;
     bottom: 0;
@@ -12,7 +12,7 @@
       border: none;
       box-shadow: none;
       border-radius: 0;
-      color: #000;
+      color: $black;
       font-size: 20px;
       line-height: 26px;
       padding: 30px;
@@ -34,7 +34,7 @@
 
 .zen-control {
   padding: 0;
-  color: #555;
+  color: $zen-control-color;
   background: none;
   border: 0;
 }
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index d22d9b014958fef4cc68b9777f943537050de995..cb923166b25756057b917fd5b077b2ead5d110bf 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,27 +1,109 @@
 /* https://github.com/MozMorris/tomorrow-pygments */
+
+/*
+* Dark syntax colors
+*/
+$dark-new-bg: rgba(51, 255, 51, 0.1);
+$dark-new-idiff: rgba(51, 255, 51, 0.2);
+$dark-old-bg: rgba(255, 51, 51, 0.2);
+$dark-old-idiff: rgba(255, 51, 51, 0.25);
+$dark-border: #808080;
+$dark-code-border: #666;
+$dark-main-bg: #1d1f21;
+$dark-main-color: #1d1f21;
+$dark-line-color: #c5c8c6;
+$dark-line-num-color: rgba(255, 255, 255, 0.3);
+$dark-diff-not-empty-bg: #557;
+$dark-highlight-bg: #ffe792;
+$dark-highlight-color: $black;
+$dark-pre-hll-bg: #373b41;
+$dark-hll-bg: #373b41;
+$dark-c: #969896;
+$dark-err: #c66;
+$dark-k: #b294bb;
+$dark-l: #de935f;
+$dark-n: #c5c8c6;
+$dark-o: #8abeb7;
+$dark-p: #c5c8c6;
+$dark-cm: #969896;
+$dark-cp: #969896;
+$dark-c1: #969896;
+$dark-cs: #969896;
+$dark-gd: #c66;
+$dark-gh: #c5c8c6;
+$dark-gi: #b5bd68;
+$dark-gp: #969896;
+$dark-gu: #8abeb7;
+$dark-kc: #b294bb;
+$dark-kd: #b294bb;
+$dark-kn: #8abeb7;
+$dark-kp: #b294bb;
+$dark-kr: #b294bb;
+$dark-kt: #f0c674;
+$dark-ld: #b5bd68;
+$dark-m: #de935f;
+$dark-s: #b5bd68;
+$dark-na: #81a2be;
+$dark-nb: #c5c8c6;
+$dark-nc: #f0c674;
+$dark-no: #c66;
+$dark-nd: #8abeb7;
+$dark-ni: #c5c8c6;
+$dark-ne: #c66;
+$dark-nf: #81a2be;
+$dark-nl: #c5c8c6;
+$dark-nn: #f0c674;
+$dark-nx: #81a2be;
+$dark-py: #c5c8c6;
+$dark-nt: #8abeb7;
+$dark-nv: #c66;
+$dark-ow: #8abeb7;
+$dark-w: #c5c8c6;
+$dark-mf: #de935f;
+$dark-mh: #de935f;
+$dark-mi: #de935f;
+$dark-mo: #de935f;
+$dark-sb: #b5bd68;
+$dark-sc: #c5c8c6;
+$dark-sd: #969896;
+$dark-s2: #b5bd68;
+$dark-se: #de935f;
+$dark-sh: #b5bd68;
+$dark-si: #de935f;
+$dark-sx: #b5bd68;
+$dark-sr: #b5bd68;
+$dark-s1: #b5bd68;
+$dark-ss: #b5bd68;
+$dark-bp: #c5c8c6;
+$dark-vc: #c66;
+$dark-vg: #c66;
+$dark-vi: #c66;
+$dark-il: #de935f;
+
 .code.dark {
   // Line numbers
   .line-numbers,
   .diff-line-num {
-    background-color: #1d1f21;
+    background-color: $dark-main-bg;
   }
 
   .diff-line-num,
   .diff-line-num a {
-    color: rgba(255, 255, 255, 0.3);
+    color: $dark-main-color;
+    color: $dark-line-num-color;
   }
 
   // Code itself
   pre.code,
   .diff-line-num {
-    border-color: #666;
+    border-color: $dark-code-border;
   }
 
   &,
   pre.code,
   .line_holder .line_content {
-    background-color: #1d1f21;
-    color: #c5c8c6;
+    background-color: $dark-main-bg;
+    color: $dark-line-color;
   }
 
   // Diff line
@@ -32,18 +114,18 @@
 
     td.diff-line-num.hll:not(.empty-cell),
     td.line_content.hll:not(.empty-cell) {
-      background-color: #557;
-      border-color: darken(#557, 15%);
+      background-color: $dark-diff-not-empty-bg;
+      border-color: darken($dark-diff-not-empty-bg, 15%);
     }
 
     .diff-line-num.new,
     .line_content.new {
-      @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
+      @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
     }
 
     .diff-line-num.old,
     .line_content.old {
-      @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080);
+      @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
     }
 
     .line_content.match {
@@ -53,77 +135,77 @@
 
   // highlight line via anchor
   pre .hll {
-    background-color: #557 !important;
+    background-color: $dark-pre-hll-bg !important;
   }
 
   // Search result highlight
   span.highlight_word {
-    background-color: #ffe792 !important;
-    color: #000 !important;
+    background-color: $dark-highlight-bg !important;
+    color: $dark-highlight-color !important;
   }
 
-  .hll { background-color: #373b41; }
-  .c { color: #969896; } /* Comment */
-  .err { color: #c66; } /* Error */
-  .k { color: #b294bb; } /* Keyword */
-  .l { color: #de935f; } /* Literal */
-  .n { color: #c5c8c6; } /* Name */
-  .o { color: #8abeb7; } /* Operator */
-  .p { color: #c5c8c6; } /* Punctuation */
-  .cm { color: #969896; } /* Comment.Multiline */
-  .cp { color: #969896; } /* Comment.Preproc */
-  .c1 { color: #969896; } /* Comment.Single */
-  .cs { color: #969896; } /* Comment.Special */
-  .gd { color: #c66; } /* Generic.Deleted */
+  .hll { background-color: $dark-hll-bg; }
+  .c { color: $dark-c; } /* Comment */
+  .err { color: $dark-err; } /* Error */
+  .k { color: $dark-k; } /* Keyword */
+  .l { color: $dark-l; } /* Literal */
+  .n { color: $dark-n; } /* Name */
+  .o { color: $dark-o; } /* Operator */
+  .p { color: $dark-p; } /* Punctuation */
+  .cm { color: $dark-cm; } /* Comment.Multiline */
+  .cp { color: $dark-cp; } /* Comment.Preproc */
+  .c1 { color: $dark-c1; } /* Comment.Single */
+  .cs { color: $dark-cs; } /* Comment.Special */
+  .gd { color: $dark-gd; } /* Generic.Deleted */
   .ge { font-style: italic; } /* Generic.Emph */
-  .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */
-  .gi { color: #b5bd68; } /* Generic.Inserted */
-  .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */
+  .gh { color: $dark-gh; font-weight: bold; } /* Generic.Heading */
+  .gi { color: $dark-gi; } /* Generic.Inserted */
+  .gp { color: $dark-gp; font-weight: bold; } /* Generic.Prompt */
   .gs { font-weight: bold; } /* Generic.Strong */
-  .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */
-  .kc { color: #b294bb; } /* Keyword.Constant */
-  .kd { color: #b294bb; } /* Keyword.Declaration */
-  .kn { color: #8abeb7; } /* Keyword.Namespace */
-  .kp { color: #b294bb; } /* Keyword.Pseudo */
-  .kr { color: #b294bb; } /* Keyword.Reserved */
-  .kt { color: #f0c674; } /* Keyword.Type */
-  .ld { color: #b5bd68; } /* Literal.Date */
-  .m { color: #de935f; } /* Literal.Number */
-  .s { color: #b5bd68; } /* Literal.String */
-  .na { color: #81a2be; } /* Name.Attribute */
-  .nb { color: #c5c8c6; } /* Name.Builtin */
-  .nc { color: #f0c674; } /* Name.Class */
-  .no { color: #c66; } /* Name.Constant */
-  .nd { color: #8abeb7; } /* Name.Decorator */
-  .ni { color: #c5c8c6; } /* Name.Entity */
-  .ne { color: #c66; } /* Name.Exception */
-  .nf { color: #81a2be; } /* Name.Function */
-  .nl { color: #c5c8c6; } /* Name.Label */
-  .nn { color: #f0c674; } /* Name.Namespace */
-  .nx { color: #81a2be; } /* Name.Other */
-  .py { color: #c5c8c6; } /* Name.Property */
-  .nt { color: #8abeb7; } /* Name.Tag */
-  .nv { color: #c66; } /* Name.Variable */
-  .ow { color: #8abeb7; } /* Operator.Word */
-  .w { color: #c5c8c6; } /* Text.Whitespace */
-  .mf { color: #de935f; } /* Literal.Number.Float */
-  .mh { color: #de935f; } /* Literal.Number.Hex */
-  .mi { color: #de935f; } /* Literal.Number.Integer */
-  .mo { color: #de935f; } /* Literal.Number.Oct */
-  .sb { color: #b5bd68; } /* Literal.String.Backtick */
-  .sc { color: #c5c8c6; } /* Literal.String.Char */
-  .sd { color: #969896; } /* Literal.String.Doc */
-  .s2 { color: #b5bd68; } /* Literal.String.Double */
-  .se { color: #de935f; } /* Literal.String.Escape */
-  .sh { color: #b5bd68; } /* Literal.String.Heredoc */
-  .si { color: #de935f; } /* Literal.String.Interpol */
-  .sx { color: #b5bd68; } /* Literal.String.Other */
-  .sr { color: #b5bd68; } /* Literal.String.Regex */
-  .s1 { color: #b5bd68; } /* Literal.String.Single */
-  .ss { color: #b5bd68; } /* Literal.String.Symbol */
-  .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */
-  .vc { color: #c66; } /* Name.Variable.Class */
-  .vg { color: #c66; } /* Name.Variable.Global */
-  .vi { color: #c66; } /* Name.Variable.Instance */
-  .il { color: #de935f; } /* Literal.Number.Integer.Long */
+  .gu { color: $dark-gu; font-weight: bold; } /* Generic.Subheading */
+  .kc { color: $dark-kc; } /* Keyword.Constant */
+  .kd { color: $dark-kd; } /* Keyword.Declaration */
+  .kn { color: $dark-kn; } /* Keyword.Namespace */
+  .kp { color: $dark-kp; } /* Keyword.Pseudo */
+  .kr { color: $dark-kr; } /* Keyword.Reserved */
+  .kt { color: $dark-kt; } /* Keyword.Type */
+  .ld { color: $dark-ld; } /* Literal.Date */
+  .m { color: $dark-m; } /* Literal.Number */
+  .s { color: $dark-s; } /* Literal.String */
+  .na { color: $dark-na; } /* Name.Attribute */
+  .nb { color: $dark-nb; } /* Name.Builtin */
+  .nc { color: $dark-nc; } /* Name.Class */
+  .no { color: $dark-no; } /* Name.Constant */
+  .nd { color: $dark-nd; } /* Name.Decorator */
+  .ni { color: $dark-ni; } /* Name.Entity */
+  .ne { color: $dark-ne; } /* Name.Exception */
+  .nf { color: $dark-nf; } /* Name.Function */
+  .nl { color: $dark-nl; } /* Name.Label */
+  .nn { color: $dark-nn; } /* Name.Namespace */
+  .nx { color: $dark-nx; } /* Name.Other */
+  .py { color: $dark-py; } /* Name.Property */
+  .nt { color: $dark-nt; } /* Name.Tag */
+  .nv { color: $dark-nv; } /* Name.Variable */
+  .ow { color: $dark-ow; } /* Operator.Word */
+  .w { color: $dark-w; } /* Text.Whitespace */
+  .mf { color: $dark-mf; } /* Literal.Number.Float */
+  .mh { color: $dark-mh; } /* Literal.Number.Hex */
+  .mi { color: $dark-mi; } /* Literal.Number.Integer */
+  .mo { color: $dark-mo; } /* Literal.Number.Oct */
+  .sb { color: $dark-sb; } /* Literal.String.Backtick */
+  .sc { color: $dark-sc; } /* Literal.String.Char */
+  .sd { color: $dark-sd; } /* Literal.String.Doc */
+  .s2 { color: $dark-s2; } /* Literal.String.Double */
+  .se { color: $dark-se; } /* Literal.String.Escape */
+  .sh { color: $dark-sh; } /* Literal.String.Heredoc */
+  .si { color: $dark-si; } /* Literal.String.Interpol */
+  .sx { color: $dark-sx; } /* Literal.String.Other */
+  .sr { color: $dark-sr; } /* Literal.String.Regex */
+  .s1 { color: $dark-s1; } /* Literal.String.Single */
+  .ss { color: $dark-ss; } /* Literal.String.Symbol */
+  .bp { color: $dark-bp; } /* Name.Builtin.Pseudo */
+  .vc { color: $dark-vc; } /* Name.Variable.Class */
+  .vg { color: $dark-vg; } /* Name.Variable.Global */
+  .vi { color: $dark-vi; } /* Name.Variable.Instance */
+  .il { color: $dark-il; } /* Literal.Number.Integer.Long */
 }
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index db8da8aab104c56e4fc71a3bdd1b7e098ef7f350..d8510baad8a5fa9d8953b103dd97db97c6ff96d9 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,27 +1,108 @@
 /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
+
+/*
+* Monokai Colors
+*/
+$monokai-bg: #272822;
+$monokai-border: #555;
+$monokai-text-color: #f8f8f2;
+$monokai-line-num-color: rgba(255, 255, 255, 0.3);
+$monokai-line-empty-bg: #49483e;
+$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
+$monokai-diff-border: #808080;
+$monokai-highlight-bg: #ffe792;
+
+$monokai-new-bg: rgba(166, 226, 46, 0.1);
+$monokai-new-idiff: rgba(166, 226, 46, 0.15);
+
+$monokai-old-bg: rgba(254, 147, 140, 0.15);
+$monokai-old-idiff: rgba(254, 147, 140, 0.2);
+
+$monokai-hll: #49483e;
+$monokai-c: #75715e;
+$monokai-err-color: #960050;
+$monokai-err-bg: #1e0010;
+$monokai-k: #66d9ef;
+$monokai-l: #ae81ff;
+$monokai-n: #f8f8f2;
+$monokai-o: #f92672;
+$monokai-p: #f8f8f2;
+$monokai-cm: #75715e;
+$monokai-cp: #75715e;
+$monokai-c1: #75715e;
+$monokai-cs: #75715e;
+$monokai-kc: #66d9ef;
+$monokai-kd: #66d9ef;
+$monokai-kn: #f92672;
+$monokai-kp: #66d9ef;
+$monokai-kr: #66d9ef;
+$monokai-kt: #66d9ef;
+$monokai-ld: #e6db74;
+$monokai-m: #ae81ff;
+$monokai-s: #e6db74;
+$monokai-na: #a6e22e;
+$monokai-nb: #f8f8f2;
+$monokai-nc: #a6e22e;
+$monokai-no: #66d9ef;
+$monokai-nd: #a6e22e;
+$monokai-ni: #f8f8f2;
+$monokai-ne: #a6e22e;
+$monokai-nf: #a6e22e;
+$monokai-nl: #f8f8f2;
+$monokai-nn: #f8f8f2;
+$monokai-nx: #a6e22e;
+$monokai-py: #f8f8f2;
+$monokai-nt: #f92672;
+$monokai-nv: #f8f8f2;
+$monokai-ow: #f92672;
+$monokai-w: #f8f8f2;
+$monokai-mf: #ae81ff;
+$monokai-mh: #ae81ff;
+$monokai-mi: #ae81ff;
+$monokai-mo: #ae81ff;
+$monokai-sb: #e6db74;
+$monokai-sc: #e6db74;
+$monokai-sd: #e6db74;
+$monokai-s2: #e6db74;
+$monokai-se: #ae81ff;
+$monokai-sh: #e6db74;
+$monokai-si: #e6db74;
+$monokai-sx: #e6db74;
+$monokai-sr: #e6db74;
+$monokai-s1: #e6db74;
+$monokai-ss: #e6db74;
+$monokai-bp: #f8f8f2;
+$monokai-vc: #f8f8f2;
+$monokai-vg: #f8f8f2;
+$monokai-vi: #f8f8f2;
+$monokai-il: #ae81ff;
+$monokai-gu: #75715e;
+$monokai-gd: #f92672;
+$monokai-gi: #a6e22e;
+
 .code.monokai {
   // Line numbers
   .line-numbers,
   .diff-line-num {
-    background-color: #272822;
+    background-color: $monokai-bg;
   }
 
   .diff-line-num,
   .diff-line-num a {
-    color: rgba(255, 255, 255, 0.3);
+    color: $monokai-line-num-color;
   }
 
   // Code itself
   pre.code,
   .diff-line-num {
-    border-color: #555;
+    border-color: $monokai-border;
   }
 
   &,
   pre.code,
   .line_holder .line_content {
-    background-color: #272822;
-    color: #f8f8f2;
+    background-color: $monokai-bg;
+    color: $monokai-text-color;
   }
 
   // Diff line
@@ -32,18 +113,18 @@
 
     td.diff-line-num.hll:not(.empty-cell),
     td.line_content.hll:not(.empty-cell) {
-      background-color: #49483e;
-      border-color: darken(#49483e, 15%);
+      background-color: $monokai-line-empty-bg;
+      border-color: $monokai-line-empty-border;
     }
 
     .diff-line-num.new,
     .line_content.new {
-      @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
+      @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
     }
 
     .diff-line-num.old,
     .line_content.old {
-      @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080);
+      @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
     }
 
     .line_content.match {
@@ -53,75 +134,75 @@
 
   // highlight line via anchor
   pre .hll {
-    background-color: #49483e !important;
+    background-color: $monokai-hll !important;
   }
 
   // Search result highlight
   span.highlight_word {
-    background-color: #ffe792 !important;
-    color: #000 !important;
+    background-color: $monokai-highlight-bg !important;
+    color: $black !important;
   }
 
-  .hll { background-color: #49483e; }
-  .c { color: #75715e; } /* Comment */
-  .err { color: #960050; background-color: #1e0010; } /* Error */
-  .k { color: #66d9ef; } /* Keyword */
-  .l { color: #ae81ff; } /* Literal */
-  .n { color: #f8f8f2; } /* Name */
-  .o { color: #f92672; } /* Operator */
-  .p { color: #f8f8f2; } /* Punctuation */
-  .cm { color: #75715e; } /* Comment.Multiline */
-  .cp { color: #75715e; } /* Comment.Preproc */
-  .c1 { color: #75715e; } /* Comment.Single */
-  .cs { color: #75715e; } /* Comment.Special */
+  .hll { background-color: $monokai-hll; }
+  .c { color: $monokai-c; } /* Comment */
+  .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */
+  .k { color: $monokai-k; } /* Keyword */
+  .l { color: $monokai-l; } /* Literal */
+  .n { color: $monokai-n; } /* Name */
+  .o { color: $monokai-o; } /* Operator */
+  .p { color: $monokai-p; } /* Punctuation */
+  .cm { color: $monokai-cm; } /* Comment.Multiline */
+  .cp { color: $monokai-cp; } /* Comment.Preproc */
+  .c1 { color: $monokai-c1; } /* Comment.Single */
+  .cs { color: $monokai-cs; } /* Comment.Special */
   .ge { font-style: italic; } /* Generic.Emph */
   .gs { font-weight: bold; } /* Generic.Strong */
-  .kc { color: #66d9ef; } /* Keyword.Constant */
-  .kd { color: #66d9ef; } /* Keyword.Declaration */
-  .kn { color: #f92672; } /* Keyword.Namespace */
-  .kp { color: #66d9ef; } /* Keyword.Pseudo */
-  .kr { color: #66d9ef; } /* Keyword.Reserved */
-  .kt { color: #66d9ef; } /* Keyword.Type */
-  .ld { color: #e6db74; } /* Literal.Date */
-  .m { color: #ae81ff; } /* Literal.Number */
-  .s { color: #e6db74; } /* Literal.String */
-  .na { color: #a6e22e; } /* Name.Attribute */
-  .nb { color: #f8f8f2; } /* Name.Builtin */
-  .nc { color: #a6e22e; } /* Name.Class */
-  .no { color: #66d9ef; } /* Name.Constant */
-  .nd { color: #a6e22e; } /* Name.Decorator */
-  .ni { color: #f8f8f2; } /* Name.Entity */
-  .ne { color: #a6e22e; } /* Name.Exception */
-  .nf { color: #a6e22e; } /* Name.Function */
-  .nl { color: #f8f8f2; } /* Name.Label */
-  .nn { color: #f8f8f2; } /* Name.Namespace */
-  .nx { color: #a6e22e; } /* Name.Other */
-  .py { color: #f8f8f2; } /* Name.Property */
-  .nt { color: #f92672; } /* Name.Tag */
-  .nv { color: #f8f8f2; } /* Name.Variable */
-  .ow { color: #f92672; } /* Operator.Word */
-  .w { color: #f8f8f2; } /* Text.Whitespace */
-  .mf { color: #ae81ff; } /* Literal.Number.Float */
-  .mh { color: #ae81ff; } /* Literal.Number.Hex */
-  .mi { color: #ae81ff; } /* Literal.Number.Integer */
-  .mo { color: #ae81ff; } /* Literal.Number.Oct */
-  .sb { color: #e6db74; } /* Literal.String.Backtick */
-  .sc { color: #e6db74; } /* Literal.String.Char */
-  .sd { color: #e6db74; } /* Literal.String.Doc */
-  .s2 { color: #e6db74; } /* Literal.String.Double */
-  .se { color: #ae81ff; } /* Literal.String.Escape */
-  .sh { color: #e6db74; } /* Literal.String.Heredoc */
-  .si { color: #e6db74; } /* Literal.String.Interpol */
-  .sx { color: #e6db74; } /* Literal.String.Other */
-  .sr { color: #e6db74; } /* Literal.String.Regex */
-  .s1 { color: #e6db74; } /* Literal.String.Single */
-  .ss { color: #e6db74; } /* Literal.String.Symbol */
-  .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */
-  .vc { color: #f8f8f2; } /* Name.Variable.Class */
-  .vg { color: #f8f8f2; } /* Name.Variable.Global */
-  .vi { color: #f8f8f2; } /* Name.Variable.Instance */
-  .il { color: #ae81ff; } /* Literal.Number.Integer.Long */
-  .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
-  .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
-  .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
+  .kc { color: $monokai-kc; } /* Keyword.Constant */
+  .kd { color: $monokai-kd; } /* Keyword.Declaration */
+  .kn { color: $monokai-kn; } /* Keyword.Namespace */
+  .kp { color: $monokai-kp; } /* Keyword.Pseudo */
+  .kr { color: $monokai-kr; } /* Keyword.Reserved */
+  .kt { color: $monokai-kt; } /* Keyword.Type */
+  .ld { color: $monokai-ld; } /* Literal.Date */
+  .m { color: $monokai-m; } /* Literal.Number */
+  .s { color: $monokai-s; } /* Literal.String */
+  .na { color: $monokai-na; } /* Name.Attribute */
+  .nb { color: $monokai-nb; } /* Name.Builtin */
+  .nc { color: $monokai-nc; } /* Name.Class */
+  .no { color: $monokai-no; } /* Name.Constant */
+  .nd { color: $monokai-nd; } /* Name.Decorator */
+  .ni { color: $monokai-ni; } /* Name.Entity */
+  .ne { color: $monokai-ne; } /* Name.Exception */
+  .nf { color: $monokai-nf; } /* Name.Function */
+  .nl { color: $monokai-nl; } /* Name.Label */
+  .nn { color: $monokai-nn; } /* Name.Namespace */
+  .nx { color: $monokai-nx; } /* Name.Other */
+  .py { color: $monokai-py; } /* Name.Property */
+  .nt { color: $monokai-nt; } /* Name.Tag */
+  .nv { color: $monokai-nv; } /* Name.Variable */
+  .ow { color: $monokai-ow; } /* Operator.Word */
+  .w { color: $monokai-w; } /* Text.Whitespace */
+  .mf { color: $monokai-mf; } /* Literal.Number.Float */
+  .mh { color: $monokai-mh; } /* Literal.Number.Hex */
+  .mi { color: $monokai-mi; } /* Literal.Number.Integer */
+  .mo { color: $monokai-mo; } /* Literal.Number.Oct */
+  .sb { color: $monokai-sb; } /* Literal.String.Backtick */
+  .sc { color: $monokai-sc; } /* Literal.String.Char */
+  .sd { color: $monokai-sd; } /* Literal.String.Doc */
+  .s2 { color: $monokai-s2; } /* Literal.String.Double */
+  .se { color: $monokai-se; } /* Literal.String.Escape */
+  .sh { color: $monokai-sh; } /* Literal.String.Heredoc */
+  .si { color: $monokai-si; } /* Literal.String.Interpol */
+  .sx { color: $monokai-sx; } /* Literal.String.Other */
+  .sr { color: $monokai-sr; } /* Literal.String.Regex */
+  .s1 { color: $monokai-s1; } /* Literal.String.Single */
+  .ss { color: $monokai-ss; } /* Literal.String.Symbol */
+  .bp { color: $monokai-bp; } /* Name.Builtin.Pseudo */
+  .vc { color: $monokai-vc; } /* Name.Variable.Class */
+  .vg { color: $monokai-vg; } /* Name.Variable.Global */
+  .vi { color: $monokai-vi; } /* Name.Variable.Instance */
+  .il { color: $monokai-il; } /* Literal.Number.Integer.Long */
+  .gu { color: $monokai-gu; } /* Generic.Subheading & Diff Unified/Comment? */
+  .gd { color: $monokai-gd; } /* Generic.Deleted & Diff Deleted */
+  .gi { color: $monokai-gi; } /* Generic.Inserted & Diff Inserted */
 }
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index a87333146de1b2b38f9ff80bea7ffe11f6b6b49c..874aecb5e16945e4c257d2e384344f59657f6f82 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,27 +1,112 @@
 /* https://gist.github.com/qguv/7936275 */
+
+/*
+* Solarized dark colors
+*/
+$solarized-dark-new-bg: rgba(133, 153, 0, 0.15);
+$solarized-dark-new-idiff: rgba(133, 153, 0, 0.25);
+$solarized-dark-old-bg: rgba(220, 50, 47, 0.3);
+$solarized-dark-old-idiff: rgba(220, 50, 47, 0.25);
+$solarized-dark-border: #113b46;
+$solarized-dark-pre-bg: #002b36;
+$solarized-dark-pre-color: #93a1a1;
+$solarized-dark-pre-border: #113b46;
+$solarized-dark-line-bg: #002b36;
+$solarized-dark-line-color: rgba(255, 255, 255, 0.3);
+$solarized-dark-highlight: #094554;
+$solarized-dark-hll-bg: #174652;
+$solarized-dark-c: #586e75;
+$solarized-dark-err: #93a1a1;
+$solarized-dark-g: #93a1a1;
+$solarized-dark-k: #859900;
+$solarized-dark-l: #93a1a1;
+$solarized-dark-n: #93a1a1;
+$solarized-dark-o: #859900;
+$solarized-dark-x: #cb4b16;
+$solarized-dark-p: #93a1a1;
+$solarized-dark-cm: #586e75;
+$solarized-dark-cp: #859900;
+$solarized-dark-c1: #586e75;
+$solarized-dark-cs: #859900;
+$solarized-dark-gd: #2aa198;
+$solarized-dark-ge: #93a1a1;
+$solarized-dark-gr: #dc322f;
+$solarized-dark-gh: #cb4b16;
+$solarized-dark-gi: #859900;
+$solarized-dark-go: #93a1a1;
+$solarized-dark-gp: #93a1a1;
+$solarized-dark-gs: #93a1a1;
+$solarized-dark-gu: #cb4b16;
+$solarized-dark-gt: #93a1a1;
+$solarized-dark-kc: #cb4b16;
+$solarized-dark-kd: #268bd2;
+$solarized-dark-kn: #859900;
+$solarized-dark-kp: #859900;
+$solarized-dark-kr: #268bd2;
+$solarized-dark-kt: #dc322f;
+$solarized-dark-ld: #93a1a1;
+$solarized-dark-m: #2aa198;
+$solarized-dark-s: #2aa198;
+$solarized-dark-na: #93a1a1;
+$solarized-dark-nb: #b58900;
+$solarized-dark-nc: #268bd2;
+$solarized-dark-no: #cb4b16;
+$solarized-dark-nd: #268bd2;
+$solarized-dark-ni: #cb4b16;
+$solarized-dark-ne: #cb4b16;
+$solarized-dark-nf: #268bd2;
+$solarized-dark-nl: #93a1a1;
+$solarized-dark-nn: #93a1a1;
+$solarized-dark-nx: #93a1a1;
+$solarized-dark-py: #93a1a1;
+$solarized-dark-nt: #268bd2;
+$solarized-dark-nv: #268bd2;
+$solarized-dark-ow: #859900;
+$solarized-dark-w: #93a1a1;
+$solarized-dark-mf: #2aa198;
+$solarized-dark-mh: #2aa198;
+$solarized-dark-mi: #2aa198;
+$solarized-dark-mo: #2aa198;
+$solarized-dark-sb: #586e75;
+$solarized-dark-sc: #2aa198;
+$solarized-dark-sd: #93a1a1;
+$solarized-dark-s2: #2aa198;
+$solarized-dark-se: #cb4b16;
+$solarized-dark-sh: #93a1a1;
+$solarized-dark-si: #2aa198;
+$solarized-dark-sx: #2aa198;
+$solarized-dark-sr: #dc322f;
+$solarized-dark-s1: #2aa198;
+$solarized-dark-ss: #2aa198;
+$solarized-dark-bp: #268bd2;
+$solarized-dark-vc: #268bd2;
+$solarized-dark-vg: #268bd2;
+$solarized-dark-vi: #268bd2;
+$solarized-dark-il: #2aa198;
+
 .code.solarized-dark {
   // Line numbers
   .line-numbers,
   .diff-line-num {
-    background-color: #002b36;
+    background-color: $solarized-dark-line-bg;
   }
 
   .diff-line-num,
   .diff-line-num a {
-    color: rgba(255, 255, 255, 0.3);
+    color: $solarized-dark-line-color;
   }
 
   // Code itself
   pre.code,
   .diff-line-num {
-    border-color: #113b46;
+    border-color: $solarized-dark-pre-border;
   }
 
   &,
   pre.code,
   .line_holder .line_content {
-    background-color: #002b36;
-    color: #93a1a1;
+    background-color: $solarized-dark-pre-bg;
+    color: $solarized-dark-pre-color;
   }
 
   // Diff line
@@ -32,18 +117,18 @@
 
     td.diff-line-num.hll:not(.empty-cell),
     td.line_content.hll:not(.empty-cell) {
-      background-color: #174652;
-      border-color: darken(#174652, 15%);
+      background-color: $solarized-dark-hll-bg;
+      border-color: darken($solarized-dark-hll-bg, 15%);
     }
 
     .diff-line-num.new,
     .line_content.new {
-      @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
+      @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
     }
 
     .diff-line-num.old,
     .line_content.old {
-      @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46);
+      @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
     }
 
     .line_content.match {
@@ -53,12 +138,12 @@
 
   // highlight line via anchor
   pre .hll {
-    background-color: #174652 !important;
+    background-color: $solarized-dark-hll-bg !important;
   }
 
   // Search result highlight
   span.highlight_word {
-    background-color: #094554 !important;
+    background-color: $solarized-dark-highlight !important;
   }
 
   /* Solarized Dark
@@ -79,72 +164,72 @@
   green     #859900  operators, other keywords
   */
 
-  .c { color: #586e75; } /* Comment */
-  .err { color: #93a1a1; } /* Error */
-  .g { color: #93a1a1; } /* Generic */
-  .k { color: #859900; } /* Keyword */
-  .l { color: #93a1a1; } /* Literal */
-  .n { color: #93a1a1; } /* Name */
-  .o { color: #859900; } /* Operator */
-  .x { color: #cb4b16; } /* Other */
-  .p { color: #93a1a1; } /* Punctuation */
-  .cm { color: #586e75; } /* Comment.Multiline */
-  .cp { color: #859900; } /* Comment.Preproc */
-  .c1 { color: #586e75; } /* Comment.Single */
-  .cs { color: #859900; } /* Comment.Special */
-  .gd { color: #2aa198; } /* Generic.Deleted */
-  .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */
-  .gr { color: #dc322f; } /* Generic.Error */
-  .gh { color: #cb4b16; } /* Generic.Heading */
-  .gi { color: #859900; } /* Generic.Inserted */
-  .go { color: #93a1a1; } /* Generic.Output */
-  .gp { color: #93a1a1; } /* Generic.Prompt */
-  .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */
-  .gu { color: #cb4b16; } /* Generic.Subheading */
-  .gt { color: #93a1a1; } /* Generic.Traceback */
-  .kc { color: #cb4b16; } /* Keyword.Constant */
-  .kd { color: #268bd2; } /* Keyword.Declaration */
-  .kn { color: #859900; } /* Keyword.Namespace */
-  .kp { color: #859900; } /* Keyword.Pseudo */
-  .kr { color: #268bd2; } /* Keyword.Reserved */
-  .kt { color: #dc322f; } /* Keyword.Type */
-  .ld { color: #93a1a1; } /* Literal.Date */
-  .m { color: #2aa198; } /* Literal.Number */
-  .s { color: #2aa198; } /* Literal.String */
-  .na { color: #93a1a1; } /* Name.Attribute */
-  .nb { color: #b58900; } /* Name.Builtin */
-  .nc { color: #268bd2; } /* Name.Class */
-  .no { color: #cb4b16; } /* Name.Constant */
-  .nd { color: #268bd2; } /* Name.Decorator */
-  .ni { color: #cb4b16; } /* Name.Entity */
-  .ne { color: #cb4b16; } /* Name.Exception */
-  .nf { color: #268bd2; } /* Name.Function */
-  .nl { color: #93a1a1; } /* Name.Label */
-  .nn { color: #93a1a1; } /* Name.Namespace */
-  .nx { color: #93a1a1; } /* Name.Other */
-  .py { color: #93a1a1; } /* Name.Property */
-  .nt { color: #268bd2; } /* Name.Tag */
-  .nv { color: #268bd2; } /* Name.Variable */
-  .ow { color: #859900; } /* Operator.Word */
-  .w { color: #93a1a1; } /* Text.Whitespace */
-  .mf { color: #2aa198; } /* Literal.Number.Float */
-  .mh { color: #2aa198; } /* Literal.Number.Hex */
-  .mi { color: #2aa198; } /* Literal.Number.Integer */
-  .mo { color: #2aa198; } /* Literal.Number.Oct */
-  .sb { color: #586e75; } /* Literal.String.Backtick */
-  .sc { color: #2aa198; } /* Literal.String.Char */
-  .sd { color: #93a1a1; } /* Literal.String.Doc */
-  .s2 { color: #2aa198; } /* Literal.String.Double */
-  .se { color: #cb4b16; } /* Literal.String.Escape */
-  .sh { color: #93a1a1; } /* Literal.String.Heredoc */
-  .si { color: #2aa198; } /* Literal.String.Interpol */
-  .sx { color: #2aa198; } /* Literal.String.Other */
-  .sr { color: #dc322f; } /* Literal.String.Regex */
-  .s1 { color: #2aa198; } /* Literal.String.Single */
-  .ss { color: #2aa198; } /* Literal.String.Symbol */
-  .bp { color: #268bd2; } /* Name.Builtin.Pseudo */
-  .vc { color: #268bd2; } /* Name.Variable.Class */
-  .vg { color: #268bd2; } /* Name.Variable.Global */
-  .vi { color: #268bd2; } /* Name.Variable.Instance */
-  .il { color: #2aa198; } /* Literal.Number.Integer.Long */
+  .c { color: $solarized-dark-c; } /* Comment */
+  .err { color: $solarized-dark-err; } /* Error */
+  .g { color: $solarized-dark-g; } /* Generic */
+  .k { color: $solarized-dark-k; } /* Keyword */
+  .l { color: $solarized-dark-l; } /* Literal */
+  .n { color: $solarized-dark-n; } /* Name */
+  .o { color: $solarized-dark-o; } /* Operator */
+  .x { color: $solarized-dark-x; } /* Other */
+  .p { color: $solarized-dark-p; } /* Punctuation */
+  .cm { color: $solarized-dark-cm; } /* Comment.Multiline */
+  .cp { color: $solarized-dark-cp; } /* Comment.Preproc */
+  .c1 { color: $solarized-dark-c1; } /* Comment.Single */
+  .cs { color: $solarized-dark-cs; } /* Comment.Special */
+  .gd { color: $solarized-dark-gd; } /* Generic.Deleted */
+  .ge { color: $solarized-dark-ge; font-style: italic; } /* Generic.Emph */
+  .gr { color: $solarized-dark-gr; } /* Generic.Error */
+  .gh { color: $solarized-dark-gh; } /* Generic.Heading */
+  .gi { color: $solarized-dark-gi; } /* Generic.Inserted */
+  .go { color: $solarized-dark-go; } /* Generic.Output */
+  .gp { color: $solarized-dark-gp; } /* Generic.Prompt */
+  .gs { color: $solarized-dark-gs; font-weight: bold; } /* Generic.Strong */
+  .gu { color: $solarized-dark-gu; } /* Generic.Subheading */
+  .gt { color: $solarized-dark-gt; } /* Generic.Traceback */
+  .kc { color: $solarized-dark-kc; } /* Keyword.Constant */
+  .kd { color: $solarized-dark-kd; } /* Keyword.Declaration */
+  .kn { color: $solarized-dark-kn; } /* Keyword.Namespace */
+  .kp { color: $solarized-dark-kp; } /* Keyword.Pseudo */
+  .kr { color: $solarized-dark-kr; } /* Keyword.Reserved */
+  .kt { color: $solarized-dark-kt; } /* Keyword.Type */
+  .ld { color: $solarized-dark-ld; } /* Literal.Date */
+  .m { color: $solarized-dark-m; } /* Literal.Number */
+  .s { color: $solarized-dark-s; } /* Literal.String */
+  .na { color: $solarized-dark-na; } /* Name.Attribute */
+  .nb { color: $solarized-dark-nb; } /* Name.Builtin */
+  .nc { color: $solarized-dark-nc; } /* Name.Class */
+  .no { color: $solarized-dark-no; } /* Name.Constant */
+  .nd { color: $solarized-dark-nd; } /* Name.Decorator */
+  .ni { color: $solarized-dark-ni; } /* Name.Entity */
+  .ne { color: $solarized-dark-ne; } /* Name.Exception */
+  .nf { color: $solarized-dark-nf; } /* Name.Function */
+  .nl { color: $solarized-dark-nl; } /* Name.Label */
+  .nn { color: $solarized-dark-nn; } /* Name.Namespace */
+  .nx { color: $solarized-dark-nx; } /* Name.Other */
+  .py { color: $solarized-dark-py; } /* Name.Property */
+  .nt { color: $solarized-dark-nt; } /* Name.Tag */
+  .nv { color: $solarized-dark-nv; } /* Name.Variable */
+  .ow { color: $solarized-dark-ow; } /* Operator.Word */
+  .w { color: $solarized-dark-w; } /* Text.Whitespace */
+  .mf { color: $solarized-dark-mf; } /* Literal.Number.Float */
+  .mh { color: $solarized-dark-mh; } /* Literal.Number.Hex */
+  .mi { color: $solarized-dark-mi; } /* Literal.Number.Integer */
+  .mo { color: $solarized-dark-mo; } /* Literal.Number.Oct */
+  .sb { color: $solarized-dark-sb; } /* Literal.String.Backtick */
+  .sc { color: $solarized-dark-sc; } /* Literal.String.Char */
+  .sd { color: $solarized-dark-sd; } /* Literal.String.Doc */
+  .s2 { color: $solarized-dark-s2; } /* Literal.String.Double */
+  .se { color: $solarized-dark-se; } /* Literal.String.Escape */
+  .sh { color: $solarized-dark-sh; } /* Literal.String.Heredoc */
+  .si { color: $solarized-dark-si; } /* Literal.String.Interpol */
+  .sx { color: $solarized-dark-sx; } /* Literal.String.Other */
+  .sr { color: $solarized-dark-sr; } /* Literal.String.Regex */
+  .s1 { color: $solarized-dark-s1; } /* Literal.String.Single */
+  .ss { color: $solarized-dark-ss; } /* Literal.String.Symbol */
+  .bp { color: $solarized-dark-bp; } /* Name.Builtin.Pseudo */
+  .vc { color: $solarized-dark-vc; } /* Name.Variable.Class */
+  .vg { color: $solarized-dark-vg; } /* Name.Variable.Global */
+  .vi { color: $solarized-dark-vi; } /* Name.Variable.Instance */
+  .il { color: $solarized-dark-il; } /* Literal.Number.Integer.Long */
 }
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index faff353ded7348ee676f10426c7828beed91564d..499a1c108b829da6ab186dfcd0329b7aadfe7ae5 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -1,15 +1,99 @@
 /* https://gist.github.com/qguv/7936275 */
 
+/*
+* Solarized light syntax colors
+*/
+$solarized-light-matchline-bg: rgba(255, 255, 255, 0.4);
+$solarized-light-new-bg: rgba(133, 153, 0, 0.2);
+$solarized-light-new-idiff: rgba(133, 153, 0, 0.25);
+$solarized-light-old-bg: rgba(220, 50, 47, 0.2);
+$solarized-light-old-idiff: rgba(220, 50, 47, 0.25);
+$solarized-light-border: #c5d0d4;
+$solarized-light-pre-bg: #002b36;
+$solarized-light-pre-bg: #fdf6e3;
+$solarized-light-pre-color: #586e75;
+$solarized-light-line-bg: #fdf6e3;
+$solarized-light-highlight: #eee8d5;
+$solarized-light-hll-bg: #ddd8c5;
+$solarized-light-c: #93a1a1;
+$solarized-light-err: #586e75;
+$solarized-light-g: #586e75;
+$solarized-light-k: #859900;
+$solarized-light-l: #586e75;
+$solarized-light-n: #586e75;
+$solarized-light-o: #859900;
+$solarized-light-x: #cb4b16;
+$solarized-light-p: #586e75;
+$solarized-light-cm: #93a1a1;
+$solarized-light-cp: #859900;
+$solarized-light-c1: #93a1a1;
+$solarized-light-cs: #859900;
+$solarized-light-gd: #2aa198;
+$solarized-light-ge: #586e75;
+$solarized-light-gr: #dc322f;
+$solarized-light-gh: #cb4b16;
+$solarized-light-gi: #859900;
+$solarized-light-go: #586e75;
+$solarized-light-gp: #586e75;
+$solarized-light-gs: #586e75;
+$solarized-light-gu: #cb4b16;
+$solarized-light-gt: #586e75;
+$solarized-light-kc: #cb4b16;
+$solarized-light-kd: #268bd2;
+$solarized-light-kn: #859900;
+$solarized-light-kp: #859900;
+$solarized-light-kr: #268bd2;
+$solarized-light-kt: #dc322f;
+$solarized-light-ld: #586e75;
+$solarized-light-m: #2aa198;
+$solarized-light-s: #2aa198;
+$solarized-light-na: #586e75;
+$solarized-light-nb: #b58900;
+$solarized-light-nc: #268bd2;
+$solarized-light-no: #cb4b16;
+$solarized-light-nd: #268bd2;
+$solarized-light-ni: #cb4b16;
+$solarized-light-ne: #cb4b16;
+$solarized-light-nf: #268bd2;
+$solarized-light-nl: #586e75;
+$solarized-light-nn: #586e75;
+$solarized-light-nx: #586e75;
+$solarized-light-py: #586e75;
+$solarized-light-nt: #268bd2;
+$solarized-light-nv: #268bd2;
+$solarized-light-ow: #859900;
+$solarized-light-w: #586e75;
+$solarized-light-mf: #2aa198;
+$solarized-light-mh: #2aa198;
+$solarized-light-mi: #2aa198;
+$solarized-light-mo: #2aa198;
+$solarized-light-sb: #93a1a1;
+$solarized-light-sc: #2aa198;
+$solarized-light-sd: #586e75;
+$solarized-light-s2: #2aa198;
+$solarized-light-se: #cb4b16;
+$solarized-light-sh: #586e75;
+$solarized-light-si: #2aa198;
+$solarized-light-sx: #2aa198;
+$solarized-light-sr: #dc322f;
+$solarized-light-s1: #2aa198;
+$solarized-light-ss: #2aa198;
+$solarized-light-bp: #268bd2;
+$solarized-light-vc: #268bd2;
+$solarized-light-vg: #268bd2;
+$solarized-light-vi: #268bd2;
+$solarized-light-il: #2aa198;
+
 @mixin matchLine {
   color: $black-transparent;
-  background: rgba(255, 255, 255, 0.4);
+  background: $solarized-light-matchline-bg;
 }
 
 .code.solarized-light {
   // Line numbers
   .line-numbers,
   .diff-line-num {
-    background-color: #fdf6e3;
+    background-color: $solarized-light-line-bg;
   }
 
   .diff-line-num,
@@ -20,14 +104,14 @@
   // Code itself
   pre.code,
   .diff-line-num {
-    border-color: #c5d0d4;
+    border-color: $solarized-light-border;
   }
 
   &,
   pre.code,
   .line_holder .line_content {
-    background-color: #fdf6e3;
-    color: #586e75;
+    background-color: $solarized-light-pre-bg;
+    color: $solarized-light-pre-color;
   }
 
   // Diff line
@@ -38,18 +122,19 @@
 
     td.diff-line-num.hll:not(.empty-cell),
     td.line_content.hll:not(.empty-cell) {
-      background-color: #ddd8c5;
-      border-color: darken(#ddd8c5, 15%);
+      background-color: $solarized-light-hll-bg;
+      border-color: darken($solarized-light-hll-bg, 15%);
     }
 
     .diff-line-num.new,
     .line_content.new {
-      @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
+      @include diff_background($solarized-light-new-bg,
+      $solarized-light-new-idiff, $solarized-light-border);
     }
 
     .diff-line-num.old,
     .line_content.old {
-      @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4);
+      @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
     }
 
     .line_content.match {
@@ -59,12 +144,12 @@
 
   // highlight line via anchor
   pre .hll {
-    background-color: #ddd8c5 !important;
+    background-color: $solarized-light-hll-bg !important;
   }
 
   // Search result highlight
   span.highlight_word {
-    background-color: #eee8d5 !important;
+    background-color: $solarized-light-highlight !important;
   }
 
   /* Solarized Light
@@ -85,72 +170,72 @@
   green     #859900  operators, other keywords
   */
 
-  .c { color: #93a1a1; } /* Comment */
-  .err { color: #586e75; } /* Error */
-  .g { color: #586e75; } /* Generic */
-  .k { color: #859900; } /* Keyword */
-  .l { color: #586e75; } /* Literal */
-  .n { color: #586e75; } /* Name */
-  .o { color: #859900; } /* Operator */
-  .x { color: #cb4b16; } /* Other */
-  .p { color: #586e75; } /* Punctuation */
-  .cm { color: #93a1a1; } /* Comment.Multiline */
-  .cp { color: #859900; } /* Comment.Preproc */
-  .c1 { color: #93a1a1; } /* Comment.Single */
-  .cs { color: #859900; } /* Comment.Special */
-  .gd { color: #2aa198; } /* Generic.Deleted */
-  .ge { color: #586e75; font-style: italic; } /* Generic.Emph */
-  .gr { color: #dc322f; } /* Generic.Error */
-  .gh { color: #cb4b16; } /* Generic.Heading */
-  .gi { color: #859900; } /* Generic.Inserted */
-  .go { color: #586e75; } /* Generic.Output */
-  .gp { color: #586e75; } /* Generic.Prompt */
-  .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */
-  .gu { color: #cb4b16; } /* Generic.Subheading */
-  .gt { color: #586e75; } /* Generic.Traceback */
-  .kc { color: #cb4b16; } /* Keyword.Constant */
-  .kd { color: #268bd2; } /* Keyword.Declaration */
-  .kn { color: #859900; } /* Keyword.Namespace */
-  .kp { color: #859900; } /* Keyword.Pseudo */
-  .kr { color: #268bd2; } /* Keyword.Reserved */
-  .kt { color: #dc322f; } /* Keyword.Type */
-  .ld { color: #586e75; } /* Literal.Date */
-  .m { color: #2aa198; } /* Literal.Number */
-  .s { color: #2aa198; } /* Literal.String */
-  .na { color: #586e75; } /* Name.Attribute */
-  .nb { color: #b58900; } /* Name.Builtin */
-  .nc { color: #268bd2; } /* Name.Class */
-  .no { color: #cb4b16; } /* Name.Constant */
-  .nd { color: #268bd2; } /* Name.Decorator */
-  .ni { color: #cb4b16; } /* Name.Entity */
-  .ne { color: #cb4b16; } /* Name.Exception */
-  .nf { color: #268bd2; } /* Name.Function */
-  .nl { color: #586e75; } /* Name.Label */
-  .nn { color: #586e75; } /* Name.Namespace */
-  .nx { color: #586e75; } /* Name.Other */
-  .py { color: #586e75; } /* Name.Property */
-  .nt { color: #268bd2; } /* Name.Tag */
-  .nv { color: #268bd2; } /* Name.Variable */
-  .ow { color: #859900; } /* Operator.Word */
-  .w { color: #586e75; } /* Text.Whitespace */
-  .mf { color: #2aa198; } /* Literal.Number.Float */
-  .mh { color: #2aa198; } /* Literal.Number.Hex */
-  .mi { color: #2aa198; } /* Literal.Number.Integer */
-  .mo { color: #2aa198; } /* Literal.Number.Oct */
-  .sb { color: #93a1a1; } /* Literal.String.Backtick */
-  .sc { color: #2aa198; } /* Literal.String.Char */
-  .sd { color: #586e75; } /* Literal.String.Doc */
-  .s2 { color: #2aa198; } /* Literal.String.Double */
-  .se { color: #cb4b16; } /* Literal.String.Escape */
-  .sh { color: #586e75; } /* Literal.String.Heredoc */
-  .si { color: #2aa198; } /* Literal.String.Interpol */
-  .sx { color: #2aa198; } /* Literal.String.Other */
-  .sr { color: #dc322f; } /* Literal.String.Regex */
-  .s1 { color: #2aa198; } /* Literal.String.Single */
-  .ss { color: #2aa198; } /* Literal.String.Symbol */
-  .bp { color: #268bd2; } /* Name.Builtin.Pseudo */
-  .vc { color: #268bd2; } /* Name.Variable.Class */
-  .vg { color: #268bd2; } /* Name.Variable.Global */
-  .vi { color: #268bd2; } /* Name.Variable.Instance */
-  .il { color: #2aa198; } /* Literal.Number.Integer.Long */
+  .c { color: $solarized-light-c; } /* Comment */
+  .err { color: $solarized-light-err; } /* Error */
+  .g { color: $solarized-light-g; } /* Generic */
+  .k { color: $solarized-light-k; } /* Keyword */
+  .l { color: $solarized-light-l; } /* Literal */
+  .n { color: $solarized-light-n; } /* Name */
+  .o { color: $solarized-light-o; } /* Operator */
+  .x { color: $solarized-light-x; } /* Other */
+  .p { color: $solarized-light-p; } /* Punctuation */
+  .cm { color: $solarized-light-cm; } /* Comment.Multiline */
+  .cp { color: $solarized-light-cp; } /* Comment.Preproc */
+  .c1 { color: $solarized-light-c1; } /* Comment.Single */
+  .cs { color: $solarized-light-cs; } /* Comment.Special */
+  .gd { color: $solarized-light-gd; } /* Generic.Deleted */
+  .ge { color: $solarized-light-ge; font-style: italic; } /* Generic.Emph */
+  .gr { color: $solarized-light-gr; } /* Generic.Error */
+  .gh { color: $solarized-light-gh; } /* Generic.Heading */
+  .gi { color: $solarized-light-gi; } /* Generic.Inserted */
+  .go { color: $solarized-light-go; } /* Generic.Output */
+  .gp { color: $solarized-light-gp; } /* Generic.Prompt */
+  .gs { color: $solarized-light-gs; font-weight: bold; } /* Generic.Strong */
+  .gu { color: $solarized-light-gu; } /* Generic.Subheading */
+  .gt { color: $solarized-light-gt; } /* Generic.Traceback */
+  .kc { color: $solarized-light-kc; } /* Keyword.Constant */
+  .kd { color: $solarized-light-kd; } /* Keyword.Declaration */
+  .kn { color: $solarized-light-kn; } /* Keyword.Namespace */
+  .kp { color: $solarized-light-kp; } /* Keyword.Pseudo */
+  .kr { color: $solarized-light-kr; } /* Keyword.Reserved */
+  .kt { color: $solarized-light-kt; } /* Keyword.Type */
+  .ld { color: $solarized-light-ld; } /* Literal.Date */
+  .m { color: $solarized-light-m; } /* Literal.Number */
+  .s { color: $solarized-light-s; } /* Literal.String */
+  .na { color: $solarized-light-na; } /* Name.Attribute */
+  .nb { color: $solarized-light-nb; } /* Name.Builtin */
+  .nc { color: $solarized-light-nc; } /* Name.Class */
+  .no { color: $solarized-light-no; } /* Name.Constant */
+  .nd { color: $solarized-light-nd; } /* Name.Decorator */
+  .ni { color: $solarized-light-ni; } /* Name.Entity */
+  .ne { color: $solarized-light-ne; } /* Name.Exception */
+  .nf { color: $solarized-light-nf; } /* Name.Function */
+  .nl { color: $solarized-light-nl; } /* Name.Label */
+  .nn { color: $solarized-light-nn; } /* Name.Namespace */
+  .nx { color: $solarized-light-nx; } /* Name.Other */
+  .py { color: $solarized-light-py; } /* Name.Property */
+  .nt { color: $solarized-light-nt; } /* Name.Tag */
+  .nv { color: $solarized-light-nv; } /* Name.Variable */
+  .ow { color: $solarized-light-ow; } /* Operator.Word */
+  .w { color: $solarized-light-w; } /* Text.Whitespace */
+  .mf { color: $solarized-light-mf; } /* Literal.Number.Float */
+  .mh { color: $solarized-light-mh; } /* Literal.Number.Hex */
+  .mi { color: $solarized-light-mi; } /* Literal.Number.Integer */
+  .mo { color: $solarized-light-mo; } /* Literal.Number.Oct */
+  .sb { color: $solarized-light-sb; } /* Literal.String.Backtick */
+  .sc { color: $solarized-light-sc; } /* Literal.String.Char */
+  .sd { color: $solarized-light-sd; } /* Literal.String.Doc */
+  .s2 { color: $solarized-light-s2; } /* Literal.String.Double */
+  .se { color: $solarized-light-se; } /* Literal.String.Escape */
+  .sh { color: $solarized-light-sh; } /* Literal.String.Heredoc */
+  .si { color: $solarized-light-si; } /* Literal.String.Interpol */
+  .sx { color: $solarized-light-sx; } /* Literal.String.Other */
+  .sr { color: $solarized-light-sr; } /* Literal.String.Regex */
+  .s1 { color: $solarized-light-s1; } /* Literal.String.Single */
+  .ss { color: $solarized-light-ss; } /* Literal.String.Symbol */
+  .bp { color: $solarized-light-bp; } /* Name.Builtin.Pseudo */
+  .vc { color: $solarized-light-vc; } /* Name.Variable.Class */
+  .vg { color: $solarized-light-vg; } /* Name.Variable.Global */
+  .vi { color: $solarized-light-vi; } /* Name.Variable.Instance */
+  .il { color: $solarized-light-il; } /* Literal.Number.Integer.Long */
 }
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index d5367d5f3f01fd0a60bbb6d4b4a4af75815c8487..1adab3ffd94f1d82635a7b48622f938e4c19abfa 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,5 +1,72 @@
 /* https://github.com/aahan/pygments-github-style */
 
+/*
+* White Syntax Colors
+*/
+$white-code-color: #333;
+$white-highlight: #fafe3d;
+$white-pre-hll-bg: #f8eec7;
+$white-hll-bg: #f8f8f8;
+$white-c: #998;
+$white-err: #a61717;
+$white-err-bg: #e3d2d2;
+$white-cm: #998;
+$white-cp: #999;
+$white-c1: #998;
+$white-cs: #999;
+$white-gd: $black;
+$white-gd-bg: #fdd;
+$white-gd-x: $black;
+$white-gd-x-bg: #faa;
+$white-gr: #a00;
+$white-gh: #999;
+$white-gi: $black;
+$white-gi-bg: #dfd;
+$white-gi-x: $black;
+$white-gi-x-bg: #afa;
+$white-go: #888;
+$white-gp: #555;
+$white-gu: #800080;
+$white-gt: #a00;
+$white-kt: #458;
+$white-m: #099;
+$white-s: #d14;
+$white-n: #333;
+$white-na: teal;
+$white-nb: #0086b3;
+$white-nc: #458;
+$white-no: teal;
+$white-ni: purple;
+$white-ne: #900;
+$white-nf: #900;
+$white-nn: #555;
+$white-nt: navy;
+$white-nv: teal;
+$white-w: #bbb;
+$white-mf: #099;
+$white-mh: #099;
+$white-mi: #099;
+$white-mo: #099;
+$white-sb: #d14;
+$white-sc: #d14;
+$white-sd: #d14;
+$white-s2: #d14;
+$white-se: #d14;
+$white-sh: #d14;
+$white-si: #d14;
+$white-sx: #d14;
+$white-sr: #009926;
+$white-s1: #d14;
+$white-ss: #990073;
+$white-bp: #999;
+$white-vc: teal;
+$white-vg: teal;
+$white-vi: teal;
+$white-il: #099;
+$white-gc-color: #999;
+$white-gc-bg: #eaf2f5;
+
+
 @mixin matchLine {
   color: $black-transparent;
   background-color: $match-line;
@@ -26,8 +93,8 @@
   &,
   pre.code,
   .line_holder .line_content {
-    background-color: #fff;
-    color: #333;
+    background-color: $white-light;
+    color: $white-code-color;
   }
 
   // Diff line
@@ -83,75 +150,75 @@
 
   // highlight line via anchor
   pre .hll {
-    background-color: #f8eec7 !important;
+    background-color: $white-pre-hll-bg !important;
   }
 
   // Search result highlight
   span.highlight_word {
-    background-color: #fafe3d !important;
+    background-color: $white-highlight !important;
   }
 
-  .hll { background-color: #f8f8f8; }
-  .c { color: #998; font-style: italic; }
-  .err { color: #a61717; background-color: #e3d2d2; }
+  .hll { background-color: $white-hll-bg; }
+  .c { color: $white-c; font-style: italic; }
+  .err { color: $white-err; background-color: $white-err-bg; }
   .k { font-weight: bold; }
   .o { font-weight: bold; }
-  .cm { color: #998; font-style: italic; }
-  .cp { color: #999; font-weight: bold; }
-  .c1 { color: #998; font-style: italic; }
-  .cs { color: #999; font-weight: bold; font-style: italic; }
-  .gd { color: #000; background-color: #fdd; }
-  .gd .x { color: #000; background-color: #faa; }
+  .cm { color: $white-cm; font-style: italic; }
+  .cp { color: $white-cp; font-weight: bold; }
+  .c1 { color: $white-c1; font-style: italic; }
+  .cs { color: $white-cs; font-weight: bold; font-style: italic; }
+  .gd { color: $white-gd; background-color: $white-gd-bg; }
+  .gd .x { color: $white-gd-x; background-color: $white-gd-x-bg; }
   .ge { font-style: italic; }
-  .gr { color: #a00; }
-  .gh { color: #999; }
-  .gi { color: #000; background-color: #dfd; }
-  .gi .x { color: #000; background-color: #afa; }
-  .go { color: #888; }
-  .gp { color: #555; }
+  .gr { color: $white-gr; }
+  .gh { color: $white-gh; }
+  .gi { color: $white-gi; background-color: $white-gi-bg; }
+  .gi .x { color: $white-gi-x; background-color: $white-gi-x-bg; }
+  .go { color: $white-go; }
+  .gp { color: $white-gp; }
   .gs { font-weight: bold; }
-  .gu { color: #800080; font-weight: bold; }
-  .gt { color: #a00; }
+  .gu { color: $white-gu; font-weight: bold; }
+  .gt { color: $white-gt; }
   .kc { font-weight: bold; }
   .kd { font-weight: bold; }
   .kn { font-weight: bold; }
   .kp { font-weight: bold; }
   .kr { font-weight: bold; }
-  .kt { color: #458; font-weight: bold; }
-  .m { color: #099; }
-  .s { color: #d14; }
-  .n { color: #333; }
-  .na { color: teal; }
-  .nb { color: #0086b3; }
-  .nc { color: #458; font-weight: bold; }
-  .no { color: teal; }
-  .ni { color: purple; }
-  .ne { color: #900; font-weight: bold; }
-  .nf { color: #900; font-weight: bold; }
-  .nn { color: #555; }
-  .nt { color: navy; }
-  .nv { color: teal; }
+  .kt { color: $white-kt; font-weight: bold; }
+  .m { color: $white-m; }
+  .s { color: $white-s; }
+  .n { color: $white-n; }
+  .na { color: $white-na; }
+  .nb { color: $white-nb; }
+  .nc { color: $white-nc; font-weight: bold; }
+  .no { color: $white-no; }
+  .ni { color: $white-ni; }
+  .ne { color: $white-ne; font-weight: bold; }
+  .nf { color: $white-nf; font-weight: bold; }
+  .nn { color: $white-nn; }
+  .nt { color: $white-nt; }
+  .nv { color: $white-nv; }
   .ow { font-weight: bold; }
-  .w { color: #bbb; }
-  .mf { color: #099; }
-  .mh { color: #099; }
-  .mi { color: #099; }
-  .mo { color: #099; }
-  .sb { color: #d14; }
-  .sc { color: #d14; }
-  .sd { color: #d14; }
-  .s2 { color: #d14; }
-  .se { color: #d14; }
-  .sh { color: #d14; }
-  .si { color: #d14; }
-  .sx { color: #d14; }
-  .sr { color: #009926; }
-  .s1 { color: #d14; }
-  .ss { color: #990073; }
-  .bp { color: #999; }
-  .vc { color: teal; }
-  .vg { color: teal; }
-  .vi { color: teal; }
-  .il { color: #099; }
-  .gc { color: #999; background-color: #eaf2f5; }
+  .w { color: $white-w; }
+  .mf { color: $white-mf; }
+  .mh { color: $white-mh; }
+  .mi { color: $white-mi; }
+  .mo { color: $white-mo; }
+  .sb { color: $white-sb; }
+  .sc { color: $white-sc; }
+  .sd { color: $white-sd; }
+  .s2 { color: $white-s2; }
+  .se { color: $white-se; }
+  .sh { color: $white-sh; }
+  .si { color: $white-si; }
+  .sx { color: $white-sx; }
+  .sr { color: $white-sr; }
+  .s1 { color: $white-s1; }
+  .ss { color: $white-ss; }
+  .bp { color: $white-bp; }
+  .vc { color: $white-vc; }
+  .vg { color: $white-vg; }
+  .vi { color: $white-vi; }
+  .il { color: $white-il; }
+  .gc { color: $white-gc-color; background-color: $white-gc-bg; }
 }
diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss
index b2bce482fdec566d84233f1b7f54c4b29e41c977..9f613710cf47a3119b8880fd4fb2ba051b6982e4 100644
--- a/app/assets/stylesheets/mailers/devise.scss
+++ b/app/assets/stylesheets/mailers/devise.scss
@@ -1,3 +1,5 @@
+@import "framework/variables";
+
 // NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout
 // used for Devise email templates, and _should not_ be included in any
 // application stylesheets.
@@ -46,7 +48,7 @@ table {
 
   &#body {
     background-color: $message-background-color;
-    border: 1px solid #000;
+    border: 1px solid $black;
     border-radius: 4px;
     margin: 0 auto;
     width: 600px;
diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
new file mode 100644
index 0000000000000000000000000000000000000000..024b4df6bd09bcc28f8fe536cd5a714041dac715
--- /dev/null
+++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
@@ -0,0 +1,207 @@
+@import "framework/variables";
+
+// This file is largely copied from `highlight/white.scss`, but modified to
+// avoid all descendant selectors (`table td`). This is because the CSS inlining
+// we use performs dramatically worse on descendant selectors than the
+// alternatives.
+// <https://gitlab.com/gitlab-org/gitlab-ee/issues/490#note_12283632>
+//
+// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of
+// preference): plain class selectors, type (element name) selectors, or
+// explicit child selectors.
+
+/*
+* Highlighted Diff Email Syntax Colors
+*/
+$highlighted-highlight-word: #fafe3d;
+$highlighted-hll-bg: #f8f8f8;
+$highlighted-c: #998;
+$highlighted-err: #a61717;
+$highlighted-err-bg: #e3d2d2;
+$highlighted-cm: #998;
+$highlighted-cp: #999;
+$highlighted-c1: #998;
+$highlighted-cs: #999;
+$highlighted-gd: #000;
+$highlighted-gd-bg: #fdd;
+$highlighted-gd-x: #000;
+$highlighted-gd-x-bg: #faa;
+$highlighted-gr: #a00;
+$highlighted-gh: #999;
+$highlighted-gi: #000;
+$highlighted-gi-bg: #dfd;
+$highlighted-gi-x: #000;
+$highlighted-gi-x-bg: #afa;
+$highlighted-go: #888;
+$highlighted-gp: #555;
+$highlighted-gu: #800080;
+$highlighted-gt: #a00;
+$highlighted-kt: #458;
+$highlighted-m: #099;
+$highlighted-s: #d14;
+$highlighted-n: #333;
+$highlighted-na: teal;
+$highlighted-nb: #0086b3;
+$highlighted-nc: #458;
+$highlighted-no: teal;
+$highlighted-ni: purple;
+$highlighted-ne: #900;
+$highlighted-nf: #900;
+$highlighted-nn: #555;
+$highlighted-nt: navy;
+$highlighted-nv: teal;
+$highlighted-w: #bbb;
+$highlighted-mf: #099;
+$highlighted-mh: #099;
+$highlighted-mi: #099;
+$highlighted-mo: #099;
+$highlighted-sb: #d14;
+$highlighted-sc: #d14;
+$highlighted-sd: #d14;
+$highlighted-s2: #d14;
+$highlighted-se: #d14;
+$highlighted-sh: #d14;
+$highlighted-si: #d14;
+$highlighted-sx: #d14;
+$highlighted-sr: #009926;
+$highlighted-s1: #d14;
+$highlighted-ss: #990073;
+$highlighted-bp: #999;
+$highlighted-vc: teal;
+$highlighted-vg: teal;
+$highlighted-vi: teal;
+$highlighted-il: #099;
+$highlighted-gc: #999;
+$highlighted-gc-bg: #eaf2f5;
+
+.code {
+  background-color: $white-light;
+  font-family: monospace;
+  font-size: $code_font_size;
+  -premailer-cellpadding: 0;
+  -premailer-cellspacing: 0;
+  -premailer-width: 100%;
+
+  > tr {
+    line-height: $code_line_height;
+  }
+}
+
+.diff-line-num {
+  padding: 0 5px;
+  text-align: right;
+  width: 35px;
+  background-color: $background-color;
+  color: $black-transparent;
+  border-right: 1px solid $table-border-gray;
+
+  &.old {
+    background-color: $line-number-old;
+    border-right-color: $line-removed-dark;
+  }
+
+  &.new {
+    background-color: $line-number-new;
+    border-right-color: $line-added-dark;
+  }
+}
+
+.line_content {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+
+  &.old {
+    background-color: $line-removed;
+
+    > .line > span.idiff,
+    > .line > span > span.idiff {
+      background-color: $line-removed-dark;
+    }
+  }
+
+  &.new {
+    background-color: $line-added;
+
+    > .line > span.idiff,
+    > .line > span > span.idiff {
+      background-color: $line-added-dark;
+    }
+  }
+
+  &.match {
+    color: $black-transparent;
+    background-color: $match-line;
+  }
+}
+
+pre {
+  margin: 0;
+}
+
+span.highlight_word {
+  background-color: $highlighted-highlight-word !important;
+}
+
+.hll { background-color: $highlighted-hll-bg; }
+.c { color: $highlighted-c; font-style: italic; }
+.err { color: $highlighted-err; background-color: $highlighted-err-bg; }
+.k { font-weight: bold; }
+.o { font-weight: bold; }
+.cm { color: $highlighted-cm; font-style: italic; }
+.cp { color: $highlighted-cp; font-weight: bold; }
+.c1 { color: $highlighted-c1; font-style: italic; }
+.cs { color: $highlighted-cs; font-weight: bold; font-style: italic; }
+.gd { color: $highlighted-gd; background-color: $highlighted-gd-bg; }
+.gd .x { color: $highlighted-gd; background-color: $highlighted-gd-x-bg; }
+.ge { font-style: italic; }
+.gr { color: $highlighted-gr; }
+.gh { color: $highlighted-gh; }
+.gi { color: $highlighted-gi; background-color: $highlighted-gi-bg; }
+.gi .x { color: $highlighted-gi; background-color: $highlighted-gi-x-bg; }
+.go { color: $highlighted-go; }
+.gp { color: $highlighted-gp; }
+.gs { font-weight: bold; }
+.gu { color: $highlighted-gu; font-weight: bold; }
+.gt { color: $highlighted-gt; }
+.kc { font-weight: bold; }
+.kd { font-weight: bold; }
+.kn { font-weight: bold; }
+.kp { font-weight: bold; }
+.kr { font-weight: bold; }
+.kt { color: $highlighted-kt; font-weight: bold; }
+.m { color: $highlighted-m; }
+.s { color: $highlighted-s; }
+.n { color: $highlighted-n; }
+.na { color: $highlighted-na; }
+.nb { color: $highlighted-nb; }
+.nc { color: $highlighted-nc; font-weight: bold; }
+.no { color: $highlighted-no; }
+.ni { color: $highlighted-ni; }
+.ne { color: $highlighted-ne; font-weight: bold; }
+.nf { color: $highlighted-nf; font-weight: bold; }
+.nn { color: $highlighted-nn; }
+.nt { color: $highlighted-nt; }
+.nv { color: $highlighted-nv; }
+.ow { font-weight: bold; }
+.w { color: $highlighted-w; }
+.mf { color: $highlighted-mf; }
+.mh { color: $highlighted-mh; }
+.mi { color: $highlighted-mi; }
+.mo { color: $highlighted-mo; }
+.sb { color: $highlighted-sb; }
+.sc { color: $highlighted-sc; }
+.sd { color: $highlighted-sd; }
+.s2 { color: $highlighted-s2; }
+.se { color: $highlighted-se; }
+.sh { color: $highlighted-sh; }
+.si { color: $highlighted-si; }
+.sx { color: $highlighted-sx; }
+.sr { color: $highlighted-sr; }
+.s1 { color: $highlighted-s1; }
+.ss { color: $highlighted-ss; }
+.bp { color: $highlighted-bp; }
+.vc { color: $highlighted-vc; }
+.vg { color: $highlighted-vg; }
+.vi { color: $highlighted-vi; }
+.il { color: $highlighted-il; }
+.gc { color: $highlighted-gc; background-color: $highlighted-gc-bg; }
diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss
deleted file mode 100644
index 8d1a6020ca4a4eafd32f4dd9eee81713119faa51..0000000000000000000000000000000000000000
--- a/app/assets/stylesheets/mailers/repository_push_email.scss
+++ /dev/null
@@ -1,143 +0,0 @@
-@import "framework/variables";
-
-// This file is largely copied from `highlight/white.scss`, but modified to
-// avoid all descendant selectors (`table td`). This is because the CSS inlining
-// we use performs dramatically worse on descendant selectors than the
-// alternatives.
-// <https://gitlab.com/gitlab-org/gitlab-ee/issues/490#note_12283632>
-//
-// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of
-// preference): plain class selectors, type (element name) selectors, or
-// explicit child selectors.
-
-.code {
-  background-color: #fff;
-  font-family: monospace;
-  font-size: $code_font_size;
-  -premailer-cellpadding: 0;
-  -premailer-cellspacing: 0;
-  -premailer-width: 100%;
-
-  > tr {
-    line-height: $code_line_height;
-  }
-}
-
-.diff-line-num {
-  padding: 0 5px;
-  text-align: right;
-  width: 35px;
-  background-color: $background-color;
-  color: $black-transparent;
-  border-right: 1px solid $table-border-gray;
-
-  &.old {
-    background-color: $line-number-old;
-    border-right-color: $line-removed-dark;
-  }
-
-  &.new {
-    background-color: $line-number-new;
-    border-right-color: $line-added-dark;
-  }
-}
-
-.line_content {
-  padding-left: 0.5em;
-  padding-right: 0.5em;
-
-  &.old {
-    background-color: $line-removed;
-
-    > .line > span.idiff,
-    > .line > span > span.idiff {
-      background-color: $line-removed-dark;
-    }
-  }
-
-  &.new {
-    background-color: $line-added;
-
-    > .line > span.idiff,
-    > .line > span > span.idiff {
-      background-color: $line-added-dark;
-    }
-  }
-
-  &.match {
-    color: $black-transparent;
-    background-color: $match-line;
-  }
-}
-
-pre {
-  margin: 0;
-}
-
-span.highlight_word {
-  background-color: #fafe3d !important;
-}
-
-.hll { background-color: #f8f8f8; }
-.c { color: #998; font-style: italic; }
-.err { color: #a61717; background-color: #e3d2d2; }
-.k { font-weight: bold; }
-.o { font-weight: bold; }
-.cm { color: #998; font-style: italic; }
-.cp { color: #999; font-weight: bold; }
-.c1 { color: #998; font-style: italic; }
-.cs { color: #999; font-weight: bold; font-style: italic; }
-.gd { color: #000; background-color: #fdd; }
-.gd .x { color: #000; background-color: #faa; }
-.ge { font-style: italic; }
-.gr { color: #a00; }
-.gh { color: #999; }
-.gi { color: #000; background-color: #dfd; }
-.gi .x { color: #000; background-color: #afa; }
-.go { color: #888; }
-.gp { color: #555; }
-.gs { font-weight: bold; }
-.gu { color: #800080; font-weight: bold; }
-.gt { color: #a00; }
-.kc { font-weight: bold; }
-.kd { font-weight: bold; }
-.kn { font-weight: bold; }
-.kp { font-weight: bold; }
-.kr { font-weight: bold; }
-.kt { color: #458; font-weight: bold; }
-.m { color: #099; }
-.s { color: #d14; }
-.n { color: #333; }
-.na { color: teal; }
-.nb { color: #0086b3; }
-.nc { color: #458; font-weight: bold; }
-.no { color: teal; }
-.ni { color: purple; }
-.ne { color: #900; font-weight: bold; }
-.nf { color: #900; font-weight: bold; }
-.nn { color: #555; }
-.nt { color: navy; }
-.nv { color: teal; }
-.ow { font-weight: bold; }
-.w { color: #bbb; }
-.mf { color: #099; }
-.mh { color: #099; }
-.mi { color: #099; }
-.mo { color: #099; }
-.sb { color: #d14; }
-.sc { color: #d14; }
-.sd { color: #d14; }
-.s2 { color: #d14; }
-.se { color: #d14; }
-.sh { color: #d14; }
-.si { color: #d14; }
-.sx { color: #d14; }
-.sr { color: #009926; }
-.s1 { color: #d14; }
-.ss { color: #990073; }
-.bp { color: #999; }
-.vc { color: teal; }
-.vg { color: teal; }
-.vi { color: teal; }
-.il { color: #099; }
-.gc { color: #999; background-color: #eaf2f5; }
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index ced8c4a99075160aa26a87b58a46d4002fee4e76..ddc382362f7ce356355edab1a48f3ce6e19a180b 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -1,3 +1,5 @@
+@import "framework/variables";
+
 img {
   max-width: 100%;
   height: auto;
@@ -5,12 +7,12 @@ img {
 
 p.details {
   font-style: italic;
-  color: #777;
+  color: $notify-details;
 }
 
 .footer > p {
   font-size: small;
-  color: #777;
+  color: $notify-footer;
 }
 
 pre.commit-message {
@@ -21,10 +23,10 @@ pre.commit-message {
   text-decoration: none;
 
   > .new-file {
-    color: #090;
+    color: $notify-new-file;
   }
 
   > .deleted-file {
-    color: #b00;
+    color: $notify-deleted-file;
   }
 }
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 14812e171fd5c5d1d5535261a5e53e566b663ba5..291372b88e37e2374c33cebe6f920210cdc54a3b 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -31,7 +31,7 @@
 
   .form-actions {
     padding-left: 130px;
-    background: #fff;
+    background: $white-light;
    }
 
   .visibility-levels {
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 486ad16ea263aaf88992cc0aae8f45e55974035d..dce5c31f2829702b19c3fc2e4789723e00cc5a00 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -15,7 +15,7 @@
   background-color: $award-emoji-menu-bg;
   border: 1px solid $award-emoji-menu-border;
   border-radius: $border-radius-base;
-  box-shadow: 0 6px 12px rgba(0,0,0,.175);
+  box-shadow: 0 6px 12px $award-emoji-menu-shadow;
   pointer-events: none;
   opacity: 0;
   transform: scale(.2);
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 4327f8bf6409961d8ecc40afa8e331173d1e1a11..0d9cf679e7c6459b3b65cf9b50440d0ebaf55016 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -145,7 +145,7 @@
 .board-blank-state {
   height: calc(100% - 49px);
   padding: $gl-padding;
-  background-color: #fff;
+  background-color: $white-light;
 }
 
 .board-blank-state-list {
@@ -191,9 +191,9 @@
 .card {
   position: relative;
   padding: 10px $gl-padding;
-  background: #fff;
+  background: $white-light;
   border-radius: $border-radius-default;
-  box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5);
+  box-shadow: 0 1px 2px $issue-boards-card-shadow;
   list-style: none;
 
   &:not(:last-child) {
@@ -325,7 +325,6 @@
   }
 
   .issuable-header-text {
-    width: 100%;
     padding-right: 35px;
 
     > strong {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 48f11eb25529affd4c57747d52e94da55d669504..842c0434bf225e7e9655a3ecbb67e570b52e3bed 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -1,7 +1,7 @@
 .build-page {
   pre.trace {
-    background: #111;
-    color: #fff;
+    background: $builds-trace-bg;
+    color: $white-light;
     font-family: $monospace_font;
     white-space: pre-wrap;
     overflow: auto;
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 87c453a7a27cdf662b643f7216e002939326bb04..d1cd1e5d848790cfc195f716dd1be4dbf6f29409 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -1,7 +1,7 @@
 .ci-body {
   .project-title {
     margin: 0;
-    color: #444;
+    color: $common-gray-dark;
     font-size: 20px;
     line-height: 1.5;
   }
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index ddc9d0e2b1a090a7de86f7721064cad7561f8733..bf656d0e28ee1f3e8f086d3cfee1ed483b2932f9 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -5,7 +5,7 @@
 .commit-author,
 .commit-committer {
   display: block;
-  color: #999;
+  color: $commit-committer-color;
   font-weight: normal;
   font-style: italic;
 }
@@ -113,17 +113,17 @@
   overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
   .max-width-marker {
     width: 72ch;
-    color: rgba(0, 0, 0, 0.0);
+    color: $commit-max-width-marker-color;
     font-family: inherit;
     left: $left;
     height: 100%;
-    border-right: 1px solid mix($input-border, white);
+    border-right: 1px solid mix($input-border, $white-light);
     position: absolute;
     z-index: 1;
   }
 
   > textarea {
-    background-color: rgba(0, 0, 0, 0.0);
+    background-color: $commit-message-text-area-bg;
     font-family: inherit;
     padding-left: $left;
     position: relative;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 83ffa0e1d396bee451e2d28e747b5f535d61d112..c29b5fdea78a33dfe067fbd7da48f6be17a0f306 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -8,8 +8,8 @@
 .commit-header {
   padding: 5px 10px;
   background-color: $background-color;
-  border-top: 1px solid #eee;
-  border-bottom: 1px solid #eee;
+  border-top: 1px solid $gray-darker;
+  border-bottom: 1px solid $gray-darker;
   font-size: 14px;
 
   &:first-child {
@@ -94,7 +94,7 @@
   }
 
   &:not(:last-child) {
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid $gray-darker;
   }
 
   a,
@@ -201,7 +201,7 @@
     .bar {
       position: absolute;
       height: 4px;
-      background-color: #ccc;
+      background-color: $divergence-graph-bar-bg;
     }
 
     .bar-behind {
@@ -218,7 +218,7 @@
       padding-top: 6px;
       padding-bottom: 0;
       font-size: 12px;
-      color: #333;
+      color: $gl-title-color;
       display: block;
     }
 
@@ -239,6 +239,6 @@
     height: 18px;
     margin: 5px 0 0;
     float: left;
-    background-color: #ccc;
+    background-color: $divergence-graph-separator-bg;
   }
 }
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
index 81e5cee240d6084b094f8cca6c9b86edfae6767f..8aab5e8231d000049a829e27a59caf938a203bb1 100644
--- a/app/assets/stylesheets/pages/confirmation.scss
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -1,6 +1,6 @@
 .well-confirmation {
   margin-bottom: 20px;
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid $gray-darker;
 
   > h1,
   h2,
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 498a8f68e4993ae268c9b1f3ee92d9922b22e015..e7a2c91003fc64144eb94263bc816a331c27f28b 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -53,7 +53,7 @@
       border-bottom: none;
       position: relative;
 
-      @media (max-width: $screen-sm-min) {
+      @media (max-width: $screen-xs-max) {
         padding: 6px 0 24px;
       }
     }
@@ -61,7 +61,7 @@
     .column {
       text-align: center;
 
-      @media (max-width: $screen-sm-min) {
+      @media (max-width: $screen-xs-max) {
         padding: 15px 0;
       }
 
@@ -78,7 +78,7 @@
       }
 
       &:last-child {
-        @media (max-width: $screen-sm-min) {
+        @media (max-width: $screen-xs-max) {
           text-align: center;
         }
       }
@@ -141,9 +141,9 @@
 
     .dismiss-icon {
       position: absolute;
-      right: $cycle-analytics-box-padding;
+      right: $cycle-analytics-dismiss-icon-color;
       cursor: pointer;
-      color: #b2b2b2;
+      color: $cycle-analytics-dismiss-icon-color;
     }
 
     .svg-container {
@@ -156,7 +156,7 @@
     }
 
     .inner-content {
-      @media (max-width: $screen-sm-min) {
+      @media (max-width: $screen-xs-max) {
         padding: 0 28px;
         text-align: center;
       }
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index 016bab104eb2b933e480d6f8b00aec7a16532bfd..4421ed6a0b9de1353e8629b5a0ba4289c8461e6f 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -32,7 +32,7 @@
   margin-bottom: 15px;
 
   i {
-    color: #888;
+    color: $dashboard-project-access-icon-color;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 0f0c0abe7ae8830b036461d7e3b39aaa9ce56abd..80baebd5ea37707f7b02cbc9e1c0a27da099de79 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,16 +1,16 @@
 .detail-page-header {
   padding: $gl-padding-top 0;
   border-bottom: 1px solid $border-color;
-  color: #5c5d5e;
+  color: $gl-text-color-dark;
   font-size: 16px;
   line-height: 34px;
 
   .author {
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
   }
 
   .identifier {
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
   }
 
   .issue_created_ago,
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 99fdea15218c5278d8ec6e912b2d831b47375b5c..737f6e0f4bed212e0ba9dffe8af1904147c03269 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -14,7 +14,7 @@
     background: $background-color;
     border-bottom: 1px solid $border-color;
     padding: 10px 16px;
-    color: #555;
+    color: $gl-diff-text-color;
     z-index: 10;
     border-radius: 3px 3px 0 0;
 
@@ -24,7 +24,7 @@
       display: block;
 
       .file-mode {
-        color: #777;
+        color: $file-mode-changed;
       }
     }
 
@@ -49,8 +49,8 @@
   .diff-content {
     overflow: auto;
     overflow-y: hidden;
-    background: #fff;
-    color: #333;
+    background: $white-light;
+    color: $gl-title-color;
     border-radius: 0 0 3px 3px;
 
     .unfold {
@@ -59,7 +59,7 @@
 
     .file-mode-changed {
       padding: 10px;
-      color: #777;
+      color: $file-mode-changed;
     }
 
     .suppressed-container {
@@ -172,7 +172,7 @@
   }
 
   .image {
-    background: #ddd;
+    background: $diff-image-bg;
     text-align: center;
     padding: 30px;
 
@@ -182,13 +182,13 @@
 
     .frame {
       display: inline-block;
-      background-color: #fff;
+      background-color: $white-light;
       line-height: 0;
 
       img {
-        border: 1px solid #fff;
-        background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%),
-        linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%);
+        border: 1px solid $white-light;
+        background-image: linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%),
+        linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%);
         background-size: 10px 10px;
         background-position: 0 0, 5px 5px;
         max-width: 100%;
@@ -206,7 +206,7 @@
     .image-info {
       font-size: 12px;
       margin: 5px 0 0;
-      color: grey;
+      color: $diff-image-info-color;
     }
 
     .view.swipe {
@@ -220,7 +220,7 @@
 
       .swipe-wrap {
         overflow: hidden;
-        border-left: 1px solid #999;
+        border-left: 1px solid $diff-swipe-border;
         position: absolute;
         display: block;
         top: 13px;
@@ -350,7 +350,7 @@
   .view-modes {
     padding: 10px;
     text-align: center;
-    background: #eee;
+    background: $gray-darker;
 
     ul,
     li {
@@ -361,8 +361,8 @@
     }
 
     li {
-      color: grey;
-      border-left: 1px solid #c1c1c1;
+      color: $diff-view-modes-color;
+      border-left: 1px solid $diff-view-modes-border;
       padding: 0 12px 0 16px;
       cursor: pointer;
 
@@ -380,7 +380,7 @@
         }
 
         cursor: default;
-        color: #333;
+        color: $gl-title-color;
       }
 
       &.disabled {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 778126bcfb79037e434a0bb8d653920caa5cb95b..6cde9c592de67b9f04b6ee3f2d591cc9ba50f4bc 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -14,10 +14,10 @@
   }
 
   .cancel-btn {
-    color: #b94a48;
+    color: $editor-cancel-color;
 
     &:hover {
-      color: #b94a48;
+      color: $editor-cancel-color;
     }
   }
 
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 4b382e8adaf633718bf4b0f4397bfb891bdd7292..de3d2ba549fae5a2c5723667981a6bfec74c0b77 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -8,7 +8,7 @@
   font-size: 34px;
 }
 
-@media (max-width: $screen-sm-min) {
+@media (max-width: $screen-xs-max) {
   .environments-container {
     width: 100%;
     overflow: auto;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 3004959ff7bcdc69979ce02e12263e35825e793c..dc67d411c7197277cd4ed86df30575f5871daad9 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -62,7 +62,7 @@
         border: none;
         background: $gray-light;
         border-radius: 0;
-        color: #777;
+        color: $events-pre-color;
         margin: 0 20px;
         overflow: hidden;
       }
@@ -80,7 +80,7 @@
     }
 
     .event-note-icon {
-      color: #777;
+      color: $events-pre-color;
       float: left;
       font-size: $gl-font-size;
       line-height: 16px;
@@ -91,7 +91,7 @@
   .event_icon {
     position: relative;
     float: right;
-    border: 1px solid #eee;
+    border: 1px solid $gray-darker;
     padding: 5px;
     border-radius: 5px;
     background: $gray-light;
@@ -170,7 +170,7 @@
 
     .event-body {
       margin: 0;
-      border-left: 2px solid #ddd;
+      border-left: 2px solid $events-body-border;
       padding-left: 10px;
     }
 
@@ -186,4 +186,3 @@
     display: none;
   }
 }
-
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index f7f9a9bb7700be56f292f89a8e2d1d85334035af..84da9180f936d529e8a6557b61a4ee5bb059ff29 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -2,15 +2,15 @@
   border: 1px solid $border-color;
 
   .controls {
-    color: #888;
+    color: $project-network-controls-color;
     font-size: 14px;
     padding: 5px;
     border-bottom: 1px solid $border-color;
-    background: #eee;
+    background: $gray-darker;
   }
 
   .network-graph {
-    background: #fff;
+    background: $white-light;
     height: 500px;
     overflow-y: scroll;
     overflow-x: hidden;
@@ -20,15 +20,14 @@
 .graphs {
   .graph-author-email {
     float: right;
-    color: #777;
+    color: $graph-author-email-color;
   }
 
   .graph-additions {
-    color: #4a2;
+    color: $gl-text-green;
   }
 
   .graph-deletions {
-    color: #d12f19;
+    color: $gl-text-red;
   }
 }
-
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 57d028cec8ce2ce4b63d8b4829b8ec897859e69d..a9af7af59e23b98e93e65fcb51a0c5a23f48d56d 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -49,14 +49,14 @@
   padding: 50px 100px;
   overflow: hidden;
 
-  @media (max-width: $screen-md-min) {
+  @media (max-width: $screen-sm-max) {
     padding: 50px 0;
   }
 
   svg {
     float: right;
 
-    @media (max-width: $screen-md-min) {
+    @media (max-width: $screen-sm-max) {
       float: none;
       display: block;
       width: 250px;
@@ -71,7 +71,7 @@
     width: 460px;
     margin-top: 120px;
 
-    @media (max-width: $screen-md-min) {
+    @media (max-width: $screen-sm-max) {
       float: none;
       margin-top: 60px;
       width: auto;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index a48b4c65db8ea6af15b13e28a2f2ed1c224ca6b2..e2e644dc23bfb4bf6fc7176574d8c9079f9daf25 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -9,7 +9,7 @@
 
   li {
     line-height: 24px;
-    color: #888;
+    color: $document-index-color;
 
     a {
       margin-right: 3px;
@@ -20,7 +20,7 @@
 
 .shortcut-mappings {
   font-size: 12px;
-  color: #555;
+  color: $help-shortcut-mapping-color;
 
   tbody:first-child tr:first-child {
     padding-top: 0;
@@ -29,7 +29,7 @@
   th {
     padding-top: 15px;
     line-height: 1.5;
-    color: #333;
+    color: $help-shortcut-header-color;
     text-align: left;
   }
 
@@ -42,7 +42,7 @@
 
   .shortcut {
     padding-right: 10px;
-    color: #999;
+    color: $help-shortcut-color;
     text-align: right;
     white-space: nowrap;
   }
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 773155fe80af9b40344753360d4154f5b4f33e39..90587b9425b563f6b30a8c4c9ef01ef202ea7892 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -132,7 +132,7 @@
     display: none;
   }
 
-  .btn-clipboard {
+  .btn-clipboard:hover {
     color: $gl-gray;
   }
 }
@@ -233,7 +233,11 @@
       width: 100%;
       text-align: center;
       padding-bottom: 10px;
-      color: #999;
+      color: $issuable-sidebar-color;
+
+      &:hover {
+        color: $gl-gray;
+      }
 
       span {
         display: block;
@@ -244,15 +248,17 @@
         display: none;
       }
 
+      .avatar:hover {
+        border-color: $issuable-avatar-hover-border;
+      }
+
       .btn-clipboard {
         border: none;
+        color: $issuable-clipboard-color;
 
         &:hover {
           background: transparent;
-        }
-
-        i {
-          color: #999;
+          color: $gl-gray;
         }
       }
     }
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index eb171195309ceaffbc778f4567599ceec1549daf..3b47f99df2c5db9450c890520cfc478f4c0b2880 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -82,18 +82,18 @@ ul.related-merge-requests > li {
 .merge-request,
 .issue {
   &.today {
-    background: #f3fff2;
-    border-color: #e1e8d5;
+    background: $issues-today-bg;
+    border-color: $issues-today-border;
   }
 
   &.closed {
     background: $gray-light;
-    border-color: #e5e5e5;
+    border-color: $issues-border;
   }
 
   &.merged {
     background: $gray-light;
-    border-color: #e5e5e5;
+    border-color: $issues-border;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index e39ce19f846076c176a93f3fedfbedc4aa5f62bb..b1ccd6444509fabf3ccb137efdeefb492577054c 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -198,7 +198,7 @@
   }
 
   .label-remove {
-    border-left: 1px solid rgba(0, 0, 0, .1);
+    border-left: 1px solid $label-remove-border;
     z-index: 3;
   }
 
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 8290519dc258074907a97e61638d4a38a07877f7..8d30bd64278726b21bb571007936f69d1f63a129 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -1,11 +1,11 @@
 .ci-body {
   .incorrect-syntax {
     font-size: 19px;
-    color: red;
+    color: $lint-incorrect-color;
   }
 
   .correct-syntax {
     font-size: 19px;
-    color: #47a447;
+    color: $lint-correct-color;
   }
 }
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 54c89d75e94864ecdcf713f5265be5994c87715c..dd27a06fcd2f344ca224dd75f4c11fbf0cadce82 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -18,7 +18,7 @@
 
     p {
       font-size: 18px;
-      color: #888;
+      color: $login-brand-holder-color;
     }
 
     h1:first-child {
@@ -174,7 +174,7 @@
   .form-control {
     &:active,
     &:focus {
-      background-color: #fff;
+      background-color: $white-light;
     }
   }
 
@@ -195,7 +195,7 @@
     h2 {
       margin-top: 0;
       font-size: 14px;
-      color: #a00;
+      color: $login-devise-error-color;
     }
   }
 }
@@ -254,4 +254,3 @@
     }
   }
 }
-
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 19ab198c2e731bb051fbb977e7e0942e650b5201..7a90713dd3f62ce1d2beef58d86ce863c69e22f2 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -1,3 +1,5 @@
+// Disabled to use the color map for creating color schemes
+// scss-lint:disable ColorVariable
 $colors: (
   white_header_head_neutral   : #e1fad7,
   white_line_head_neutral     : #effdec,
@@ -98,6 +100,7 @@ $colors: (
   solarized_dark_header_not_chosen     : rgba(#839496, .25),
   solarized_dark_line_not_chosen       : rgba(#839496, .15)
 );
+// scss-lint:enable ColorVariable
 
 
 @mixin color-scheme($color) {
@@ -228,14 +231,15 @@ $colors: (
       position: absolute;
       right: 10px;
       padding: 0;
-      color: #fff;
+      outline: none;
+      color: $white-light;
       width: 75px; // static width to make 2 buttons have same width
       height: 19px;
     }
   }
 
   .btn-success .fa-spinner {
-    color: #fff;
+    color: $white-light;
   }
 
   .editor-wrap {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index da1187af41c19d6e2d8896b884e6bac8cd4d6ca3..1c6fe7afe147f55676a4f71026e579e4b31cd4e9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -86,7 +86,7 @@
   }
 
   .normal {
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
   }
 
   .js-deployment-link {
@@ -143,7 +143,7 @@
   }
 
   .mr-widget-footer {
-    border-top: 1px solid #eee;
+    border-top: 1px solid $gray-darker;
   }
 
   .ci-coverage {
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 8843d1463db08222f87bcdc1a682e84344a1d970..dfc6079bd151df951b5bc9034bcf90bcedae5992 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -123,7 +123,7 @@
   padding: 20px 0;
 }
 
-@media (max-width: $screen-sm-min) {
+@media (max-width: $screen-xs-max) {
   .milestone-actions {
     @include clearfix();
     padding-top: $gl-vert-padding;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 16ddef481bd362f119fde963a9268bb56ba70321..ff092d538456d0bf4061b2cbbbe9751cbe7166c0 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -111,7 +111,7 @@
   text-align: center;
   font-size: 13px;
 
-  @media (max-width: $screen-md-min) {
+  @media (max-width: $screen-sm-max) {
     // On smaller devices the warning becomes the fourth item in the list,
     // rather than centering, and grows to span the full width of the
     // comment area.
@@ -132,7 +132,7 @@
   font-size: 15px;
 
   .md-area {
-    background-color: #fff;
+    background-color: $white-light;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e66c1f8d0720a71d9630359c2dfda4c56261ca65..16b099c09eb4bb6f06b2a2d8a919a88b40efccfc 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -3,9 +3,9 @@
  */
 
 @-webkit-keyframes targe3-note {
-  from { background: #fffff0; }
-  50% { background: #ffffd3; }
-  to { background: #fffff0; }
+  from { background: $note-targe3-outside; }
+  50% { background: $note-targe3-inside; }
+  to { background: $note-targe3-outside; }
 }
 
 ul.notes {
@@ -90,14 +90,14 @@ ul.notes {
         }
 
         &.system-note-commit-list {
-          max-height: 63px;
+          max-height: 70px;
           overflow: hidden;
           display: block;
 
           ul {
-            margin: 3px 0 3px 15px !important;
+            margin: 3px 0 3px 16px !important;
 
-            li {
+            .gfm-commit {
               font-family: $monospace_font;
               font-size: 12px;
             }
@@ -172,6 +172,10 @@ ul.notes {
       &.timeline-entry {
         padding: 14px 10px;
       }
+
+      .system-note {
+        padding: 0;
+      }
     }
 
     &.is-editting {
@@ -249,7 +253,7 @@ ul.notes {
 }
 
 .page-sidebar-pinned.right-sidebar-expanded {
-  @media (max-width: $screen-lg-min) {
+  @media (max-width: $screen-md-max) {
     .note-header {
       .note-headline-light {
         display: block;
@@ -301,7 +305,7 @@ ul.notes {
     &.notes_line2 {
       text-align: center;
       padding: 10px 0;
-      border-left: 1px solid #ddd !important;
+      border-left: 1px solid $note-line2-border !important;
     }
 
     &.notes_content {
@@ -467,7 +471,7 @@ ul.notes {
   .add-diff-note {
     margin-top: -4px;
     border-radius: 40px;
-    background: #fff;
+    background: $white-light;
     padding: 4px;
     font-size: 16px;
     color: $gl-link-color;
@@ -480,7 +484,7 @@ ul.notes {
 
     &:hover {
       background: $gl-info;
-      color: #fff;
+      color: $white-light;
       @include show-add-diff-note;
     }
   }
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 6fab97a71aacf411c8e094108c16bca3f361d2ff..f8677f93fe045b8bb5124b91f2e6f163705421bb 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -180,7 +180,7 @@
   .modal-dialog {
     width: 380px;
 
-    @media (max-width: $screen-sm-min) {
+    @media (max-width: $screen-xs-max) {
       width: auto;
     }
 
@@ -261,4 +261,4 @@ table.u2f-registrations {
   td:not(:last-child) {
     border-right: solid 1px transparent;
   }
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 19a7a97ea0d234b3056d8ceaa2020d297a0125ea..62862c72b3b910e7067f622151937f88b95af5a6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -8,7 +8,7 @@
 
 .no-ssh-key-message,
 .project-limit-message {
-  background-color: #f28d35;
+  background-color: $project-limit-message-bg;
   margin-bottom: 0;
 }
 
@@ -76,7 +76,7 @@
     &.static-namespace {
       height: 35px;
       border-radius: 3px;
-      border: 1px solid #e5e5e5;
+      border: 1px solid $border-color;
     }
 
     &+ .select2 a {
@@ -189,7 +189,7 @@
   }
 
   .download-button {
-    @media (max-width: $screen-lg-min) {
+    @media (max-width: $screen-md-max) {
       margin-left: 0;
     }
   }
@@ -225,7 +225,7 @@
         left: 0;
         margin-top: -6px;
         border-width: 7px 5px 7px 0;
-        border-right-color: #dce0e5;
+        border-right-color: $count-arrow-border;
         pointer-events: none;
       }
 
@@ -240,7 +240,7 @@
         left: 1px;
         margin-top: -9px;
         border-width: 10px 7px 10px 0;
-        border-right-color: #fff;
+        border-right-color: $white-light;
         pointer-events: none;
       }
     }
@@ -248,7 +248,7 @@
     .count {
       @include btn-gray;
       display: inline-block;
-      background: white;
+      background: $white-light;
       border-radius: 2px;
       border-width: 1px;
       border-style: solid;
@@ -270,7 +270,7 @@
       }
 
       &:hover {
-        background: #fff;
+        background: $white-light;
       }
     }
   }
@@ -302,7 +302,7 @@
 
     .option-descr {
       margin-left: 29px;
-      color: #54565b;
+      color: $project-option-descr-color;
     }
   }
 }
@@ -310,7 +310,7 @@
 .save-project-loader {
   margin-top: 50px;
   margin-bottom: 50px;
-  color: #555;
+  color: $save-project-loader-color;
 }
 
 .transfer-project .select2-container {
@@ -373,7 +373,7 @@ a.deploy-project-label {
 
   > li + li::before {
     padding: 0 3px;
-    color: #999;
+    color: $project-breadcrumb-color;
   }
 
   a {
@@ -549,20 +549,20 @@ a.deploy-project-label {
 }
 
 pre.light-well {
-  border-color: #f1f1f1;
+  border-color: $well-light-border;
 }
 
 .git-empty {
   margin: 0 7px 7px;
 
   h5 {
-    color: #5c5d5e;
+    color: $gl-text-color-dark;
   }
 
   .light-well {
     border-radius: 2px;
 
-    color: #5b6169;
+    color: $well-light-text-color;
     font-size: 13px;
     line-height: 1.6em;
   }
@@ -716,7 +716,7 @@ pre.light-well {
 
   .form-control {
     @extend .monospace;
-    background: #fff;
+    background: $white-light;
     font-size: 14px;
     margin-left: -1px;
     cursor: auto;
@@ -726,17 +726,17 @@ pre.light-well {
 
 .cannot-be-merged,
 .cannot-be-merged:hover {
-  color: #e62958;
+  color: $error-exclamation-point;
   margin-top: 2px;
 }
 
 .private-forks-notice .private-fork-icon {
   i:nth-child(1) {
-    color: #2aa056;
+    color: $project-private-forks-notice-odd;
   }
 
   i:nth-child(2) {
-    color: #fff;
+    color: $white-light;
   }
 }
 
@@ -876,3 +876,11 @@ pre.light-well {
     pointer-events: none;
   }
 }
+
+.variables-table {
+  table-layout: fixed;
+
+  .variable-key {
+    width: 30%;
+  }
+}
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 7b3878c91df64a41764d44592ccd3ba25388c384..9b6ff2375573291799cd12333c11490bb69762e6 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -1,27 +1,27 @@
 .runner-state {
   padding: 6px 12px;
   margin-right: 10px;
-  color: #fff;
+  color: $white-light;
 
   &.runner-state-shared {
-    background: #32b186;
+    background: $runner-state-shared-bg;
   }
 
   &.runner-state-specific {
-    background: #3498db;
+    background: $runner-state-specific-bg;
   }
 }
 
 .runner-status-online {
-  color: green;
+  color: $runner-status-online-color;
 }
 
 .runner-status-offline {
-  color: gray;
+  color: $runner-status-offline-color;
 }
 
 .runner-status-paused {
-  color: red;
+  color: $runner-status-paused-color;
 }
 
 .runner {
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 69288b31cc4299794d97dfe0551f062e066fdd53..dfa4d033fb88cf8c2adbf8d72d46d17a4f0672e4 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -1,16 +1,16 @@
 .tint-box {
-  background: #f3f3f3;
+  background: $stat-graph-common-bg;
   position: relative;
   margin-bottom: 10px;
 }
 
 .area {
-  fill: #1db34f;
+  fill: $stat-graph-area-fill;
   fill-opacity: 0.5;
 }
 
 .axis {
-  fill: #aaa;
+  fill: $stat-graph-axis-fill;
   font-size: 10px;
 }
 
@@ -37,26 +37,26 @@
     @include make-md-column(6);
     margin-top: 10px;
 
-    @media (max-width: $screen-sm-min) {
+    @media (max-width: $screen-xs-max) {
       width: 100%;
     }
   }
 
   .person .spark {
     display: block;
-    background: #f3f3f3;
+    background: $stat-graph-common-bg;
     width: 100%;
   }
 
   .person .area-contributor {
-    fill: #f17f49;
+    fill: $stat-graph-orange-fill;
   }
 }
 
 .selection rect {
-  fill: #333;
+  fill: $stat-graph-selection-fill;
   fill-opacity: 0.1;
-  stroke: #333;
+  stroke: $stat-graph-selection-stroke;
   stroke-width: 1px;
   stroke-opacity: 0.4;
   shape-rendering: crispedges;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 4c258bae1f48a67a9739d63c47f56181933426fc..5084b466722e7f9f23884561245327b829c58e6d 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -2,7 +2,7 @@
   .ci-status {
     padding: 2px 7px;
     margin-right: 10px;
-    border: 1px solid #eee;
+    border: 1px solid $gray-darker;
     white-space: nowrap;
     border-radius: 4px;
 
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index b3aef2fdd328ecb7cfeaf8f4fe0ca3817790cd3d..508b30f394787640f040d51293aa9cfe61636882 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -11,7 +11,7 @@
       background: $todo-alert-blue;
       margin-left: -17px;
       font-size: 11px;
-      color: white;
+      color: $white-light;
       padding: 3px;
       padding-top: 1px;
       padding-bottom: 1px;
@@ -81,7 +81,7 @@
       word-wrap: break-word;
 
       .md {
-        color: #7f8fa4;
+        color: $gl-grayish-blue;
         font-size: $gl-font-size;
 
         .label {
@@ -90,7 +90,7 @@
         }
 
         p {
-          color: #5c5d5e;
+          color: $gl-text-color-dark;
         }
       }
 
@@ -102,7 +102,7 @@
         border: none;
         background: $gray-light;
         border-radius: 0;
-        color: #777;
+        color: $todo-body-pre-color;
         margin: 0 20px;
         overflow: hidden;
       }
@@ -146,7 +146,7 @@
 
     .todo-body {
       margin: 0;
-      border-left: 2px solid #ddd;
+      border-left: 2px solid $todo-body-border;
       padding-left: 10px;
     }
   }
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 2b836fa1f4adeffa3c19a0866e9260b028abeb9e..20ad63be0458014cd4ba662f373a177330a4bd15 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -31,7 +31,7 @@
       .last-commit {
         @include str-truncated(506px);
 
-        @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) {
+        @media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
           @include str-truncated(450px);
         }
 
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index e73cecc92be3a998f2348dcc260a41bc0296d6c3..8c87bc3cafd0570c14f81ccb050be70fbf7010b4 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -7,11 +7,11 @@
   .example {
     &::before {
       content: "Example";
-      color: #bbb;
+      color: $ui-dev-kit-example-color;
     }
 
     padding: 15px;
-    border: 1px dashed #ddd;
+    border: 1px dashed $ui-dev-kit-example-border;
     margin-bottom: 15px;
   }
 }
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index dfaeba41cf6603c3d86ca3bfeeb8ac19a9f97310..b9f8153315025829a6435861394e0af078fe62ae 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,3 +4,128 @@
   margin-right: auto;
   padding-right: 7px;
 }
+
+.wiki-page-header {
+  @extend .top-area;
+  position: relative;
+
+  .wiki-page-title {
+    margin: 0;
+    font-size: 22px;
+  }
+
+  .wiki-last-edit-by {
+    color: $gl-gray-light;
+
+    strong {
+      color: $gl-text-color;
+    }
+  }
+
+  .light {
+    font-weight: normal;
+    color: $gl-gray-light;
+  }
+
+  .git-access-header {
+    padding: 16px 40px 11px 0;
+    line-height: 28px;
+    font-size: 18px;
+  }
+
+  .git-clone-holder {
+    width: 100%;
+    padding-bottom: 40px;
+  }
+
+  button.sidebar-toggle {
+    position: absolute;
+    right: 0;
+    top: 11px;
+    display: block;
+  }
+
+  @media (min-width: $screen-sm-min) {
+    &.has-sidebar-toggle {
+      padding-right: 40px;
+    }
+
+    .git-clone-holder {
+      width: 480px;
+    }
+
+    .nav-controls {
+      width: auto;
+      min-width: 50%;
+      white-space: nowrap;
+    }
+  }
+
+  @media (min-width: $screen-md-min) {
+    &.has-sidebar-toggle {
+      padding-right: 0;
+    }
+
+    button.sidebar-toggle {
+      display: none;
+    }
+  }
+}
+
+.wiki-git-access {
+  margin: $gl-padding 0;
+
+  h3 {
+    font-size: 22px;
+    font-weight: normal;
+    margin-top: 1.4em;
+  }
+}
+
+.right-sidebar.wiki-sidebar {
+  padding: $gl-padding 0;
+
+  &.right-sidebar-collapsed {
+    display: none;
+  }
+
+  .blocks-container {
+    padding: 0 $gl-padding;
+  }
+
+  .block {
+    width: 100%;
+  }
+
+  a {
+    color: $layout-link-gray;
+
+    &:hover,
+    &.active {
+      color: $black;
+    }
+  }
+
+  .active > a {
+    color: $black;
+  }
+
+  ul.wiki-pages,
+  ul.wiki-pages li {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+  }
+
+  ul.wiki-pages li {
+    margin: 5px 0 10px;
+  }
+
+  .wiki-sidebar-header {
+    padding: 0 $gl-padding $gl-padding;
+
+    .gutter-toggle {
+      margin-top: 0;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 3fa7fa3d7e3fc47bfe63137c7d5e86549c92a03c..9f9d630978a6be5567a30550ec8b641b04a21879 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -20,6 +20,266 @@
   $l-cyan: #8abeb7;
   $l-white: $ci-text-color;
 
+  /*
+  * xterm colors
+  */
+  $xterm-fg-0: $black;
+  $xterm-fg-1: #800000;
+  $xterm-fg-2: #008000;
+  $xterm-fg-3: #808000;
+  $xterm-fg-4: #000080;
+  $xterm-fg-5: #800080;
+  $xterm-fg-6: #008080;
+  $xterm-fg-7: #c0c0c0;
+  $xterm-fg-8: #808080;
+  $xterm-fg-9: #f00;
+  $xterm-fg-10: #0f0;
+  $xterm-fg-11: #ff0;
+  $xterm-fg-12: #00f;
+  $xterm-fg-13: #f0f;
+  $xterm-fg-14: #0ff;
+  $xterm-fg-15: $white-light;
+  $xterm-fg-16: $black;
+  $xterm-fg-17: #00005f;
+  $xterm-fg-18: #000087;
+  $xterm-fg-19: #0000af;
+  $xterm-fg-20: #0000d7;
+  $xterm-fg-21: #00f;
+  $xterm-fg-22: #005f00;
+  $xterm-fg-23: #005f5f;
+  $xterm-fg-24: #005f87;
+  $xterm-fg-25: #005faf;
+  $xterm-fg-26: #005fd7;
+  $xterm-fg-27: #005fff;
+  $xterm-fg-28: #008700;
+  $xterm-fg-29: #00875f;
+  $xterm-fg-30: #008787;
+  $xterm-fg-31: #0087af;
+  $xterm-fg-32: #0087d7;
+  $xterm-fg-33: #0087ff;
+  $xterm-fg-34: #00af00;
+  $xterm-fg-35: #00af5f;
+  $xterm-fg-36: #00af87;
+  $xterm-fg-37: #00afaf;
+  $xterm-fg-38: #00afd7;
+  $xterm-fg-39: #00afff;
+  $xterm-fg-40: #00d700;
+  $xterm-fg-41: #00d75f;
+  $xterm-fg-42: #00d787;
+  $xterm-fg-43: #00d7af;
+  $xterm-fg-44: #00d7d7;
+  $xterm-fg-45: #00d7ff;
+  $xterm-fg-46: #0f0;
+  $xterm-fg-47: #00ff5f;
+  $xterm-fg-48: #00ff87;
+  $xterm-fg-49: #00ffaf;
+  $xterm-fg-50: #00ffd7;
+  $xterm-fg-51: #0ff;
+  $xterm-fg-52: #5f0000;
+  $xterm-fg-53: #5f005f;
+  $xterm-fg-54: #5f0087;
+  $xterm-fg-55: #5f00af;
+  $xterm-fg-56: #5f00d7;
+  $xterm-fg-57: #5f00ff;
+  $xterm-fg-58: #5f5f00;
+  $xterm-fg-59: #5f5f5f;
+  $xterm-fg-60: #5f5f87;
+  $xterm-fg-61: #5f5faf;
+  $xterm-fg-62: #5f5fd7;
+  $xterm-fg-63: #5f5fff;
+  $xterm-fg-64: #5f8700;
+  $xterm-fg-65: #5f875f;
+  $xterm-fg-66: #5f8787;
+  $xterm-fg-67: #5f87af;
+  $xterm-fg-68: #5f87d7;
+  $xterm-fg-69: #5f87ff;
+  $xterm-fg-70: #5faf00;
+  $xterm-fg-71: #5faf5f;
+  $xterm-fg-72: #5faf87;
+  $xterm-fg-73: #5fafaf;
+  $xterm-fg-74: #5fafd7;
+  $xterm-fg-75: #5fafff;
+  $xterm-fg-76: #5fd700;
+  $xterm-fg-77: #5fd75f;
+  $xterm-fg-78: #5fd787;
+  $xterm-fg-79: #5fd7af;
+  $xterm-fg-80: #5fd7d7;
+  $xterm-fg-81: #5fd7ff;
+  $xterm-fg-82: #5fff00;
+  $xterm-fg-83: #5fff5f;
+  $xterm-fg-84: #5fff87;
+  $xterm-fg-85: #5fffaf;
+  $xterm-fg-86: #5fffd7;
+  $xterm-fg-87: #5fffff;
+  $xterm-fg-88: #870000;
+  $xterm-fg-89: #87005f;
+  $xterm-fg-90: #870087;
+  $xterm-fg-91: #8700af;
+  $xterm-fg-92: #8700d7;
+  $xterm-fg-93: #8700ff;
+  $xterm-fg-94: #875f00;
+  $xterm-fg-95: #875f5f;
+  $xterm-fg-96: #875f87;
+  $xterm-fg-97: #875faf;
+  $xterm-fg-98: #875fd7;
+  $xterm-fg-99: #875fff;
+  $xterm-fg-100: #878700;
+  $xterm-fg-101: #87875f;
+  $xterm-fg-102: #878787;
+  $xterm-fg-103: #8787af;
+  $xterm-fg-104: #8787d7;
+  $xterm-fg-105: #8787ff;
+  $xterm-fg-106: #87af00;
+  $xterm-fg-107: #87af5f;
+  $xterm-fg-108: #87af87;
+  $xterm-fg-109: #87afaf;
+  $xterm-fg-110: #87afd7;
+  $xterm-fg-111: #87afff;
+  $xterm-fg-112: #87d700;
+  $xterm-fg-113: #87d75f;
+  $xterm-fg-114: #87d787;
+  $xterm-fg-115: #87d7af;
+  $xterm-fg-116: #87d7d7;
+  $xterm-fg-117: #87d7ff;
+  $xterm-fg-118: #87ff00;
+  $xterm-fg-119: #87ff5f;
+  $xterm-fg-120: #87ff87;
+  $xterm-fg-121: #87ffaf;
+  $xterm-fg-122: #87ffd7;
+  $xterm-fg-123: #87ffff;
+  $xterm-fg-124: #af0000;
+  $xterm-fg-125: #af005f;
+  $xterm-fg-126: #af0087;
+  $xterm-fg-127: #af00af;
+  $xterm-fg-128: #af00d7;
+  $xterm-fg-129: #af00ff;
+  $xterm-fg-130: #af5f00;
+  $xterm-fg-131: #af5f5f;
+  $xterm-fg-132: #af5f87;
+  $xterm-fg-133: #af5faf;
+  $xterm-fg-134: #af5fd7;
+  $xterm-fg-135: #af5fff;
+  $xterm-fg-136: #af8700;
+  $xterm-fg-137: #af875f;
+  $xterm-fg-138: #af8787;
+  $xterm-fg-139: #af87af;
+  $xterm-fg-140: #af87d7;
+  $xterm-fg-141: #af87ff;
+  $xterm-fg-142: #afaf00;
+  $xterm-fg-143: #afaf5f;
+  $xterm-fg-144: #afaf87;
+  $xterm-fg-145: #afafaf;
+  $xterm-fg-146: #afafd7;
+  $xterm-fg-147: #afafff;
+  $xterm-fg-148: #afd700;
+  $xterm-fg-149: #afd75f;
+  $xterm-fg-150: #afd787;
+  $xterm-fg-151: #afd7af;
+  $xterm-fg-152: #afd7d7;
+  $xterm-fg-153: #afd7ff;
+  $xterm-fg-154: #afff00;
+  $xterm-fg-155: #afff5f;
+  $xterm-fg-156: #afff87;
+  $xterm-fg-157: #afffaf;
+  $xterm-fg-158: #afffd7;
+  $xterm-fg-159: #afffff;
+  $xterm-fg-160: #d70000;
+  $xterm-fg-161: #d7005f;
+  $xterm-fg-162: #d70087;
+  $xterm-fg-163: #d700af;
+  $xterm-fg-164: #d700d7;
+  $xterm-fg-165: #d700ff;
+  $xterm-fg-166: #d75f00;
+  $xterm-fg-167: #d75f5f;
+  $xterm-fg-168: #d75f87;
+  $xterm-fg-169: #d75faf;
+  $xterm-fg-170: #d75fd7;
+  $xterm-fg-171: #d75fff;
+  $xterm-fg-172: #d78700;
+  $xterm-fg-173: #d7875f;
+  $xterm-fg-174: #d78787;
+  $xterm-fg-175: #d787af;
+  $xterm-fg-176: #d787d7;
+  $xterm-fg-177: #d787ff;
+  $xterm-fg-178: #d7af00;
+  $xterm-fg-179: #d7af5f;
+  $xterm-fg-180: #d7af87;
+  $xterm-fg-181: #d7afaf;
+  $xterm-fg-182: #d7afd7;
+  $xterm-fg-183: #d7afff;
+  $xterm-fg-184: #d7d700;
+  $xterm-fg-185: #d7d75f;
+  $xterm-fg-186: #d7d787;
+  $xterm-fg-187: #d7d7af;
+  $xterm-fg-188: #d7d7d7;
+  $xterm-fg-189: #d7d7ff;
+  $xterm-fg-190: #d7ff00;
+  $xterm-fg-191: #d7ff5f;
+  $xterm-fg-192: #d7ff87;
+  $xterm-fg-193: #d7ffaf;
+  $xterm-fg-194: #d7ffd7;
+  $xterm-fg-195: #d7ffff;
+  $xterm-fg-196: #f00;
+  $xterm-fg-197: #ff005f;
+  $xterm-fg-198: #ff0087;
+  $xterm-fg-199: #ff00af;
+  $xterm-fg-200: #ff00d7;
+  $xterm-fg-201: #f0f;
+  $xterm-fg-202: #ff5f00;
+  $xterm-fg-203: #ff5f5f;
+  $xterm-fg-204: #ff5f87;
+  $xterm-fg-205: #ff5faf;
+  $xterm-fg-206: #ff5fd7;
+  $xterm-fg-207: #ff5fff;
+  $xterm-fg-208: #ff8700;
+  $xterm-fg-209: #ff875f;
+  $xterm-fg-210: #ff8787;
+  $xterm-fg-211: #ff87af;
+  $xterm-fg-212: #ff87d7;
+  $xterm-fg-213: #ff87ff;
+  $xterm-fg-214: #ffaf00;
+  $xterm-fg-215: #ffaf5f;
+  $xterm-fg-216: #ffaf87;
+  $xterm-fg-217: #ffafaf;
+  $xterm-fg-218: #ffafd7;
+  $xterm-fg-219: #ffafff;
+  $xterm-fg-220: #ffd700;
+  $xterm-fg-221: #ffd75f;
+  $xterm-fg-222: #ffd787;
+  $xterm-fg-223: #ffd7af;
+  $xterm-fg-224: #ffd7d7;
+  $xterm-fg-225: #ffd7ff;
+  $xterm-fg-226: #ff0;
+  $xterm-fg-227: #ffff5f;
+  $xterm-fg-228: #ffff87;
+  $xterm-fg-229: #ffffaf;
+  $xterm-fg-230: #ffffd7;
+  $xterm-fg-231: $white-light;
+  $xterm-fg-232: #080808;
+  $xterm-fg-233: #121212;
+  $xterm-fg-234: #1c1c1c;
+  $xterm-fg-235: #262626;
+  $xterm-fg-236: #303030;
+  $xterm-fg-237: #3a3a3a;
+  $xterm-fg-238: #444;
+  $xterm-fg-239: #4e4e4e;
+  $xterm-fg-240: #585858;
+  $xterm-fg-241: #626262;
+  $xterm-fg-242: #6c6c6c;
+  $xterm-fg-243: #767676;
+  $xterm-fg-244: #808080;
+  $xterm-fg-245: #8a8a8a;
+  $xterm-fg-246: #949494;
+  $xterm-fg-247: #9e9e9e;
+  $xterm-fg-248: #a8a8a8;
+  $xterm-fg-249: #b2b2b2;
+  $xterm-fg-250: #bcbcbc;
+  $xterm-fg-251: #c6c6c6;
+  $xterm-fg-252: #d0d0d0;
+  $xterm-fg-253: #dadada;
+  $xterm-fg-254: #e4e4e4;
+  $xterm-fg-255: #eee;
+
   .term-bold {
     font-weight: bold;
   }
@@ -169,1026 +429,1026 @@
   }
 
   .xterm-fg-0 {
-    color: #000;
+    color: $xterm-fg-0;
   }
 
   .xterm-fg-1 {
-    color: #800000;
+    color: $xterm-fg-1;
   }
 
   .xterm-fg-2 {
-    color: #008000;
+    color: $xterm-fg-2;
   }
 
   .xterm-fg-3 {
-    color: #808000;
+    color: $xterm-fg-3;
   }
 
   .xterm-fg-4 {
-    color: #000080;
+    color: $xterm-fg-4;
   }
 
   .xterm-fg-5 {
-    color: #800080;
+    color: $xterm-fg-5;
   }
 
   .xterm-fg-6 {
-    color: #008080;
+    color: $xterm-fg-6;
   }
 
   .xterm-fg-7 {
-    color: #c0c0c0;
+    color: $xterm-fg-7;
   }
 
   .xterm-fg-8 {
-    color: #808080;
+    color: $xterm-fg-8;
   }
 
   .xterm-fg-9 {
-    color: #f00;
+    color: $xterm-fg-9;
   }
 
   .xterm-fg-10 {
-    color: #0f0;
+    color: $xterm-fg-10;
   }
 
   .xterm-fg-11 {
-    color: #ff0;
+    color: $xterm-fg-11;
   }
 
   .xterm-fg-12 {
-    color: #00f;
+    color: $xterm-fg-12;
   }
 
   .xterm-fg-13 {
-    color: #f0f;
+    color: $xterm-fg-13;
   }
 
   .xterm-fg-14 {
-    color: #0ff;
+    color: $xterm-fg-14;
   }
 
   .xterm-fg-15 {
-    color: #fff;
+    color: $white-light;
   }
 
   .xterm-fg-16 {
-    color: #000;
+    color: $black;
   }
 
   .xterm-fg-17 {
-    color: #00005f;
+    color: $xterm-fg-17;
   }
 
   .xterm-fg-18 {
-    color: #000087;
+    color: $xterm-fg-18;
   }
 
   .xterm-fg-19 {
-    color: #0000af;
+    color: $xterm-fg-19;
   }
 
   .xterm-fg-20 {
-    color: #0000d7;
+    color: $xterm-fg-20;
   }
 
   .xterm-fg-21 {
-    color: #00f;
+    color: $xterm-fg-21;
   }
 
   .xterm-fg-22 {
-    color: #005f00;
+    color: $xterm-fg-22;
   }
 
   .xterm-fg-23 {
-    color: #005f5f;
+    color: $xterm-fg-23;
   }
 
   .xterm-fg-24 {
-    color: #005f87;
+    color: $xterm-fg-24;
   }
 
   .xterm-fg-25 {
-    color: #005faf;
+    color: $xterm-fg-25;
   }
 
   .xterm-fg-26 {
-    color: #005fd7;
+    color: $xterm-fg-26;
   }
 
   .xterm-fg-27 {
-    color: #005fff;
+    color: $xterm-fg-27;
   }
 
   .xterm-fg-28 {
-    color: #008700;
+    color: $xterm-fg-28;
   }
 
   .xterm-fg-29 {
-    color: #00875f;
+    color: $xterm-fg-29;
   }
 
   .xterm-fg-30 {
-    color: #008787;
+    color: $xterm-fg-30;
   }
 
   .xterm-fg-31 {
-    color: #0087af;
+    color: $xterm-fg-31;
   }
 
   .xterm-fg-32 {
-    color: #0087d7;
+    color: $xterm-fg-32;
   }
 
   .xterm-fg-33 {
-    color: #0087ff;
+    color: $xterm-fg-33;
   }
 
   .xterm-fg-34 {
-    color: #00af00;
+    color: $xterm-fg-34;
   }
 
   .xterm-fg-35 {
-    color: #00af5f;
+    color: $xterm-fg-35;
   }
 
   .xterm-fg-36 {
-    color: #00af87;
+    color: $xterm-fg-36;
   }
 
   .xterm-fg-37 {
-    color: #00afaf;
+    color: $xterm-fg-37;
   }
 
   .xterm-fg-38 {
-    color: #00afd7;
+    color: $xterm-fg-38;
   }
 
   .xterm-fg-39 {
-    color: #00afff;
+    color: $xterm-fg-39;
   }
 
   .xterm-fg-40 {
-    color: #00d700;
+    color: $xterm-fg-40;
   }
 
   .xterm-fg-41 {
-    color: #00d75f;
+    color: $xterm-fg-41;
   }
 
   .xterm-fg-42 {
-    color: #00d787;
+    color: $xterm-fg-42;
   }
 
   .xterm-fg-43 {
-    color: #00d7af;
+    color: $xterm-fg-43;
   }
 
   .xterm-fg-44 {
-    color: #00d7d7;
+    color: $xterm-fg-44;
   }
 
   .xterm-fg-45 {
-    color: #00d7ff;
+    color: $xterm-fg-45;
   }
 
   .xterm-fg-46 {
-    color: #0f0;
+    color: $xterm-fg-46;
   }
 
   .xterm-fg-47 {
-    color: #00ff5f;
+    color: $xterm-fg-47;
   }
 
   .xterm-fg-48 {
-    color: #00ff87;
+    color: $xterm-fg-48;
   }
 
   .xterm-fg-49 {
-    color: #00ffaf;
+    color: $xterm-fg-49;
   }
 
   .xterm-fg-50 {
-    color: #00ffd7;
+    color: $xterm-fg-50;
   }
 
   .xterm-fg-51 {
-    color: #0ff;
+    color: $xterm-fg-51;
   }
 
   .xterm-fg-52 {
-    color: #5f0000;
+    color: $xterm-fg-52;
   }
 
   .xterm-fg-53 {
-    color: #5f005f;
+    color: $xterm-fg-53;
   }
 
   .xterm-fg-54 {
-    color: #5f0087;
+    color: $xterm-fg-54;
   }
 
   .xterm-fg-55 {
-    color: #5f00af;
+    color: $xterm-fg-55;
   }
 
   .xterm-fg-56 {
-    color: #5f00d7;
+    color: $xterm-fg-56;
   }
 
   .xterm-fg-57 {
-    color: #5f00ff;
+    color: $xterm-fg-57;
   }
 
   .xterm-fg-58 {
-    color: #5f5f00;
+    color: $xterm-fg-58;
   }
 
   .xterm-fg-59 {
-    color: #5f5f5f;
+    color: $xterm-fg-59;
   }
 
   .xterm-fg-60 {
-    color: #5f5f87;
+    color: $xterm-fg-60;
   }
 
   .xterm-fg-61 {
-    color: #5f5faf;
+    color: $xterm-fg-61;
   }
 
   .xterm-fg-62 {
-    color: #5f5fd7;
+    color: $xterm-fg-62;
   }
 
   .xterm-fg-63 {
-    color: #5f5fff;
+    color: $xterm-fg-63;
   }
 
   .xterm-fg-64 {
-    color: #5f8700;
+    color: $xterm-fg-64;
   }
 
   .xterm-fg-65 {
-    color: #5f875f;
+    color: $xterm-fg-65;
   }
 
   .xterm-fg-66 {
-    color: #5f8787;
+    color: $xterm-fg-66;
   }
 
   .xterm-fg-67 {
-    color: #5f87af;
+    color: $xterm-fg-67;
   }
 
   .xterm-fg-68 {
-    color: #5f87d7;
+    color: $xterm-fg-68;
   }
 
   .xterm-fg-69 {
-    color: #5f87ff;
+    color: $xterm-fg-69;
   }
 
   .xterm-fg-70 {
-    color: #5faf00;
+    color: $xterm-fg-70;
   }
 
   .xterm-fg-71 {
-    color: #5faf5f;
+    color: $xterm-fg-71;
   }
 
   .xterm-fg-72 {
-    color: #5faf87;
+    color: $xterm-fg-72;
   }
 
   .xterm-fg-73 {
-    color: #5fafaf;
+    color: $xterm-fg-73;
   }
 
   .xterm-fg-74 {
-    color: #5fafd7;
+    color: $xterm-fg-74;
   }
 
   .xterm-fg-75 {
-    color: #5fafff;
+    color: $xterm-fg-75;
   }
 
   .xterm-fg-76 {
-    color: #5fd700;
+    color: $xterm-fg-76;
   }
 
   .xterm-fg-77 {
-    color: #5fd75f;
+    color: $xterm-fg-77;
   }
 
   .xterm-fg-78 {
-    color: #5fd787;
+    color: $xterm-fg-78;
   }
 
   .xterm-fg-79 {
-    color: #5fd7af;
+    color: $xterm-fg-79;
   }
 
   .xterm-fg-80 {
-    color: #5fd7d7;
+    color: $xterm-fg-80;
   }
 
   .xterm-fg-81 {
-    color: #5fd7ff;
+    color: $xterm-fg-81;
   }
 
   .xterm-fg-82 {
-    color: #5fff00;
+    color: $xterm-fg-82;
   }
 
   .xterm-fg-83 {
-    color: #5fff5f;
+    color: $xterm-fg-83;
   }
 
   .xterm-fg-84 {
-    color: #5fff87;
+    color: $xterm-fg-84;
   }
 
   .xterm-fg-85 {
-    color: #5fffaf;
+    color: $xterm-fg-85;
   }
 
   .xterm-fg-86 {
-    color: #5fffd7;
+    color: $xterm-fg-86;
   }
 
   .xterm-fg-87 {
-    color: #5fffff;
+    color: $xterm-fg-87;
   }
 
   .xterm-fg-88 {
-    color: #870000;
+    color: $xterm-fg-88;
   }
 
   .xterm-fg-89 {
-    color: #87005f;
+    color: $xterm-fg-89;
   }
 
   .xterm-fg-90 {
-    color: #870087;
+    color: $xterm-fg-90;
   }
 
   .xterm-fg-91 {
-    color: #8700af;
+    color: $xterm-fg-91;
   }
 
   .xterm-fg-92 {
-    color: #8700d7;
+    color: $xterm-fg-92;
   }
 
   .xterm-fg-93 {
-    color: #8700ff;
+    color: $xterm-fg-93;
   }
 
   .xterm-fg-94 {
-    color: #875f00;
+    color: $xterm-fg-94;
   }
 
   .xterm-fg-95 {
-    color: #875f5f;
+    color: $xterm-fg-95;
   }
 
   .xterm-fg-96 {
-    color: #875f87;
+    color: $xterm-fg-96;
   }
 
   .xterm-fg-97 {
-    color: #875faf;
+    color: $xterm-fg-97;
   }
 
   .xterm-fg-98 {
-    color: #875fd7;
+    color: $xterm-fg-98;
   }
 
   .xterm-fg-99 {
-    color: #875fff;
+    color: $xterm-fg-99;
   }
 
   .xterm-fg-100 {
-    color: #878700;
+    color: $xterm-fg-100;
   }
 
   .xterm-fg-101 {
-    color: #87875f;
+    color: $xterm-fg-101;
   }
 
   .xterm-fg-102 {
-    color: #878787;
+    color: $xterm-fg-102;
   }
 
   .xterm-fg-103 {
-    color: #8787af;
+    color: $xterm-fg-103;
   }
 
   .xterm-fg-104 {
-    color: #8787d7;
+    color: $xterm-fg-104;
   }
 
   .xterm-fg-105 {
-    color: #8787ff;
+    color: $xterm-fg-105;
   }
 
   .xterm-fg-106 {
-    color: #87af00;
+    color: $xterm-fg-106;
   }
 
   .xterm-fg-107 {
-    color: #87af5f;
+    color: $xterm-fg-107;
   }
 
   .xterm-fg-108 {
-    color: #87af87;
+    color: $xterm-fg-108;
   }
 
   .xterm-fg-109 {
-    color: #87afaf;
+    color: $xterm-fg-109;
   }
 
   .xterm-fg-110 {
-    color: #87afd7;
+    color: $xterm-fg-110;
   }
 
   .xterm-fg-111 {
-    color: #87afff;
+    color: $xterm-fg-111;
   }
 
   .xterm-fg-112 {
-    color: #87d700;
+    color: $xterm-fg-112;
   }
 
   .xterm-fg-113 {
-    color: #87d75f;
+    color: $xterm-fg-113;
   }
 
   .xterm-fg-114 {
-    color: #87d787;
+    color: $xterm-fg-114;
   }
 
   .xterm-fg-115 {
-    color: #87d7af;
+    color: $xterm-fg-115;
   }
 
   .xterm-fg-116 {
-    color: #87d7d7;
+    color: $xterm-fg-116;
   }
 
   .xterm-fg-117 {
-    color: #87d7ff;
+    color: $xterm-fg-117;
   }
 
   .xterm-fg-118 {
-    color: #87ff00;
+    color: $xterm-fg-118;
   }
 
   .xterm-fg-119 {
-    color: #87ff5f;
+    color: $xterm-fg-119;
   }
 
   .xterm-fg-120 {
-    color: #87ff87;
+    color: $xterm-fg-120;
   }
 
   .xterm-fg-121 {
-    color: #87ffaf;
+    color: $xterm-fg-121;
   }
 
   .xterm-fg-122 {
-    color: #87ffd7;
+    color: $xterm-fg-122;
   }
 
   .xterm-fg-123 {
-    color: #87ffff;
+    color: $xterm-fg-123;
   }
 
   .xterm-fg-124 {
-    color: #af0000;
+    color: $xterm-fg-124;
   }
 
   .xterm-fg-125 {
-    color: #af005f;
+    color: $xterm-fg-125;
   }
 
   .xterm-fg-126 {
-    color: #af0087;
+    color: $xterm-fg-126;
   }
 
   .xterm-fg-127 {
-    color: #af00af;
+    color: $xterm-fg-127;
   }
 
   .xterm-fg-128 {
-    color: #af00d7;
+    color: $xterm-fg-128;
   }
 
   .xterm-fg-129 {
-    color: #af00ff;
+    color: $xterm-fg-129;
   }
 
   .xterm-fg-130 {
-    color: #af5f00;
+    color: $xterm-fg-130;
   }
 
   .xterm-fg-131 {
-    color: #af5f5f;
+    color: $xterm-fg-131;
   }
 
   .xterm-fg-132 {
-    color: #af5f87;
+    color: $xterm-fg-132;
   }
 
   .xterm-fg-133 {
-    color: #af5faf;
+    color: $xterm-fg-133;
   }
 
   .xterm-fg-134 {
-    color: #af5fd7;
+    color: $xterm-fg-134;
   }
 
   .xterm-fg-135 {
-    color: #af5fff;
+    color: $xterm-fg-135;
   }
 
   .xterm-fg-136 {
-    color: #af8700;
+    color: $xterm-fg-136;
   }
 
   .xterm-fg-137 {
-    color: #af875f;
+    color: $xterm-fg-137;
   }
 
   .xterm-fg-138 {
-    color: #af8787;
+    color: $xterm-fg-138;
   }
 
   .xterm-fg-139 {
-    color: #af87af;
+    color: $xterm-fg-139;
   }
 
   .xterm-fg-140 {
-    color: #af87d7;
+    color: $xterm-fg-140;
   }
 
   .xterm-fg-141 {
-    color: #af87ff;
+    color: $xterm-fg-141;
   }
 
   .xterm-fg-142 {
-    color: #afaf00;
+    color: $xterm-fg-142;
   }
 
   .xterm-fg-143 {
-    color: #afaf5f;
+    color: $xterm-fg-143;
   }
 
   .xterm-fg-144 {
-    color: #afaf87;
+    color: $xterm-fg-144;
   }
 
   .xterm-fg-145 {
-    color: #afafaf;
+    color: $xterm-fg-145;
   }
 
   .xterm-fg-146 {
-    color: #afafd7;
+    color: $xterm-fg-146;
   }
 
   .xterm-fg-147 {
-    color: #afafff;
+    color: $xterm-fg-147;
   }
 
   .xterm-fg-148 {
-    color: #afd700;
+    color: $xterm-fg-148;
   }
 
   .xterm-fg-149 {
-    color: #afd75f;
+    color: $xterm-fg-149;
   }
 
   .xterm-fg-150 {
-    color: #afd787;
+    color: $xterm-fg-150;
   }
 
   .xterm-fg-151 {
-    color: #afd7af;
+    color: $xterm-fg-151;
   }
 
   .xterm-fg-152 {
-    color: #afd7d7;
+    color: $xterm-fg-152;
   }
 
   .xterm-fg-153 {
-    color: #afd7ff;
+    color: $xterm-fg-153;
   }
 
   .xterm-fg-154 {
-    color: #afff00;
+    color: $xterm-fg-154;
   }
 
   .xterm-fg-155 {
-    color: #afff5f;
+    color: $xterm-fg-155;
   }
 
   .xterm-fg-156 {
-    color: #afff87;
+    color: $xterm-fg-156;
   }
 
   .xterm-fg-157 {
-    color: #afffaf;
+    color: $xterm-fg-157;
   }
 
   .xterm-fg-158 {
-    color: #afffd7;
+    color: $xterm-fg-158;
   }
 
   .xterm-fg-159 {
-    color: #afffff;
+    color: $xterm-fg-159;
   }
 
   .xterm-fg-160 {
-    color: #d70000;
+    color: $xterm-fg-160;
   }
 
   .xterm-fg-161 {
-    color: #d7005f;
+    color: $xterm-fg-161;
   }
 
   .xterm-fg-162 {
-    color: #d70087;
+    color: $xterm-fg-162;
   }
 
   .xterm-fg-163 {
-    color: #d700af;
+    color: $xterm-fg-163;
   }
 
   .xterm-fg-164 {
-    color: #d700d7;
+    color: $xterm-fg-164;
   }
 
   .xterm-fg-165 {
-    color: #d700ff;
+    color: $xterm-fg-165;
   }
 
   .xterm-fg-166 {
-    color: #d75f00;
+    color: $xterm-fg-166;
   }
 
   .xterm-fg-167 {
-    color: #d75f5f;
+    color: $xterm-fg-167;
   }
 
   .xterm-fg-168 {
-    color: #d75f87;
+    color: $xterm-fg-168;
   }
 
   .xterm-fg-169 {
-    color: #d75faf;
+    color: $xterm-fg-169;
   }
 
   .xterm-fg-170 {
-    color: #d75fd7;
+    color: $xterm-fg-170;
   }
 
   .xterm-fg-171 {
-    color: #d75fff;
+    color: $xterm-fg-171;
   }
 
   .xterm-fg-172 {
-    color: #d78700;
+    color: $xterm-fg-172;
   }
 
   .xterm-fg-173 {
-    color: #d7875f;
+    color: $xterm-fg-173;
   }
 
   .xterm-fg-174 {
-    color: #d78787;
+    color: $xterm-fg-174;
   }
 
   .xterm-fg-175 {
-    color: #d787af;
+    color: $xterm-fg-175;
   }
 
   .xterm-fg-176 {
-    color: #d787d7;
+    color: $xterm-fg-176;
   }
 
   .xterm-fg-177 {
-    color: #d787ff;
+    color: $xterm-fg-177;
   }
 
   .xterm-fg-178 {
-    color: #d7af00;
+    color: $xterm-fg-178;
   }
 
   .xterm-fg-179 {
-    color: #d7af5f;
+    color: $xterm-fg-179;
   }
 
   .xterm-fg-180 {
-    color: #d7af87;
+    color: $xterm-fg-180;
   }
 
   .xterm-fg-181 {
-    color: #d7afaf;
+    color: $xterm-fg-181;
   }
 
   .xterm-fg-182 {
-    color: #d7afd7;
+    color: $xterm-fg-182;
   }
 
   .xterm-fg-183 {
-    color: #d7afff;
+    color: $xterm-fg-183;
   }
 
   .xterm-fg-184 {
-    color: #d7d700;
+    color: $xterm-fg-184;
   }
 
   .xterm-fg-185 {
-    color: #d7d75f;
+    color: $xterm-fg-185;
   }
 
   .xterm-fg-186 {
-    color: #d7d787;
+    color: $xterm-fg-186;
   }
 
   .xterm-fg-187 {
-    color: #d7d7af;
+    color: $xterm-fg-187;
   }
 
   .xterm-fg-188 {
-    color: #d7d7d7;
+    color: $xterm-fg-188;
   }
 
   .xterm-fg-189 {
-    color: #d7d7ff;
+    color: $xterm-fg-189;
   }
 
   .xterm-fg-190 {
-    color: #d7ff00;
+    color: $xterm-fg-190;
   }
 
   .xterm-fg-191 {
-    color: #d7ff5f;
+    color: $xterm-fg-191;
   }
 
   .xterm-fg-192 {
-    color: #d7ff87;
+    color: $xterm-fg-192;
   }
 
   .xterm-fg-193 {
-    color: #d7ffaf;
+    color: $xterm-fg-193;
   }
 
   .xterm-fg-194 {
-    color: #d7ffd7;
+    color: $xterm-fg-194;
   }
 
   .xterm-fg-195 {
-    color: #d7ffff;
+    color: $xterm-fg-195;
   }
 
   .xterm-fg-196 {
-    color: #f00;
+    color: $xterm-fg-196;
   }
 
   .xterm-fg-197 {
-    color: #ff005f;
+    color: $xterm-fg-197;
   }
 
   .xterm-fg-198 {
-    color: #ff0087;
+    color: $xterm-fg-198;
   }
 
   .xterm-fg-199 {
-    color: #ff00af;
+    color: $xterm-fg-199;
   }
 
   .xterm-fg-200 {
-    color: #ff00d7;
+    color: $xterm-fg-200;
   }
 
   .xterm-fg-201 {
-    color: #f0f;
+    color: $xterm-fg-201;
   }
 
   .xterm-fg-202 {
-    color: #ff5f00;
+    color: $xterm-fg-202;
   }
 
   .xterm-fg-203 {
-    color: #ff5f5f;
+    color: $xterm-fg-203;
   }
 
   .xterm-fg-204 {
-    color: #ff5f87;
+    color: $xterm-fg-204;
   }
 
   .xterm-fg-205 {
-    color: #ff5faf;
+    color: $xterm-fg-205;
   }
 
   .xterm-fg-206 {
-    color: #ff5fd7;
+    color: $xterm-fg-206;
   }
 
   .xterm-fg-207 {
-    color: #ff5fff;
+    color: $xterm-fg-207;
   }
 
   .xterm-fg-208 {
-    color: #ff8700;
+    color: $xterm-fg-208;
   }
 
   .xterm-fg-209 {
-    color: #ff875f;
+    color: $xterm-fg-209;
   }
 
   .xterm-fg-210 {
-    color: #ff8787;
+    color: $xterm-fg-210;
   }
 
   .xterm-fg-211 {
-    color: #ff87af;
+    color: $xterm-fg-211;
   }
 
   .xterm-fg-212 {
-    color: #ff87d7;
+    color: $xterm-fg-212;
   }
 
   .xterm-fg-213 {
-    color: #ff87ff;
+    color: $xterm-fg-213;
   }
 
   .xterm-fg-214 {
-    color: #ffaf00;
+    color: $xterm-fg-214;
   }
 
   .xterm-fg-215 {
-    color: #ffaf5f;
+    color: $xterm-fg-215;
   }
 
   .xterm-fg-216 {
-    color: #ffaf87;
+    color: $xterm-fg-216;
   }
 
   .xterm-fg-217 {
-    color: #ffafaf;
+    color: $xterm-fg-217;
   }
 
   .xterm-fg-218 {
-    color: #ffafd7;
+    color: $xterm-fg-218;
   }
 
   .xterm-fg-219 {
-    color: #ffafff;
+    color: $xterm-fg-219;
   }
 
   .xterm-fg-220 {
-    color: #ffd700;
+    color: $xterm-fg-220;
   }
 
   .xterm-fg-221 {
-    color: #ffd75f;
+    color: $xterm-fg-221;
   }
 
   .xterm-fg-222 {
-    color: #ffd787;
+    color: $xterm-fg-222;
   }
 
   .xterm-fg-223 {
-    color: #ffd7af;
+    color: $xterm-fg-223;
   }
 
   .xterm-fg-224 {
-    color: #ffd7d7;
+    color: $xterm-fg-224;
   }
 
   .xterm-fg-225 {
-    color: #ffd7ff;
+    color: $xterm-fg-225;
   }
 
   .xterm-fg-226 {
-    color: #ff0;
+    color: $xterm-fg-226;
   }
 
   .xterm-fg-227 {
-    color: #ffff5f;
+    color: $xterm-fg-227;
   }
 
   .xterm-fg-228 {
-    color: #ffff87;
+    color: $xterm-fg-228;
   }
 
   .xterm-fg-229 {
-    color: #ffffaf;
+    color: $xterm-fg-229;
   }
 
   .xterm-fg-230 {
-    color: #ffffd7;
+    color: $xterm-fg-230;
   }
 
   .xterm-fg-231 {
-    color: #fff;
+    color: $xterm-fg-231;
   }
 
   .xterm-fg-232 {
-    color: #080808;
+    color: $xterm-fg-232;
   }
 
   .xterm-fg-233 {
-    color: #121212;
+    color: $xterm-fg-233;
   }
 
   .xterm-fg-234 {
-    color: #1c1c1c;
+    color: $xterm-fg-234;
   }
 
   .xterm-fg-235 {
-    color: #262626;
+    color: $xterm-fg-235;
   }
 
   .xterm-fg-236 {
-    color: #303030;
+    color: $xterm-fg-236;
   }
 
   .xterm-fg-237 {
-    color: #3a3a3a;
+    color: $xterm-fg-237;
   }
 
   .xterm-fg-238 {
-    color: #444;
+    color: $xterm-fg-238;
   }
 
   .xterm-fg-239 {
-    color: #4e4e4e;
+    color: $xterm-fg-239;
   }
 
   .xterm-fg-240 {
-    color: #585858;
+    color: $xterm-fg-240;
   }
 
   .xterm-fg-241 {
-    color: #626262;
+    color: $xterm-fg-241;
   }
 
   .xterm-fg-242 {
-    color: #6c6c6c;
+    color: $xterm-fg-242;
   }
 
   .xterm-fg-243 {
-    color: #767676;
+    color: $xterm-fg-243;
   }
 
   .xterm-fg-244 {
-    color: #808080;
+    color: $xterm-fg-244;
   }
 
   .xterm-fg-245 {
-    color: #8a8a8a;
+    color: $xterm-fg-245;
   }
 
   .xterm-fg-246 {
-    color: #949494;
+    color: $xterm-fg-246;
   }
 
   .xterm-fg-247 {
-    color: #9e9e9e;
+    color: $xterm-fg-247;
   }
 
   .xterm-fg-248 {
-    color: #a8a8a8;
+    color: $xterm-fg-248;
   }
 
   .xterm-fg-249 {
-    color: #b2b2b2;
+    color: $xterm-fg-249;
   }
 
   .xterm-fg-250 {
-    color: #bcbcbc;
+    color: $xterm-fg-250;
   }
 
   .xterm-fg-251 {
-    color: #c6c6c6;
+    color: $xterm-fg-251;
   }
 
   .xterm-fg-252 {
-    color: #d0d0d0;
+    color: $xterm-fg-252;
   }
 
   .xterm-fg-253 {
-    color: #dadada;
+    color: $xterm-fg-253;
   }
 
   .xterm-fg-254 {
-    color: #e4e4e4;
+    color: $xterm-fg-254;
   }
 
   .xterm-fg-255 {
-    color: #eee;
+    color: $xterm-fg-255;
   }
 }
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b81842e319b29fef815274eeadb44588f874799d..c2bb846482479b9f25e5f7b12c2613c1e68e117a 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -112,6 +112,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :koding_enabled,
       :koding_url,
       :email_author_in_body,
+      :html_emails_enabled,
       :repository_checks_enabled,
       :metrics_packet_size,
       :send_user_confirmation_email,
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 5c44637fdee22180638311cd185c545ed25b6092..5f13353baa11877951033488fb659f6bcd7602cc 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController
     @users = @users.reorder(:name)
     @users = @users.page(params[:page])
 
-    if params[:todo_filter].present?
+    if params[:todo_filter].present? && current_user
       @users = @users.todo_authors(current_user.id, params[:todo_state_filter])
     end
 
diff --git a/app/helpers/lfs_helper.rb b/app/controllers/concerns/lfs_request.rb
similarity index 72%
rename from app/helpers/lfs_helper.rb
rename to app/controllers/concerns/lfs_request.rb
index 2425c3a8bc81344ff17cf681582ce6c4792ffd7a..ed22b1e54700fc9c2dcc5c66cbb8203ad989796f 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -1,5 +1,21 @@
-module LfsHelper
-  include Gitlab::Routing.url_helpers
+# This concern assumes:
+# - a `#project` accessor
+# - a `#user` accessor
+# - a `#authentication_result` accessor
+# - a `#can?(object, action, subject)` method
+# - a `#ci?` method
+# - a `#download_request?` method
+# - a `#upload_request?` method
+# - a `#has_authentication_ability?(ability)` method
+module LfsRequest
+  extend ActiveSupport::Concern
+
+  included do
+    before_action :require_lfs_enabled!
+    before_action :lfs_check_access!
+  end
+
+  private
 
   def require_lfs_enabled!
     return if Gitlab.config.lfs.enabled
@@ -17,35 +33,15 @@ module LfsHelper
     return if download_request? && lfs_download_access?
     return if upload_request? && lfs_upload_access?
 
-    if project.public? || (user && user.can?(:read_project, project))
-      render_lfs_forbidden
+    if project.public? || can?(user, :read_project, project)
+      lfs_forbidden!
     else
       render_lfs_not_found
     end
   end
 
-  def lfs_download_access?
-    return false unless project.lfs_enabled?
-
-    ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
-  end
-
-  def objects
-    @objects ||= (params[:objects] || []).to_a
-  end
-
-  def user_can_download_code?
-    has_authentication_ability?(:download_code) && can?(user, :download_code, project)
-  end
-
-  def build_can_download_code?
-    has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
-  end
-
-  def lfs_upload_access?
-    return false unless project.lfs_enabled?
-
-    has_authentication_ability?(:push_code) && can?(user, :push_code, project)
+  def lfs_forbidden!
+    render_lfs_forbidden
   end
 
   def render_lfs_forbidden
@@ -70,6 +66,30 @@ module LfsHelper
     )
   end
 
+  def lfs_download_access?
+    return false unless project.lfs_enabled?
+
+    ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
+  end
+
+  def lfs_upload_access?
+    return false unless project.lfs_enabled?
+
+    has_authentication_ability?(:push_code) && can?(user, :push_code, project)
+  end
+
+  def lfs_deploy_token?
+    authentication_result.lfs_deploy_token?(project)
+  end
+
+  def user_can_download_code?
+    has_authentication_ability?(:download_code) && can?(user, :download_code, project)
+  end
+
+  def build_can_download_code?
+    has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
+  end
+
   def storage_project
     @storage_project ||= begin
       result = project
@@ -82,4 +102,8 @@ module LfsHelper
       result
     end
   end
+
+  def objects
+    @objects ||= (params[:objects] || []).to_a
+  end
 end
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 3717c49f272f4d91f704267e5bc621e0c2adb79f..fbf9a026b108e14b80ca844d03951b52e6e171f2 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -1,11 +1,8 @@
 module ToggleAwardEmoji
   extend ActiveSupport::Concern
 
-  included do
-    before_action :authenticate_user!, only: [:toggle_award_emoji]
-  end
-
   def toggle_award_emoji
+    authenticate_user!
     name = params.require(:name)
 
     if awardable.user_can_award?(current_user, name)
diff --git a/app/controllers/concerns/workhorse_request.rb b/app/controllers/concerns/workhorse_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..43c0f1b173c9d39b2867c6e2d477250942f59916
--- /dev/null
+++ b/app/controllers/concerns/workhorse_request.rb
@@ -0,0 +1,13 @@
+module WorkhorseRequest
+  extend ActiveSupport::Concern
+
+  included do
+    before_action :verify_workhorse_api!
+  end
+
+  private
+
+  def verify_workhorse_api!
+    Gitlab::Workhorse.verify_api_request!(request.headers)
+  end
+end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 4b3c71874be1b764e54d7bd4ac2f9b51f7be2fa6..37feff79999561954faf962405280fa7488b9dd6 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -6,9 +6,11 @@ class HelpController < ApplicationController
   def index
     @help_index = File.read(Rails.root.join('doc', 'README.md'))
 
-    # Prefix Markdown links with `help/` unless they already have been
-    # See http://rubular.com/r/ie2MlpdUMq
-    @help_index.gsub!(/(\]\()(\/?help\/)?([^\)\(]+\))/, '\1/help/\3')
+    # Prefix Markdown links with `help/` unless they are external links
+    # See http://rubular.com/r/X3baHTbPO2
+    @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do
+      "#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}"
+    end
   end
 
   def show
diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb
index f193adb46b488734c3e7c294823714ac92062f6e..daa51ae41df0fc52a7c57b4efbdfd3e3f48a6c34 100644
--- a/app/controllers/profiles/avatars_controller.rb
+++ b/app/controllers/profiles/avatars_controller.rb
@@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController
     @user.remove_avatar!
 
     @user.save
-    @user.reset_events_cache
 
     redirect_to profile_path
   end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index ada7db3c552bf6c18dbd53b9e94cd8452abf5ecc..537886870761f9e3d7abfbc140a45cbd9e60983a 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController
     @project.remove_avatar!
 
     @project.save
-    @project.reset_events_cache
 
     redirect_to edit_project_path(@project)
   end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 56ced786311bb7a3911223590411d63c69a9a8ce..9940263ae2494479ef56d30ad8165f742b34878e 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController
   before_action :assign_blob_vars
   before_action :commit, except: [:new, :create]
   before_action :blob, except: [:new, :create]
-  before_action :from_merge_request, only: [:edit, :update]
   before_action :require_branch_head, only: [:edit, :update]
   before_action :editor_variables, except: [:show, :preview, :diff]
   before_action :validate_diff_params, only: :diff
@@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController
 
   def update
     @path = params[:file_path] if params[:file_path].present?
-    after_edit_path =
-      if from_merge_request && @target_branch == @ref
-        diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
-          "##{hexdigest(@path)}"
-      else
-        namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
-      end
-
     create_commit(Files::UpdateService, success_path: after_edit_path,
                                         failure_view: :edit,
                                         failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
@@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController
     render_404
   end
 
-  def from_merge_request
-    # If blob edit was initiated from merge request page
-    @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
+  def after_edit_path
+    from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
+    if from_merge_request && @target_branch == @ref
+      diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
+        "##{hexdigest(@path)}"
+    else
+      namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
+    end
   end
 
   def editor_variables
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 6b9f37983c47392be1126037b33512b4fc6deff0..89d84809e3abf5dd5e85a6d35c1180b5407026e6 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController
         execute(branch_name, ref)
 
     if params[:issue_iid]
-      issue = @project.issues.find_by(iid: params[:issue_iid])
+      issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid])
       SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue
     end
 
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index fd263960b93a5d975248eedad0eede761f39abd3..ac639ef015b546fdd16c86fb37efed8a3d47d90e 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
   before_action :authorize_read_cycle_analytics!
 
   def show
-    @cycle_analytics = ::CycleAnalytics.new(@project, from: start_date(cycle_analytics_params))
+    @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params))
 
     stats_values, cycle_analytics_json = generate_cycle_analytics_data
 
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 3f41916e6d3a38a8c37d2f3766cb21b90b33b844..8714349e27f3ef8c98cf88421d41481b285d5fda 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
 
   private
 
+  def download_request?
+    raise NotImplementedError
+  end
+
+  def upload_request?
+    raise NotImplementedError
+  end
+
   def authenticate_user
     @authentication_result = Gitlab::Auth::Result.new
 
@@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     authentication_result.ci?(project)
   end
 
-  def lfs_deploy_token?
-    authentication_result.lfs_deploy_token?(project)
-  end
-
   def authentication_has_download_access?
     has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
   end
@@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   def authentication_project
     authentication_result.project
   end
-
-  def verify_workhorse_api!
-    Gitlab::Workhorse.verify_api_request!(request.headers)
-  end
 end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 13caeb42d4061eb74dc1c76b3e887ef388eacb1d..9184dcccac557ba79a2ce9dde12779063b6586cb 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -1,7 +1,5 @@
-# This file should be identical in GitLab Community Edition and Enterprise Edition
-
 class Projects::GitHttpController < Projects::GitHttpClientController
-  before_action :verify_workhorse_api!
+  include WorkhorseRequest
 
   # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
   # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
@@ -67,14 +65,18 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def render_denied
-    if user && user.can?(:read_project, project)
-      render plain: 'Access denied', status: :forbidden
+    if user && can?(user, :read_project, project)
+      render plain: access_denied_message, status: :forbidden
     else
       # Do not leak information about project existence
       render_not_found
     end
   end
 
+  def access_denied_message
+    'Access denied'
+  end
+
   def upload_pack_allowed?
     return false unless Gitlab.config.gitlab_shell.upload_pack
 
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index 2d49327694131e47e3edaa321de4091bd62e61d0..440259b643cf8aeca69719ee3a96485d80360eff 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -1,8 +1,7 @@
 class Projects::LfsApiController < Projects::GitHttpClientController
-  include LfsHelper
+  include LfsRequest
 
-  before_action :require_lfs_enabled!
-  before_action :lfs_check_access!, except: [:deprecated]
+  skip_before_action :lfs_check_access!, only: [:deprecated]
 
   def batch
     unless objects.present?
@@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController
 
   private
 
+  def download_request?
+    params[:operation] == 'download'
+  end
+
+  def upload_request?
+    params[:operation] == 'upload'
+  end
+
   def existing_oids
     @existing_oids ||= begin
       storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
@@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController
       }
     }
   end
-
-  def download_request?
-    params[:operation] == 'download'
-  end
-
-  def upload_request?
-    params[:operation] == 'upload'
-  end
 end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 9005b104e901f2cda7a6f9397f23194e641a5b83..32759672b6c01ab2607466ca3b6ffdda6e3a196b 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -1,9 +1,8 @@
 class Projects::LfsStorageController < Projects::GitHttpClientController
-  include LfsHelper
+  include LfsRequest
+  include WorkhorseRequest
 
-  before_action :require_lfs_enabled!
-  before_action :lfs_check_access!
-  before_action :verify_workhorse_api!, only: [:upload_authorize]
+  skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize]
 
   def download
     lfs_object = LfsObject.find_by_oid(oid)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index e24a670631f1737f2290d25e5cd9c7f884b6f800..d2cef52842c7e09ed321bd44c3b27ef94048b058 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -325,16 +325,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     @merge_request.update(merge_error: nil)
 
     if params[:merge_when_build_succeeds].present?
-      unless @merge_request.pipeline
+      unless @merge_request.head_pipeline
         @status = :failed
         return
       end
 
-      if @merge_request.pipeline.active?
+      if @merge_request.head_pipeline.active?
         MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
                                                         .execute(@merge_request)
         @status = :merge_when_build_succeeds
-      elsif @merge_request.pipeline.success?
+      elsif @merge_request.head_pipeline.success?
         # This can be triggered when a user clicks the auto merge button while
         # the tests finish at about the same time
         MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@@ -398,7 +398,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def ci_status
-    pipeline = @merge_request.pipeline
+    pipeline = @merge_request.head_pipeline
+
     if pipeline
       status = pipeline.status
       coverage = pipeline.try(:coverage)
@@ -491,7 +492,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   def validates_merge_request
     # Show git not found page
     # if there is no saved commits between source & target branch
-    if @merge_request.commits.blank?
+    if @merge_request.has_no_commits?
       # and if target branch doesn't exist
       return invalid_mr unless @merge_request.target_branch_exists?
     end
@@ -499,7 +500,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def define_show_vars
     @noteable = @merge_request
-    @commits_count = @merge_request.commits.count
+    @commits_count = @merge_request.commits_count
 
     if @merge_request.locked_long_ago?
       @merge_request.unlock_mr
@@ -534,7 +535,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def define_widget_vars
-    @pipeline = @merge_request.pipeline
+    @pipeline = @merge_request.head_pipeline
   end
 
   def define_commit_vars
@@ -563,8 +564,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def define_pipelines_vars
     @pipelines = @merge_request.all_pipelines
-    @pipeline = @merge_request.pipeline
-    @statuses = @pipeline.statuses.relevant if @pipeline.present?
+    @pipeline = @merge_request.head_pipeline
+    @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
   end
 
   def define_new_vars
@@ -631,7 +632,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def merge_when_build_succeeds_active?
     params[:merge_when_build_succeeds].present? &&
-      @merge_request.pipeline && @merge_request.pipeline.active?
+      @merge_request.head_pipeline && @merge_request.head_pipeline.active?
   end
 
   def build_merge_request
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index f029fde2a2f78dd7523e05f6ba7d9a15031711cf..15ca080c6964d616a05614e61ef0740cf99ecbbd 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -197,6 +197,7 @@ class Projects::NotesController < Projects::ApplicationController
       )
     end
 
+    attrs[:commands_changes] = note.commands_changes unless attrs[:award]
     attrs
   end
 
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 533af80aee0ab929b9005975d972ad7c6a343db5..85188cfdd4ca7e597da0bdc4d2de33ca564aee72 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -1,6 +1,6 @@
 class Projects::PipelinesController < Projects::ApplicationController
   before_action :pipeline, except: [:index, :new, :create]
-  before_action :commit, only: [:show]
+  before_action :commit, only: [:show, :builds]
   before_action :authorize_read_pipeline!
   before_action :authorize_create_pipeline!, only: [:new, :create]
   before_action :authorize_update_pipeline!, only: [:retry, :cancel]
@@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController
   def show
   end
 
+  def builds
+    respond_to do |format|
+      format.html do
+        render 'show'
+      end
+    end
+  end
+
   def retry
     pipeline.retry_failed(current_user)
 
diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb
index 5685d0f4e7c892d30cfb97a543065814cb4fa8e3..52517381c65e04cf503b645f717f7aa5e338f7b9 100644
--- a/app/controllers/projects/todos_controller.rb
+++ b/app/controllers/projects/todos_controller.rb
@@ -16,13 +16,7 @@ class Projects::TodosController < Projects::ApplicationController
     @issuable ||= begin
       case params[:issuable_type]
       when "issue"
-        issue = @project.issues.find(params[:issuable_id])
-
-        if can?(current_user, :read_issue, issue)
-          issue
-        else
-          render_404
-        end
+        IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
       when "merge_request"
         @project.merge_requests.find(params[:issuable_id])
       end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 177ccf5eec963113407f3d8f1d8872da3b505b8c..c3353446fd115718de5aa54fedb0ae2381c0a938 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -115,6 +115,8 @@ class Projects::WikisController < Projects::ApplicationController
 
     # Call #wiki to make sure the Wiki Repo is initialized
     @project_wiki.wiki
+
+    @sidebar_wiki_pages = @project_wiki.pages.first(15)
   rescue ProjectWiki::CouldNotCreateWikiError
     flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
     redirect_to project_path(@project)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 6297b2db369080c9e2fc0dddf367ba717164236e..001c83ccb4b4d2636dbcf6a4cb3c41ca7318fb89 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -16,14 +16,12 @@
 #     label_name: string
 #     sort: string
 #
-require_relative 'projects_finder'
-
 class IssuableFinder
   NONE = '0'
 
   attr_accessor :current_user, :params
 
-  def initialize(current_user, params)
+  def initialize(current_user, params = {})
     @current_user = current_user
     @params = params
   end
@@ -43,6 +41,40 @@ class IssuableFinder
     sort(items)
   end
 
+  def find(*params)
+    execute.find(*params)
+  end
+
+  def find_by(*params)
+    execute.find_by(*params)
+  end
+
+  # We often get counts for each state by running a query per state, and
+  # counting those results. This is typically slower than running one query
+  # (even if that query is slower than any of the individual state queries) and
+  # grouping and counting within that query.
+  #
+  def count_by_state
+    count_params = params.merge(state: nil, sort: nil)
+    labels_count = label_names.any? ? label_names.count : 1
+    finder = self.class.new(current_user, count_params)
+    counts = Hash.new(0)
+
+    # Searching by label includes a GROUP BY in the query, but ours will be last
+    # because it is added last. Searching by multiple labels also includes a row
+    # per issuable, so we have to count those in Ruby - which is bad, but still
+    # better than performing multiple queries.
+    #
+    finder.execute.reorder(nil).group(:state).count.each do |key, value|
+      counts[Array(key).last.to_sym] += value / labels_count
+    end
+
+    counts[:all] = counts.values.sum
+    counts[:opened] += counts[:reopened]
+
+    counts
+  end
+
   def group
     return @group if defined?(@group)
 
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 0b7832e658308fbfde55a9ec421850120e1e4c0b..a653a6d59c6e3126d44c384cfff24e74e0c1e2d2 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -12,7 +12,7 @@ class NotesFinder
       when "commit"
         project.notes.for_commit_id(target_id).non_diff_notes
       when "issue"
-        project.issues.visible_to_user(current_user).find(target_id).notes.inc_author
+        IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author
       when "merge_request"
         project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
       when "snippet", "project_snippet"
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index cbab1fd5967c90ea15f7c2d618b4abdc144c3442..81e0b6bb5ae82828f61044f0d36fc6da15c66ad8 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -43,7 +43,7 @@ module DropdownsHelper
     default_label = data_attr[:default_label]
     content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
       output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
-      output << icon('caret-down')
+      output << icon('chevron-down')
       output.html_safe
     end
   end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 75cd9eece5cb70af36fba18a8ff97e9f1ebb6c80..19ab059aea6a86bb78b0fc86fbb98f3314e1a092 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -8,11 +8,7 @@ module GroupsHelper
       group = Group.find_by(path: group)
     end
 
-    if group && group.avatar.present?
-      group.avatar.url
-    else
-      image_path('no_group_avatar.png')
-    end
+    group.try(:avatar_url) || image_path('no_group_avatar.png')
   end
 
   def group_title(group, name = nil, url = nil)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 8bebda077876a6196837301a56788ac615ead952..8231f8fa334b871d419eac3c7ef78e6ec6bc0f6d 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -143,6 +143,20 @@ module IssuablesHelper
     end
   end
 
+  def issuable_filter_params
+    [
+      :search,
+      :author_id,
+      :assignee_id,
+      :milestone_title,
+      :label_name
+    ]
+  end
+
+  def issuable_filter_present?
+    issuable_filter_params.any? { |k| params.key?(k) }
+  end
+
   private
 
   def assigned_issuables_count(assignee, issuable_type, state)
@@ -165,17 +179,10 @@ module IssuablesHelper
     end
   end
 
-  def issuable_filters_present
-    params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name]
-  end
-
   def issuables_count_for_state(issuable_type, state)
-    issuables_finder = public_send("#{issuable_type}_finder")
-    
-    params = issuables_finder.params.merge(state: state)
-    finder = issuables_finder.class.new(issuables_finder.current_user, params)
-
-    finder.execute.page(1).total_count
+    @counts ||= {}
+    @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
+    @counts[issuable_type][state]
   end
 
   IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
@@ -185,6 +192,7 @@ module IssuablesHelper
     opts = params.with_indifferent_access
     opts[:state] = state
     opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+    opts.delete_if { |_, value| value.blank? }
 
     hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
   end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index df87fac132def9e5305c7e6ed9ba134dcf5496f7..a3331dc80cb5f91c5e25f8812880328c005b2b6a 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -20,6 +20,11 @@ module NavHelper
       end
     elsif current_path?('builds#show')
       "page-gutter build-sidebar right-sidebar-expanded"
+    elsif current_path?('wikis#show') ||
+      current_path?('wikis#edit') ||
+      current_path?('wikis#history') ||
+      current_path?('wikis#git_access')
+      "page-gutter wiki-sidebar right-sidebar-expanded"
     end
   end
 
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 898ce6a3af7531cad318c94d50a290779bcecb4a..9cda3b787613db6f04db163c788dc47cba16c40e 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -390,21 +390,7 @@ module ProjectsHelper
       "success"
     end
   end
-
-  def new_readme_path
-    ref = @repository.root_ref if @repository
-    ref ||= 'master'
-
-    namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
-  end
-
-  def new_license_path
-    ref = @repository.root_ref if @repository
-    ref ||= 'master'
-
-    namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
-  end
-
+  
   def readme_cache_key
     sha = @project.commit.try(:sha) || 'nil'
     [@project.path_with_namespace, sha, "readme"].join('-')
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 96116e916dd3cd807413029ee1a392de1f85f31f..0d20c9092c40ca85ffdfbb72364fcc71f055c206 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,6 +4,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @commit = @note.noteable
+      @discussion = @note.to_discussion if @note.diff_note?
       @target_url = namespace_project_commit_url(*note_target_url_options)
 
       mail_answer_thread(@commit,
@@ -24,6 +25,7 @@ module Emails
       setup_note_mail(note_id, recipient_id)
 
       @merge_request = @note.noteable
+      @discussion = @note.to_discussion if @note.diff_note?
       @target_url = namespace_project_merge_request_url(*note_target_url_options)
       mail_answer_thread(@merge_request, note_thread_options(recipient_id))
     end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e7d33bd26db65416f3f1b53599ad8eabe9f9a16c..88c46076df612c996114946927f4aeed6bf42d2e 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -203,7 +203,7 @@ module Ci
                                    .reorder(iid: :asc)
 
       merge_requests.find do |merge_request|
-        merge_request.commits.any? { |ci| ci.id == pipeline.sha }
+        merge_request.commits_sha.include?(pipeline.sha)
       end
     end
 
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3fee6c187700ee57e17cefdcccf830170d161d2b..fabbf97d4db3e9b5df683e69976756a294302924 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -161,23 +161,27 @@ module Ci
     end
 
     def retryable?
-      builds.latest.any? do |build|
-        (build.failed? || build.canceled?) && build.retryable?
-      end
+      builds.latest.failed_or_canceled.any?(&:retryable?)
     end
 
     def cancelable?
-      builds.running_or_pending.any?
+      statuses.cancelable.any?
     end
 
     def cancel_running
-      builds.running_or_pending.each(&:cancel)
+      Gitlab::OptimisticLocking.retry_lock(
+        statuses.cancelable) do |cancelable|
+          cancelable.each(&:cancel)
+        end
     end
 
     def retry_failed(user)
-      builds.latest.failed.select(&:retryable?).each do |build|
-        Ci::Build.retry(build, user)
-      end
+      Gitlab::OptimisticLocking.retry_lock(
+        builds.latest.failed_or_canceled) do |failed_or_canceled|
+          failed_or_canceled.select(&:retryable?).each do |build|
+            Ci::Build.retry(build, user)
+          end
+        end
     end
 
     def mark_as_processable_after_stage(stage_idx)
@@ -313,7 +317,7 @@ module Ci
     def merge_requests
       @merge_requests ||= project.merge_requests
         .where(source_branch: self.ref)
-        .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
+        .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
     end
 
     private
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 9e7fde9503d0fc0ade1b1bb52e6508f6114f3eb6..176c524cf7bab15d4acc5aff7e0ffbc2c98b3c89 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -48,6 +48,10 @@ class Commit
         max_lines: DIFF_HARD_LIMIT_LINES,
       }
     end
+
+    def from_hash(hash, project)
+      new(Gitlab::Git::Commit.new(hash), project)
+    end
   end
 
   attr_accessor :raw
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index ef3e73a4072d0cdc3ed0b77e16709d8d35a45055..2f5aa91a96416198e769fd7a3c177c6efbf96722 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -73,6 +73,11 @@ module HasStatus
     scope :skipped, -> { where(status: 'skipped')  }
     scope :running_or_pending, -> { where(status: [:running, :pending]) }
     scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+    scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
+
+    scope :cancelable, -> do
+      where(status: [:running, :pending, :created])
+    end
   end
 
   def started?
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index 7fd0905ee818d4e41109b006864d9f0af6680c3f..9dd4d9c6f24ddf461f125043d784ca56b863bbfa 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -2,6 +2,9 @@ module ProtectedBranchAccess
   extend ActiveSupport::Concern
 
   included do
+    belongs_to :protected_branch
+    delegate :project, to: :protected_branch
+
     scope :master, -> { where(access_level: Gitlab::Access::MASTER) }
     scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) }
   end
@@ -9,4 +12,10 @@ module ProtectedBranchAccess
   def humanize
     self.class.human_access_levels[self.access_level]
   end
+
+  def check_access(user)
+    return true if user.is_admin?
+
+    project.team.max_member_access(user.id) >= access_level
+  end
 end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index cb8e088d21d8da96fbff9e6d3152ddd4185e370f..ba4ee6fcf9d8d3d8767abf31583e45b0c4e0029d 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -1,14 +1,15 @@
 class CycleAnalytics
   STAGES = %i[issue plan code test review staging production].freeze
 
-  def initialize(project, from:)
+  def initialize(project, current_user, from:)
     @project = project
+    @current_user = current_user
     @from = from
     @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
   end
 
   def summary
-    @summary ||= Summary.new(@project, from: @from)
+    @summary ||= Summary.new(@project, @current_user, from: @from)
   end
 
   def permissions(user:)
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
index b46db449bf325d80688d7abe77f7515d1af640b3..82f53d17ddd06d39e624cc0a2bf89a72d5cd09ca 100644
--- a/app/models/cycle_analytics/summary.rb
+++ b/app/models/cycle_analytics/summary.rb
@@ -1,12 +1,13 @@
 class CycleAnalytics
   class Summary
-    def initialize(project, from:)
+    def initialize(project, current_user, from:)
       @project = project
+      @current_user = current_user
       @from = from
     end
 
     def new_issues
-      @project.issues.created_after(@from).count
+      IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count
     end
 
     def commits
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index de06c13481a1899aa80110139bbd6b700f6091b7..75a855632352066ce75452912ea20cc3e6e419e0 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -25,7 +25,12 @@ class Discussion
             to: :last_resolved_note,
             allow_nil: true
 
-  delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
+  delegate  :blob,
+            :highlighted_diff_lines,
+            :diff_lines,
+
+            to: :diff_file,
+            allow_nil: true
 
   def self.for_notes(notes)
     notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
@@ -159,10 +164,11 @@ class Discussion
   end
 
   # Returns an array of at most 16 highlighted lines above a diff note
-  def truncated_diff_lines
+  def truncated_diff_lines(highlight: true)
+    lines = highlight ? highlighted_diff_lines : diff_lines
     prev_lines = []
 
-    highlighted_diff_lines.each do |line|
+    lines.each do |line|
       if line.meta?
         prev_lines.clear
       else
diff --git a/app/models/event.rb b/app/models/event.rb
index 21eaca917b8449747ed1aad99b75252ba5d15a3a..2662f170765c89f5c26acebff02bd5d86492604c 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -43,12 +43,6 @@ class Event < ActiveRecord::Base
   scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
 
   class << self
-    def reset_event_cache_for(target)
-      Event.where(target_id: target.id, target_type: target.class.to_s).
-        order('id DESC').limit(100).
-        update_all(updated_at: Time.now)
-    end
-
     # Update Gitlab::ContributionsCalendar#activity_dates if this changes
     def contributions
       where("action = ? OR (target_type in (?) AND action in (?))",
@@ -353,6 +347,10 @@ class Event < ActiveRecord::Base
       update_all(last_activity_at: created_at)
   end
 
+  def authored_by?(user)
+    user ? author_id == user.id : false
+  end
+
   private
 
   def recent_update?
diff --git a/app/models/issue.rb b/app/models/issue.rb
index dd0cb75f9a8a718a7bae6a3af115f6c74eac0370..fbf07040301419e4fadc666f024da8b0b3b12d19 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -182,18 +182,6 @@ class Issue < ActiveRecord::Base
     branches_with_iid - branches_with_merge_request
   end
 
-  # Reset issue events cache
-  #
-  # Since we do cache @event we need to reset cache in special cases:
-  # * when an issue is updated
-  # Events cache stored like  events/23-20130109142513.
-  # The cache key includes updated_at timestamp.
-  # Thus it will automatically generate a new fragment
-  # when the event is updated because the key changes.
-  def reset_events_cache
-    Event.reset_event_cache_for(self)
-  end
-
   # To allow polymorphism with MergeRequest.
   def source_project
     project
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index fdf54cc8a7ed2cff80c2b7a2118a472fdba71b89..bfb016df46d33c9497c273a7ad1af5a434881b9e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -22,7 +22,8 @@ class MergeRequest < ActiveRecord::Base
   after_create :ensure_merge_request_diff, unless: :importing?
   after_update :reload_diff_if_branch_changed
 
-  delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
+  delegate :commits, :real_size, :commits_sha, :commits_count,
+    to: :merge_request_diff, prefix: nil
 
   # When this attribute is true some MR validation is ignored
   # It allows us to close or modify broken merge requests
@@ -605,18 +606,6 @@ class MergeRequest < ActiveRecord::Base
     self.target_project.repository.branch_names.include?(self.target_branch)
   end
 
-  # Reset merge request events cache
-  #
-  # Since we do cache @event we need to reset cache in special cases:
-  # * when a merge request is updated
-  # Events cache stored like  events/23-20130109142513.
-  # The cache key includes updated_at timestamp.
-  # Thus it will automatically generate a new fragment
-  # when the event is updated because the key changes.
-  def reset_events_cache
-    Event.reset_event_cache_for(self)
-  end
-
   def merge_commit_message
     message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
     message << "#{title}\n\n"
@@ -674,7 +663,7 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def broken?
-    self.commits.blank? || branch_missing? || cannot_be_merged?
+    has_no_commits? || branch_missing? || cannot_be_merged?
   end
 
   def can_be_merged_by?(user)
@@ -690,7 +679,7 @@ class MergeRequest < ActiveRecord::Base
   def mergeable_ci_state?
     return true unless project.only_allow_merge_if_build_succeeds?
 
-    !pipeline || pipeline.success? || pipeline.skipped?
+    !head_pipeline || head_pipeline.success? || head_pipeline.skipped?
   end
 
   def environments
@@ -782,18 +771,14 @@ class MergeRequest < ActiveRecord::Base
     diverged_commits_count > 0
   end
 
-  def commits_sha
-    commits.map(&:sha)
-  end
-
-  def pipeline
+  def head_pipeline
     return unless diff_head_sha && source_project
 
-    @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
+    @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
   end
 
   def all_pipelines
-    return unless source_project
+    return Ci::Pipeline.none unless source_project
 
     @all_pipelines ||= source_project.pipelines
       .where(sha: all_commits_sha, ref: source_branch)
@@ -883,4 +868,12 @@ class MergeRequest < ActiveRecord::Base
       @conflicts_can_be_resolved_in_ui = false
     end
   end
+
+  def has_commits?
+    commits_count > 0
+  end
+
+  def has_no_commits?
+    !has_commits?
+  end
 end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 58a24eb84cb4752e991ccc128056931e4a3982f6..b8f36a2c958fd6be30ac0db06b09ea34b0b0e606 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -127,11 +127,7 @@ class MergeRequestDiff < ActiveRecord::Base
   end
 
   def commits_sha
-    if @commits
-      commits.map(&:sha)
-    else
-      st_commits.map { |commit| commit[:id] }
-    end
+    st_commits.map { |commit| commit[:id] }
   end
 
   def diff_refs
@@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base
     CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
   end
 
+  def commits_count
+    st_commits.count
+  end
+
   private
 
   # Old GitLab implementations may have generated diffs as ["--broken-diff"].
diff --git a/app/models/note.rb b/app/models/note.rb
index 9ff5e308ed253770d903132282833280ca1288d8..5b50ca285c3a31cc5b05f1dccccc027c3b74baa4 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -19,6 +19,9 @@ class Note < ActiveRecord::Base
   # Banzai::ObjectRenderer
   attr_accessor :user_visible_reference_count
 
+  # Attribute used to store the attributes that have ben changed by slash commands.
+  attr_accessor :commands_changes
+
   default_value_for :system, false
 
   attr_mentionable :note, pipeline: :note
@@ -198,19 +201,6 @@ class Note < ActiveRecord::Base
     super(noteable_type.to_s.classify.constantize.base_class.to_s)
   end
 
-  # Reset notes events cache
-  #
-  # Since we do cache @event we need to reset cache in special cases:
-  # * when a note is updated
-  # * when a note is removed
-  # Events cache stored like  events/23-20130109142513.
-  # The cache key includes updated_at timestamp.
-  # Thus it will automatically generate a new fragment
-  # when the event is updated because the key changes.
-  def reset_events_cache
-    Event.reset_event_cache_for(self)
-  end
-
   def editable?
     !system?
   end
diff --git a/app/models/project.rb b/app/models/project.rb
index 9256e9ddd95900a7526487c76d3bede25d37dc02..f01cb613b8575c75b39fc1a7f42a15492cec47c7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -687,9 +687,9 @@ class Project < ActiveRecord::Base
     self.id
   end
 
-  def get_issue(issue_id)
+  def get_issue(issue_id, current_user)
     if default_issues_tracker?
-      issues.find_by(iid: issue_id)
+      IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
     else
       ExternalIssue.new(issue_id, self)
     end
@@ -976,7 +976,6 @@ class Project < ActiveRecord::Base
       begin
         gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
         send_move_instructions(old_path_with_namespace)
-        reset_events_cache
 
         @old_path_with_namespace = old_path_with_namespace
 
@@ -1043,22 +1042,6 @@ class Project < ActiveRecord::Base
     attrs
   end
 
-  # Reset events cache related to this project
-  #
-  # Since we do cache @event we need to reset cache in special cases:
-  # * when project was moved
-  # * when project was renamed
-  # * when the project avatar changes
-  # Events cache stored like  events/23-20130109142513.
-  # The cache key includes updated_at timestamp.
-  # Thus it will automatically generate a new fragment
-  # when the event is updated because the key changes.
-  def reset_events_cache
-    Event.where(project_id: self.id).
-      order('id DESC').limit(100).
-      update_all(updated_at: Time.now)
-  end
-
   def project_member(user)
     project_members.find_by(user_id: user)
   end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 70bbbbcda85530fd04f08a87088ccd4ed741403a..894315a8593fa9cf6e817c3abbb270e1f75caf79 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -9,6 +9,9 @@ class JiraService < IssueTrackerService
 
   before_update :reset_password
 
+  # This is confusing, but JiraService does not really support these events.
+  # The values here are required to display correct options in the service
+  # configuration screen.
   def supported_events
     %w(commit merge_request)
   end
@@ -105,18 +108,29 @@ class JiraService < IssueTrackerService
     "#{url}/secure/CreateIssue.jspa"
   end
 
-  def execute(push, issue = nil)
-    if issue.nil?
-      # No specific issue, that means
-      # we just want to test settings
-      test_settings
-    else
-      jira_issue = jira_request { client.Issue.find(issue.iid) }
+  def execute(push)
+    # This method is a no-op, because currently JiraService does not
+    # support any events.
+  end
 
-      return false unless jira_issue.present?
+  def close_issue(entity, external_issue)
+    issue = jira_request { client.Issue.find(external_issue.iid) }
 
-      close_issue(push, jira_issue)
-    end
+    return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
+
+    commit_id = if entity.is_a?(Commit)
+                  entity.id
+                elsif entity.is_a?(MergeRequest)
+                  entity.diff_head_sha
+                end
+
+    commit_url = build_entity_url(:commit, commit_id)
+
+    # Depending on the JIRA project's workflow, a comment during transition
+    # may or may not be allowed. Refresh the issue after transition and check
+    # if it is closed, so we don't have one comment for every commit.
+    issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
+    add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
   end
 
   def create_cross_reference_note(mentioned, noteable, author)
@@ -156,6 +170,11 @@ class JiraService < IssueTrackerService
     "Please fill in Password and Username."
   end
 
+  def test(_)
+    result = test_settings
+    { success: result.present?, result: result }
+  end
+
   def can_test?
     username.present? && password.present?
   end
@@ -182,24 +201,6 @@ class JiraService < IssueTrackerService
     end
   end
 
-  def close_issue(entity, issue)
-    return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
-
-    commit_id = if entity.is_a?(Commit)
-                  entity.id
-                elsif entity.is_a?(MergeRequest)
-                  entity.diff_head_sha
-                end
-
-    commit_url = build_entity_url(:commit, commit_id)
-
-    # Depending on the JIRA project's workflow, a comment during transition
-    # may or may not be allowed. Refresh the issue after transition and check
-    # if it is closed, so we don't have one comment for every commit.
-    issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
-    add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
-  end
-
   def transition_issue(issue)
     issue.transitions.build.save(transition: { id: jira_issue_transition_id })
   end
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index 806b3ccd27591421c5718bac60d77c21a1848b43..771e3376613555a79c182d38118237ed260fe587 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,9 +1,6 @@
 class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
   include ProtectedBranchAccess
 
-  belongs_to :protected_branch
-  delegate :project, to: :protected_branch
-
   validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
                                                              Gitlab::Access::DEVELOPER] }
 
@@ -13,10 +10,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
       Gitlab::Access::DEVELOPER => "Developers + Masters"
     }.with_indifferent_access
   end
-
-  def check_access(user)
-    return true if user.is_admin?
-
-    project.team.max_member_access(user.id) >= access_level
-  end
 end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index 92e9c51d883cd50b0ff6828cd94960f2d4fe0f21..14610cb42b7eb4abe96d9c98d41cc77c6dcbdd54 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,9 +1,6 @@
 class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
   include ProtectedBranchAccess
 
-  belongs_to :protected_branch
-  delegate :project, to: :protected_branch
-
   validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
                                                              Gitlab::Access::DEVELOPER,
                                                              Gitlab::Access::NO_ACCESS] }
@@ -18,8 +15,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
 
   def check_access(user)
     return false if access_level == Gitlab::Access::NO_ACCESS
-    return true if user.is_admin?
 
-    project.team.max_member_access(user.id) >= access_level
+    super
   end
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 09e8cc060c7ad1a678fa76afff570634fe5d7181..6bfa24f632910616ea150efe8002d81c97dd25c5 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -196,18 +196,12 @@ class Repository
 
     options = { message: message, tagger: user_to_committer(user) } if message
 
-    rugged.tags.create(tag_name, target, options)
-    tag = find_tag(tag_name)
-
-    GitHooksService.new.execute(user, path_to_repo, oldrev, tag.target, ref) do
-      # we already created a tag, because we need tag SHA to pass correct
-      # values to hooks
+    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service|
+      raw_tag = rugged.tags.create(tag_name, target, options)
+      service.newrev = raw_tag.target_id
     end
 
-    tag
-  rescue GitHooksService::PreReceiveError
-    rugged.tags.delete(tag_name)
-    raise
+    find_tag(tag_name)
   end
 
   def rm_branch(user, branch_name)
diff --git a/app/models/user.rb b/app/models/user.rb
index 513a19d81d27e6195dd27915976c332fef325731..b9bb4a9e3f7e98ec24e216f28bb8c0463cb45f5c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -445,27 +445,21 @@ class User < ActiveRecord::Base
   end
 
   def refresh_authorized_projects
-    loop do
-      begin
-        Gitlab::Database.serialized_transaction do
-          project_authorizations.delete_all
-
-          # project_authorizations_union can return multiple records for the same project/user with
-          # different access_level so we take row with the maximum access_level
-          project_authorizations.connection.execute <<-SQL
-            INSERT INTO project_authorizations (user_id, project_id, access_level)
-            SELECT user_id, project_id, MAX(access_level) AS access_level
-            FROM (#{project_authorizations_union.to_sql}) sub
-            GROUP BY user_id, project_id
-          SQL
-
-          update_column(:authorized_projects_populated, true) unless authorized_projects_populated
-        end
-
-        break
-      # In the event of a concurrent modification Rails raises StatementInvalid.
-      # In this case we want to keep retrying until the transaction succeeds
-      rescue ActiveRecord::StatementInvalid
+    transaction do
+      project_authorizations.delete_all
+
+      # project_authorizations_union can return multiple records for the same
+      # project/user with different access_level so we take row with the maximum
+      # access_level
+      project_authorizations.connection.execute <<-SQL
+      INSERT INTO project_authorizations (user_id, project_id, access_level)
+      SELECT user_id, project_id, MAX(access_level) AS access_level
+      FROM (#{project_authorizations_union.to_sql}) sub
+      GROUP BY user_id, project_id
+      SQL
+
+      unless authorized_projects_populated
+        update_column(:authorized_projects_populated, true)
       end
     end
   end
@@ -518,7 +512,7 @@ class User < ActiveRecord::Base
   end
 
   def require_ssh_key?
-    keys.count == 0
+    keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
   end
 
   def require_password?
@@ -708,20 +702,6 @@ class User < ActiveRecord::Base
       project.project_member(self)
   end
 
-  # Reset project events cache related to this user
-  #
-  # Since we do cache @event we need to reset cache in special cases:
-  # * when the user changes their avatar
-  # Events cache stored like  events/23-20130109142513.
-  # The cache key includes updated_at timestamp.
-  # Thus it will automatically generate a new fragment
-  # when the event is updated because the key changes.
-  def reset_events_cache
-    Event.where(author_id: id).
-      order('id DESC').limit(1000).
-      update_all(updated_at: Time.now)
-  end
-
   def full_website_url
     return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
 
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 1ee31023e26628b6f184d1b76c36dbe8372ef3f2..8ac4bd9df6d4ca1ef1f16aecdf2b2b0dc326885a 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -50,6 +50,7 @@ class ProjectPolicy < BasePolicy
 
   def reporter_access!
     can! :download_code
+    can! :download_wiki_code
     can! :fork_project
     can! :create_project_snippet
     can! :update_issue
@@ -187,6 +188,7 @@ class ProjectPolicy < BasePolicy
 
     unless project.feature_available?(:wiki, user) || project.has_external_wiki?
       cannot!(*named_abilities(:wiki))
+      cannot!(:download_wiki_code)
     end
 
     unless project.feature_available?(:builds, user) && repository_enabled
@@ -226,6 +228,7 @@ class ProjectPolicy < BasePolicy
     can! :read_commit_status
     can! :read_container_image
     can! :download_code
+    can! :download_wiki_code
     can! :read_cycle_analytics
 
     # NOTE: may be overridden by IssuePolicy
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb
index abefcd5cc02440ad5c7bcf9b7989c164b0c5cb21..a0db5b8f0f4cebe2db6be2f1d2f71ca09005f23c 100644
--- a/app/serializers/analytics_build_entity.rb
+++ b/app/serializers/analytics_build_entity.rb
@@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity
   end
 
   expose :duration, as: :total_time do |build|
-    distance_of_time_as_hash(build.duration.to_f)
+    build.duration ? distance_of_time_as_hash(build.duration.to_f) : {}
   end
 
   expose :branch do
diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb
index cf1c418a88e82e620626cc3d869dc684ec4cc086..b5384e6462b2967398a4f94143fa31c8b0401a56 100644
--- a/app/serializers/build_entity.rb
+++ b/app/serializers/build_entity.rb
@@ -16,6 +16,9 @@ class BuildEntity < Grape::Entity
     path_to(:play_namespace_project_build, build)
   end
 
+  expose :created_at
+  expose :updated_at
+
   private
 
   def path_to(route, build)
diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb
index 918abba8d998920043d3f3673083477609b246ae..9607ad55a8b76430cdd7b457884785a97ff76a09 100644
--- a/app/serializers/entity_date_helper.rb
+++ b/app/serializers/entity_date_helper.rb
@@ -2,6 +2,8 @@ module EntityDateHelper
   include ActionView::Helpers::DateHelper
 
   def interval_in_words(diff)
+    return 'Not started' unless diff
+
     "#{distance_of_time_in_words(Time.now, diff)} ago"
   end
 
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index cde856b0186833916b5848f0762e8fdfedf3b725..e3bc9847200a27d5a976d7eb413fa83fe54d4518 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -45,9 +45,15 @@ module Ci
         return error('No builds for this pipeline.')
       end
 
-      pipeline.save
-      pipeline.process!
-      pipeline
+      Ci::Pipeline.transaction do
+        pipeline.save
+
+        Ci::CreatePipelineBuildsService
+          .new(project, current_user)
+          .execute(pipeline)
+      end
+
+      pipeline.tap(&:process!)
     end
 
     private
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 8face432d97fc52dc0fbd6b7ef083c6a6fb6c1e2..2e028c44d8b2e57aa87e8ec0d475a8d70828a75d 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -5,10 +5,7 @@ module Ci
     def execute(pipeline)
       @pipeline = pipeline
 
-      # This method will ensure that our pipeline does have all builds for all stages created
-      if created_builds.empty?
-        create_builds!
-      end
+      ensure_created_builds! # TODO, remove me in 9.0
 
       new_builds =
         stage_indexes_of_created_builds.map do |index|
@@ -22,10 +19,6 @@ module Ci
 
     private
 
-    def create_builds!
-      Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline)
-    end
-
     def process_stage(index)
       current_status = status_for_prior_stages(index)
 
@@ -76,5 +69,18 @@ module Ci
     def created_builds
       pipeline.builds.created
     end
+
+    # This method is DEPRECATED and should be removed in 9.0.
+    #
+    # We need it to maintain backwards compatibility with  previous versions
+    # when builds were not created within one transaction with the pipeline.
+    #
+    def ensure_created_builds!
+      return if created_builds.any?
+
+      Ci::CreatePipelineBuildsService
+        .new(project, current_user)
+        .execute(pipeline)
+    end
   end
 end
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index 172bd85dadebab24868348f24a733cc1085cd1a3..6cd3908d43ad38d9fc3f43763e6f8763cb9d4cfe 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -1,6 +1,8 @@
 class GitHooksService
   PreReceiveError = Class.new(StandardError)
 
+  attr_accessor :oldrev, :newrev, :ref
+
   def execute(user, repo_path, oldrev, newrev, ref)
     @repo_path  = repo_path
     @user       = Gitlab::GlId.gl_id(user)
@@ -16,7 +18,7 @@ class GitHooksService
       end
     end
 
-    yield
+    yield self
 
     run_hook('post-receive')
   end
@@ -25,6 +27,6 @@ class GitHooksService
 
   def run_hook(name)
     hook = Gitlab::Git::Hook.new(name, @repo_path)
-    hook.trigger(@user, @oldrev, @newrev, @ref)
+    hook.trigger(@user, oldrev, newrev, ref)
   end
 end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 647930d555c25a7b2cde11b43d45903eaa426381..185556c12cccb0f6cfb04eb7b0b5a2e2c98c60fb 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -135,7 +135,7 @@ class GitPushService < BaseService
 
     @push_commits.each do |commit|
       ProcessCommitWorker.
-        perform_async(project.id, current_user.id, commit.id, default)
+        perform_async(project.id, current_user.id, commit.to_hash, default)
     end
   end
 
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 575795788de07eb82d87e90febc918343f391419..ce68e433ab8f1147eb5d269a665df4e9cd650e73 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -85,14 +85,15 @@ class IssuableBaseService < BaseService
 
   def find_or_create_label_ids
     labels = params.delete(:labels)
+
     return unless labels
 
-    params[:label_ids] = labels.split(',').map do |label_name|
+    params[:label_ids] = labels.split(",").map do |label_name|
       service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip)
       label   = service.execute
 
-      label.id
-    end
+      label.try(:id)
+    end.compact
   end
 
   def process_label_ids(attributes, existing_label_ids: nil)
@@ -140,6 +141,7 @@ class IssuableBaseService < BaseService
 
     params.delete(:state_event)
     params[:author] ||= current_user
+
     label_ids = process_label_ids(params)
 
     issuable.assign_attributes(params)
@@ -184,8 +186,6 @@ class IssuableBaseService < BaseService
     params[:label_ids] = process_label_ids(params, existing_label_ids: issuable.label_ids)
 
     if params.present? && update_issuable(issuable, params)
-      issuable.reset_events_cache
-
       # We do not touch as it will affect a update on updated_at field
       ActiveRecord::Base.no_touching do
         handle_common_system_notes(issuable, old_labels: old_labels)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index ab4c51386a42973cbe9f3d1ae3291fd7dbf48de6..f1030912c68750e348df38be54803eaa73735963 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -17,7 +17,7 @@ module Issues
     # allowed to close the given issue.
     def close_issue(issue, commit: nil, notifications: true, system_note: true)
       if project.jira_tracker? && project.jira_service.active
-        project.jira_service.execute(commit, issue)
+        project.jira_service.close_issue(commit, issue)
         todo_service.close_issue(issue, current_user)
         return issue
       end
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
index d622f9edd3334da3b208b95f6fee942115fc524e..cf4f7606c9442b6702ba41d75886d07deb209cbc 100644
--- a/app/services/labels/find_or_create_service.rb
+++ b/app/services/labels/find_or_create_service.rb
@@ -22,9 +22,14 @@ module Labels
       ).execute(skip_authorization: skip_authorization)
     end
 
+    # Only creates the label if current_user can do so, if the label does not exist
+    # and the user can not create the label, nil is returned
     def find_or_create_label
       new_label = available_labels.find_by(title: title)
-      new_label ||= project.labels.create(params)
+
+      if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project))
+        new_label = project.labels.create(params)
+      end
 
       new_label
     end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 58f69a41e14bb4d5281dd8a6a7085e1e84342caa..800fd39c42490a9ca025d68e6a0e9e4c616cde84 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -55,7 +55,7 @@ module MergeRequests
 
     def pipeline_merge_requests(pipeline)
       merge_requests_for(pipeline.ref).each do |merge_request|
-        next unless pipeline == merge_request.pipeline
+        next unless pipeline == merge_request.head_pipeline
 
         yield merge_request
       end
@@ -63,7 +63,7 @@ module MergeRequests
 
     def commit_status_merge_requests(commit_status)
       merge_requests_for(commit_status.ref).each do |merge_request|
-        pipeline = merge_request.pipeline
+        pipeline = merge_request.head_pipeline
 
         next unless pipeline
         next unless pipeline.sha == commit_status.sha
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index dd0d738674e22bdc150d5453b1ee2912c039d5c8..bebfca7537bf25caa22e1d7e443c5af11a5ed2d0 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -81,7 +81,7 @@ module MergeRequests
         commit = commits.first
         merge_request.title = commit.title
         merge_request.description ||= commit.description.try(:strip)
-      elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?)
+      elsif iid && issue = merge_request.target_project.get_issue(iid, current_user)
         case issue
         when Issue
           merge_request.title = "Resolve \"#{issue.title}\""
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 22596b4014ab3c77c62634139a2a10a0f3bd09fd..e4056306bc45c06aa0f2a8004155e6d1f6ce545f 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -63,7 +63,7 @@ module MergeRequests
         if merge_request.source_branch == @branch_name || force_push?
           merge_request.reload_diff
         else
-          mr_commit_ids = merge_request.commits.map(&:id)
+          mr_commit_ids = merge_request.commits_sha
           push_commit_ids = @commits.map(&:id)
           matches = mr_commit_ids & push_commit_ids
           merge_request.reload_diff if matches.any?
@@ -123,7 +123,7 @@ module MergeRequests
       return unless @commits.present?
 
       merge_requests_for_source_branch.each do |merge_request|
-        mr_commit_ids = Set.new(merge_request.commits.map(&:id))
+        mr_commit_ids = Set.new(merge_request.commits_sha)
 
         new_commits, existing_commits = @commits.partition do |commit|
           mr_commit_ids.include?(commit.id)
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 7935fabe2da6a2c844b538679e7cc9c8115fe7db..d75592e31f300aec13f4c459eadaeeffddb451a9 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -43,6 +43,8 @@ module Notes
         if only_commands
           note.errors.add(:commands_only, 'Your commands have been executed!')
         end
+
+        note.commands_changes = command_params.keys
       end
 
       note
diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb
index 7f1b30ec84ea9cae0dae44a8e6d1c4d98ed29a9f..a673e8e9dde2ac568dfeeca01d6f3be77105dbf4 100644
--- a/app/services/notes/delete_service.rb
+++ b/app/services/notes/delete_service.rb
@@ -2,7 +2,6 @@ module Notes
   class DeleteService < BaseService
     def execute(note)
       note.destroy
-      note.reset_events_cache
     end
   end
 end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 1361b1e0300329fbc9521876d98062dffa448099..75a4b3ed8264f496e82a2f7bedaf30953d013516 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -5,7 +5,6 @@ module Notes
 
       note.update_attributes(params.merge(updated_by: current_user))
       note.create_new_cross_references!(current_user)
-      note.reset_events_cache
 
       if note.previous_changes.include?('note')
         TodoService.new.update_note(note, current_user)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index ecdcbf08ee10a8ec881bff7716ce1b12673ce012..9a7af5730d2b3f5a43f821e6084c00e67017d1af 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -171,7 +171,6 @@ class NotificationService
     return true unless note.noteable_type.present?
 
     # ignore gitlab service messages
-    return true if note.note.start_with?('Status changed to closed')
     return true if note.cross_reference? && note.system?
 
     target = note.noteable
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 28470f59807cb48c578d77e354b22aadbfe6aa86..34ec575e8084da0639133fb3e5fd6142ab3ff3e0 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -61,9 +61,6 @@ module Projects
         # Move missing group labels to project
         Labels::TransferService.new(current_user, old_group, project).execute
 
-        # clear project cached events
-        project.reset_events_cache
-
         # Move uploads
         Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
 
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1ce66d50368805498468337c29c2831463b7de8d..a33845848b406ba5a213a9a56a64bdcd636b2097 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -21,7 +21,7 @@ module SystemNoteService
     total_count  = new_commits.length + existing_commits.length
     commits_text = "#{total_count} commit".pluralize(total_count)
 
-    body = "Added #{commits_text}:\n\n"
+    body = "added #{commits_text}\n\n"
     body << existing_commit_summary(noteable, existing_commits, oldrev)
     body << new_commit_summary(new_commits).join("\n")
     body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
@@ -38,13 +38,13 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Assignee removed"
+  #   "removed assignee"
   #
-  #   "Reassigned to @rspeicher"
+  #   "assigned to @rspeicher"
   #
   # Returns the created Note object
   def change_assignee(noteable, project, author, assignee)
-    body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}"
+    body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
@@ -59,11 +59,11 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Added ~1 and removed ~2 ~3 labels"
+  #   "added ~1 and removed ~2 ~3 labels"
   #
-  #   "Added ~4 label"
+  #   "added ~4 label"
   #
-  #   "Removed ~5 label"
+  #   "removed ~5 label"
   #
   # Returns the created Note object
   def change_label(noteable, project, author, added_labels, removed_labels)
@@ -85,7 +85,6 @@ module SystemNoteService
     end
 
     body << ' ' << 'label'.pluralize(labels_count)
-    body = body.capitalize
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
@@ -99,14 +98,13 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Milestone removed"
+  #   "removed milestone"
   #
-  #   "Miletone changed to 7.11"
+  #   "changed milestone to 7.11"
   #
   # Returns the created Note object
   def change_milestone(noteable, project, author, milestone)
-    body = 'Milestone '
-    body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}"
+    body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
@@ -121,46 +119,46 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Status changed to merged"
+  #   "merged"
   #
-  #   "Status changed to closed by bc17db76"
+  #   "closed via bc17db76"
   #
   # Returns the created Note object
   def change_status(noteable, project, author, status, source)
-    body = "Status changed to #{status}"
-    body << " by #{source.gfm_reference(project)}" if source
+    body = status.dup
+    body << " via #{source.gfm_reference(project)}" if source
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   # Called when 'merge when build succeeds' is executed
   def merge_when_build_succeeds(noteable, project, author, last_commit)
-    body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
+    body = "enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   # Called when 'merge when build succeeds' is canceled
   def cancel_merge_when_build_succeeds(noteable, project, author)
-    body = 'Canceled the automatic merge'
+    body = 'canceled the automatic merge'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   def remove_merge_request_wip(noteable, project, author)
-    body = 'Unmarked this merge request as a Work In Progress'
+    body = 'unmarked as a Work In Progress'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   def add_merge_request_wip(noteable, project, author)
-    body = 'Marked this merge request as a **Work In Progress**'
+    body = 'marked as a **Work In Progress**'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   def self.resolve_all_discussions(merge_request, project, author)
-    body = "Resolved all discussions"
+    body = "resolved all discussions"
 
     create_note(noteable: merge_request, project: project, author: author, note: body)
   end
@@ -174,7 +172,7 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Title changed from **Old** to **New**"
+  #   "changed title from **Old** to **New**"
   #
   # Returns the created Note object
   def change_title(noteable, project, author, old_title)
@@ -185,7 +183,7 @@ module SystemNoteService
     marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true)
     marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
 
-    body = "Changed title: **#{marked_old_title}** → **#{marked_new_title}**"
+    body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**"
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
@@ -197,11 +195,11 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  # "Made the issue confidential"
+  # "made the issue confidential"
   #
   # Returns the created Note object
   def change_issue_confidentiality(issue, project, author)
-    body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
+    body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone'
     create_note(noteable: issue, project: project, author: author, note: body)
   end
 
@@ -216,11 +214,11 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Target branch changed from `Old` to `New`"
+  #   "changed target branch from `Old` to `New`"
   #
   # Returns the created Note object
   def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
-    body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize
+    body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
@@ -235,7 +233,7 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Restored target branch `feature`"
+  #   "restored target branch `feature`"
   #
   # Returns the created Note object
   def change_branch_presence(noteable, project, author, branch_type, branch, presence)
@@ -246,18 +244,18 @@ module SystemNoteService
         'deleted'
       end
 
-    body = "#{verb} #{branch_type} branch `#{branch}`".capitalize
+    body = "#{verb} #{branch_type} branch `#{branch}`"
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   # Called when a branch is created from the 'new branch' button on a issue
   # Example note text:
   #
-  #   "Started branch `201-issue-branch-button`"
+  #   "created branch `201-issue-branch-button`"
   def new_issue_branch(issue, project, author, branch)
     link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
 
-    body = "Started branch [`#{branch}`](#{link})"
+    body = "created branch [`#{branch}`](#{link})"
     create_note(noteable: issue, project: project, author: author, note: body)
   end
 
@@ -269,11 +267,11 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Mentioned in #1"
+  #   "mentioned in #1"
   #
-  #   "Mentioned in !2"
+  #   "mentioned in !2"
   #
-  #   "Mentioned in 54f7727c"
+  #   "mentioned in 54f7727c"
   #
   # See cross_reference_note_content.
   #
@@ -303,12 +301,12 @@ module SystemNoteService
   end
 
   def cross_reference?(note_text)
-    note_text.start_with?(cross_reference_note_prefix)
+    note_text =~ /\A#{cross_reference_note_prefix}/i
   end
 
   # Check if a cross-reference is disallowed
   #
-  # This method prevents adding a "Mentioned in !1" note on every single commit
+  # This method prevents adding a "mentioned in !1" note on every single commit
   # in a merge request. Additionally, it prevents the creation of references to
   # external issues (which would fail).
   #
@@ -370,12 +368,12 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Soandso marked the task Whatever as completed."
+  #   "marked the task Whatever as completed."
   #
   # Returns the created Note object
   def change_task_status(noteable, project, author, new_task)
     status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
-    body = "Marked the task **#{new_task.source}** as #{status_label}"
+    body = "marked the task **#{new_task.source}** as #{status_label}"
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
@@ -388,7 +386,7 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "Moved to some_namespace/project_new#11"
+  #   "moved to some_namespace/project_new#11"
   #
   # Returns the created Note object
   def noteable_moved(noteable, project, noteable_ref, author, direction:)
@@ -397,7 +395,7 @@ module SystemNoteService
     end
 
     cross_reference = noteable_ref.to_reference(project)
-    body = "Moved #{direction} #{cross_reference}"
+    body = "moved #{direction} #{cross_reference}"
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
@@ -405,10 +403,12 @@ module SystemNoteService
 
   def notes_for_mentioner(mentioner, noteable, notes)
     if mentioner.is_a?(Commit)
-      notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
+      text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
+      notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize)
     else
       gfm_reference = mentioner.gfm_reference(noteable.project)
-      notes.where(note: cross_reference_note_content(gfm_reference))
+      text = cross_reference_note_content(gfm_reference)
+      notes.where(note: [text, text.capitalize])
     end
   end
 
@@ -417,7 +417,7 @@ module SystemNoteService
   end
 
   def cross_reference_note_prefix
-    'Mentioned in '
+    'mentioned in '
   end
 
   def cross_reference_note_content(gfm_reference)
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 71ff14a3f20d497b2bfccc78820ce433c8395c1e..38683fdf6d7cb47a274e48a205770df7dd481dc1 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -3,16 +3,10 @@ class AvatarUploader < CarrierWave::Uploader::Base
 
   storage :file
 
-  after :store, :reset_events_cache
-
   def store_dir
     "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
   end
 
-  def reset_events_cache(file)
-    model.reset_events_cache if model.is_a?(User)
-  end
-
   def exists?
     model.avatar.file && model.avatar.file.exists?
   end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index ce803f329f91208d1d6b7a4e142268a699c1c3a3..7accd2529af704279a86cd9aba9e07e24a24d424 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -443,7 +443,16 @@
             Some email servers do not support overriding the email sender name.
             Enable this option to include the name of the author of the issue,
             merge request or comment in the email body instead.
-
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :html_emails_enabled do
+            = f.check_box :html_emails_enabled
+            Enable HTML emails
+          .help-block
+            By default GitLab sends emails in HTML and plain text formats so mail
+            clients can choose what format to use. Disable this option if you only
+            want to send emails in plain text format.
   %fieldset
     %legend Automatic Git repository housekeeping
     .form-group
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index f7abad542862393c4a29e28f06e23d35a6f91cf9..48b0fd504f4f8c5645787d7d4b6f9cc45e1ee850 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -4,13 +4,13 @@
   %ul.nav-links
     = nav_link(page: [dashboard_projects_path, root_path]) do
       = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
-        Your Projects
+        Your projects
     = nav_link(page: starred_dashboard_projects_path) do
       = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
-        Starred Projects
+        Starred projects
     = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
       = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
-        Explore Projects
+        Explore projects
 
   .nav-controls
     = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index fdea834ff45954c8ac106e0e617f1bafe5b24a65..4a55aac0df6785016767c8f850e991bd8ef3bb46 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -4,6 +4,18 @@
     Welcome to GitLab
   %p.blank-state-text
     Code, test, and deploy together
+
+- if current_user.can_create_group?
+  .blank-state
+    .blank-state-icon
+      = custom_icon("group", size: 50)
+    %h3.blank-state-title
+      You can create a group for several dependent projects.
+    %p.blank-state-text
+      Groups are the best way to manage projects and members.
+    = link_to new_group_path, class: "btn btn-new" do
+      New group
+
 .blank-state
   .blank-state-icon
     = custom_icon("project", size: 50)
@@ -21,17 +33,6 @@
     = link_to new_project_path, class: "btn btn-new" do
       New project
 
-- if current_user.can_create_group?
-  .blank-state
-    .blank-state-icon
-      = custom_icon("group", size: 50)
-    %h3.blank-state-title
-      You can create a group for several dependent projects.
-    %p.blank-state-text
-      Groups are the best way to manage projects and members.
-    = link_to new_group_path, class: "btn btn-new" do
-      New group
-
 -if publicish_project_count > 0
   .blank-state
     .blank-state-icon
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 472d698486b334f84d57b0559951b6f5001d413d..62f52086be41ea60027a50633cdd6be005f4b470 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -50,13 +50,13 @@
             data: { data: todo_actions_options }})
         .pull-right
           .dropdown.inline.prepend-left-10
-            %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+            %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'}
               %span.light
               - if @sort.present?
                 = sort_options_hash[@sort]
               - else
                 = sort_title_recently_created
-              = icon('caret-down')
+              = icon('chevron-down')
             %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
               %li
                 = link_to todos_filter_path(sort: sort_value_priority) do
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index e4b4ea675d2fbe6665b071369b9577f27e1757d7..2bce27804849fe3c9c5649a5db0c22d280824daf 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -32,6 +32,7 @@
                 an outdated diff
 
             = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
+          = render "discussions/headline", discussion: discussion
 
         .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
           - if discussion.diff_discussion? && discussion.diff_file
diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml
index c034bbe430ef69a8bb6ab24d99232e28ef19e8d8..8bddbef3562c9fb29dd37bc6bd4794c69121d1cb 100644
--- a/app/views/errors/access_denied.html.haml
+++ b/app/views/errors/access_denied.html.haml
@@ -1,6 +1,10 @@
-- page_title "Access Denied"
-%h1 403
-%h3 Access Denied
-%hr
-%p You are not allowed to access this page.
-%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"}
+- content_for(:title, 'Access Denied')
+%img{:alt => "GitLab Logo",
+     :src => image_path('logo.svg')}
+  %h1
+    403
+.container
+  %h3 Access Denied
+  %hr
+  %p You are not allowed to access this page.
+  %p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"}
diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml
index 90cfbebfcc67212f86b7c0ad27407c3c5051eea0..064ff14ad2c77ef8175ba618aa5f808f522f7fdf 100644
--- a/app/views/errors/encoding.html.haml
+++ b/app/views/errors/encoding.html.haml
@@ -1,5 +1,9 @@
-- page_title "Encoding Error"
-%h1 500
-%h3 Encoding Error
-%hr
-%p Page can't be loaded because of an encoding error.
+- content_for(:title, 'Encoding Error')
+%img{:alt => "GitLab Logo",
+     :src => image_path('logo.svg')}
+  %h1
+    500
+.container
+  %h3 Encoding Error
+  %hr
+  %p Page can't be loaded because of an encoding error.
diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml
index ff5d4cc1506f57a541c6990e9b33d28cda777e7e..c5c12a410acf10f878c656f8771f98698c7a9300 100644
--- a/app/views/errors/git_not_found.html.haml
+++ b/app/views/errors/git_not_found.html.haml
@@ -1,7 +1,10 @@
-- page_title "Git Resource Not Found"
-%h1 404
-%h3 Git Resource Not found
-%hr
-%p
-  Application can't get access to some branch or commit in your repository. It
-  may have been moved.
+- content_for(:title, 'Git Resource Not Found')
+%img{:alt => "GitLab Logo",
+     :src => image_path('logo.svg')}
+  %h1
+    404
+.container
+  %h3 Git Resource Not found
+  %hr
+  %p Application can't get access to some branch or commit in your repository. It
+  may have been moved
diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml
index 3756b98ebb2e59148b3c91b38212e3259e72e714..50a54a93cb56f5e4a17c18da5e47563b506685ea 100644
--- a/app/views/errors/not_found.html.haml
+++ b/app/views/errors/not_found.html.haml
@@ -1,5 +1,9 @@
-- page_title "Not Found"
-%h1 404
-%h3 The resource you were looking for doesn't exist.
-%hr
-%p You may have mistyped the address or the page may have moved.
+- content_for(:title, 'Not Found')
+%img{:alt => "GitLab Logo",
+     :src => image_path('logo.svg')}
+  %h1
+    404
+.container
+  %h3 The resource you were looking for doesn't exist.
+  %hr
+  %p You may have mistyped the address or the page may have moved.
diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml
index 3e70e98a24c55456052dc119d2235f8927e50c05..d91f1878cb6cebb839b5919f7ac39d2be88ceca7 100644
--- a/app/views/errors/omniauth_error.html.haml
+++ b/app/views/errors/omniauth_error.html.haml
@@ -1,9 +1,13 @@
-- page_title "Auth Error"
-%h1 422
-%h3 Sign-in using #{@provider} auth failed
-%hr
-%p Sign-in failed because #{@error}.
-%p There are couple of steps you can take:
+- content_for(:title, 'Auth Error')
+%img{:alt => "GitLab Logo",
+     :src => image_path('logo.svg')}
+  %h1
+    422
+.container
+  %h3 Sign-in using #{@provider} auth failed
+  %hr
+  %p Sign-in failed because #{@error}.
+  %p There are couple of steps you can take:
 
 %ul
   %li Try logging in using your email
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 5c318cd3b8bdcff0f5e7212f654e9c1cf62ef205..a0bd14df2096cd7462c29bcd701f04431f8a89ed 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,14 +3,13 @@
     .event-item-timestamp
       #{time_ago_with_tooltip(event.created_at)}
 
-    = cache [event, current_application_settings, "v2.2"] do
-      = author_avatar(event, size: 40)
+    = author_avatar(event, size: 40)
 
-      - if event.created_project?
-        = render "events/event/created_project", event: event
-      - elsif event.push?
-        = render "events/event/push", event: event
-      - elsif event.commented?
-        = render "events/event/note", event: event
-      - else
-        = render "events/event/common", event: event
+    - if event.created_project?
+      = render "events/event/created_project", event: event
+    - elsif event.push?
+      = render "events/event/push", event: event
+    - elsif event.commented?
+      = render "events/event/note", event: event
+    - else
+      = render "events/event/common", event: event
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 44fff49d99c20133b188601eacf6d1e51cb5e8f1..64ca3c32e0109ec63ba9cdf3f93193f01a685e98 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -18,7 +18,7 @@
       - few_commits.each do |commit|
         = render "events/commit", commit: commit, project: project, event: event
 
-      - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project)
+      - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user)
       - if event.commits_count > 1
         %li.commits-stat
           - if event.commits_count > 2
@@ -35,12 +35,12 @@
             Compare #{from_label}...#{truncate_sha(event.commit_to)}
 
           - if create_mr
-            %span{"data-user-is" => event.author_id, "data-display" => "inline"}
+            %span
               or
               = link_to create_mr_path(project.default_branch, event.ref_name, project) do
                 create a merge request
       - elsif create_mr
-        %li.commits-stat{"data-user-is" => event.author_id}
+        %li.commits-stat
           = link_to create_mr_path(project.default_branch, event.ref_name, project) do
             Create Merge Request
 - elsif event.rm_ref?
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index a1b39d9e1a0cf2e973c88a5112437479a184d6f5..4e5d965ccbe3244aa6c050aa7a0e8852f84295f2 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -17,13 +17,13 @@
 
   .pull-right
     .dropdown.inline
-      %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+      %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'}
         %span.light
         - if @sort.present?
           = sort_options_hash[@sort]
         - else
           = sort_title_recently_created
-        = icon('caret-down')
+        = icon('chevron-down')
       %ul.dropdown-menu.dropdown-menu-align-right
         %li
           = link_to explore_groups_path(sort: sort_value_recently_created) do
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 4cff14b096b36243346cb840364de8f6fca7c946..5ea154c36b46f07868553e4cc6ca9613f9ce6cb9 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,13 +1,13 @@
 - if current_user
   .dropdown
-    %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+    %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
       = icon('globe')
       %span.light Visibility:
       - if params[:visibility_level].present?
         = visibility_level_label(params[:visibility_level].to_i)
       - else
         Any
-      = icon('caret-down')
+      = icon('chevron-down')
     %ul.dropdown-menu.dropdown-menu-align-right
       %li
         = link_to filter_projects_path(visibility_level: nil) do
@@ -20,14 +20,14 @@
 
 - if @tags.present?
   .dropdown
-    %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+    %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
       = icon('tags')
       %span.light Tags:
       - if params[:tag].present?
         = params[:tag]
       - else
         Any
-      = icon('caret-down')
+      = icon('chevron-down')
     %ul.dropdown-menu.dropdown-menu-align-right
       %li
         = link_to filter_projects_path(tag: nil) do
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 2fd4859c1c628e50158caa477d424f21c1b993df..882fdf1317de821771ed861b15899c4fa437b838 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -6,7 +6,7 @@
   - if inviter = @member.created_by
     by
     = link_to inviter.name, user_url(inviter)
-  to join 
+  to join
   - case @member.source
   - when Project
     - project = @member.source
@@ -20,11 +20,18 @@
       = link_to group.name, group_url(group)
   as #{@member.human_access}.
 
-- if @member.source.users.include?(current_user)
+- is_member = @member.source.users.include?(current_user)
+
+- if is_member
   %p
     However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}.
     Sign in using a different account to accept the invitation.
-- else
+
+- if @member.invite_email != current_user.email
+  %p
+    Note that this invitation was sent to #{mail_to @member.invite_email}, but you are signed in as #{link_to current_user.to_reference, user_url(current_user)} with email #{mail_to current_user.email}.
+
+- unless is_member
   .actions
     = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success"
     = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10"
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 757de92d6d409d9bccfbbd3b27279e94f8a60611..3e488cf73b97a65c97c4b675f4138f603b16376b 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -56,5 +56,3 @@
   = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
   = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
   = render 'layouts/bootlint' if Rails.env.development?
-
-  = render 'layouts/user_styles'
diff --git a/app/views/layouts/_user_styles.html.haml b/app/views/layouts/_user_styles.html.haml
deleted file mode 100644
index b76b3cb5510f7f6453961eaefe52847c15231270..0000000000000000000000000000000000000000
--- a/app/views/layouts/_user_styles.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-:css
-  [data-user-is] {
-    display: none !important;
-  }
-
-  [data-user-is="#{current_user.try(:id)}"] {
-    display: block !important;
-  }
-
-  [data-user-is="#{current_user.try(:id)}"][data-display="inline"] {
-    display: inline !important;
-  }
-
-  [data-user-is-not] {
-    display: block !important;
-  }
-
-  [data-user-is-not][data-display="inline"] {
-    display: inline !important;
-  }
-
-  [data-user-is-not="#{current_user.try(:id)}"] {
-    display: none !important;
-  }
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 7fbe065df00cf0900501571bc9b3fa5fde4016d2..a3b925f6afd6ebf8914c7d3e50e2247583b3258f 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,10 +1,59 @@
 !!! 5
 %html{ lang: "en"}
-  = render "layouts/head"
-  %body{class: "#{user_application_theme} application navless"}
-    = Gon::Base.render_data
-    = render "layouts/header/empty"
-    .container.navless-container
-      = render "layouts/flash"
-      .error-page
-        = yield
+  %head
+    %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"}
+    %title= yield(:title)
+    :css
+      body {
+        color: #666;
+        text-align: center;
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+        margin: auto;
+        font-size: 14px;
+      }
+
+      h1 {
+        font-size: 56px;
+        line-height: 100px;
+        font-weight: normal;
+        color: #456;
+      }
+
+      h2 {
+        font-size: 24px;
+        color: #666;
+        line-height: 1.5em;
+      }
+
+      h3 {
+        color: #456;
+        font-size: 20px;
+        font-weight: normal;
+        line-height: 28px;
+      }
+
+      hr {
+        max-width: 800px;
+        margin: 18px auto;
+        border: 0;
+        border-top: 1px solid #EEE;
+        border-bottom: 1px solid white;
+      }
+
+      img {
+        max-width: 40vw;
+        display: block;
+        margin: 40px auto;
+      }
+
+      .container {
+        margin: auto 20px;
+      }
+
+      ul {
+      margin: auto;
+      text-align: left;
+      display:inline-block;
+      }
+%body
+  = yield
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 2a6d9cda3799e1ba0d4547c3e0e9274ed438c34e..817e4bebb058c96abc7b78feaf4e94f427d9145f 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,5 +1,4 @@
 .nav-sidebar
-  .sidebar-header Across GitLab
   %ul.nav
     = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
       = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 99a58bbb676501cd92b6656723ae8ec6da8aaabd..701bcd3ab71391eced00f72bfeafda5ab2bfd358 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -70,7 +70,7 @@
           %span
             Issues
             - if @project.default_issues_tracker?
-              %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
+              %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
 
     - if project_nav_tab? :merge_requests
       = nav_link(controller: :merge_requests) do
diff --git a/app/views/notify/_note_message.text.erb b/app/views/notify/_note_message.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f82cbc9a3fceff6df527aefb4e826abd098599c6
--- /dev/null
+++ b/app/views/notify/_note_message.text.erb
@@ -0,0 +1,5 @@
+<% if current_application_settings.email_author_in_body %>
+  <%= @note.author_name %> wrote:
+<% end -%>
+
+<%= @note.note %>
diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..edf8dfe7e9e88e29d42e4a087ac408d32b3246e8
--- /dev/null
+++ b/app/views/notify/_note_mr_or_commit_email.html.haml
@@ -0,0 +1,18 @@
+= content_for :head do
+  = stylesheet_link_tag 'mailers/highlighted_diff_email'
+
+New comment
+
+- if @discussion && @discussion.diff_file
+  on
+  = link_to @note.diff_file.file_path, @target_url, class: 'details'
+  \:
+  %table
+    = render partial: "projects/diffs/line",
+      collection: @discussion.truncated_diff_lines,
+      as: :line,
+      locals: { diff_file: @note.diff_file,
+        plain: true,
+        email: true }
+
+= render 'note_message'
diff --git a/app/views/notify/_note_mr_or_commit_email.text.erb b/app/views/notify/_note_mr_or_commit_email.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b4fcdf6b1e932db52d86a502959fea0fff5d33a3
--- /dev/null
+++ b/app/views/notify/_note_mr_or_commit_email.text.erb
@@ -0,0 +1,8 @@
+<% if @discussion && @discussion.diff_file -%>
+ on <%= @note.diff_file.file_path -%>
+<% end -%>:
+
+<%= url %>
+
+<%= render 'simple_diff' if @discussion -%>
+<%= render 'note_message' %>
diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c28d1cc34d3e394b7f86039f71fd3da885a4911e
--- /dev/null
+++ b/app/views/notify/_simple_diff.text.erb
@@ -0,0 +1,3 @@
+<% @discussion.truncated_diff_lines(highlight: false).each do |line| %>
+> <%= line.text %>
+<% end %>
diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml
index 1d961e4424c6b5238902774678a6009d6d94b72f..0a650e3b2ca49db6244de3d47324f6ee330b34a5 100644
--- a/app/views/notify/note_commit_email.html.haml
+++ b/app/views/notify/note_commit_email.html.haml
@@ -1,2 +1,2 @@
-= render 'note_message'
-
+%p.details
+  = render 'note_mr_or_commit_email'
diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb
index aaeaf5fdf731c498ea6a46f570b9e7e9c5e924f7..6aa085a172e4e0479f802f0046d1e1ce8d7397c1 100644
--- a/app/views/notify/note_commit_email.text.erb
+++ b/app/views/notify/note_commit_email.text.erb
@@ -1,9 +1,2 @@
-New comment for Commit <%= @commit.short_id %>
-
-<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
-
-
-Author: <%= @note.author_name %>
-
-<%= @note.note %>
-
+New comment for Commit <%= @commit.short_id -%>
+<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url } %>
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index ea7e3d199fd9c9ebf050aed49bc0365e2fe4a5c6..0a650e3b2ca49db6244de3d47324f6ee330b34a5 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,7 +1,2 @@
-- if @note.diff_note? && @note.diff_file
-  %p.details
-    New comment on diff for
-    = link_to @note.diff_file.file_path, @target_url
-    \:
-
-= render 'note_message'
+%p.details
+  = render 'note_mr_or_commit_email'
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
index 8cdab63829e28565522ec0180e51b5803531517c..2ce64c494cf608ce4b93e7d82d9594433d64b80b 100644
--- a/app/views/notify/note_merge_request_email.text.erb
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -1,9 +1,2 @@
-New comment for Merge Request <%= @merge_request.to_reference %>
-
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
-
-
-<%= @note.author_name %>
-
-<%= @note.note %>
-
+New comment for Merge Request <%= @merge_request.to_reference -%>
+<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url }%>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 307c5a11206b7038b94a83951637c7965104626f..25883de257c0ac9da0537b7a7cffdd72156fa572 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,5 +1,5 @@
 = content_for :head do
-  = stylesheet_link_tag 'mailers/repository_push_email'
+  = stylesheet_link_tag 'mailers/highlighted_diff_email'
 
 %h3
   #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml
index de1337a2a24e093ff7a4a26244c0c8d7c28a53f7..5307e0b48cb8eb5cbbc8c558d2e13d9047688293 100644
--- a/app/views/profiles/update_username.js.haml
+++ b/app/views/profiles/update_username.js.haml
@@ -2,5 +2,6 @@
   :plain
     new Flash("Username successfully changed", "notice")
 - else
+  - error = @user.errors.full_messages.first
   :plain
-    new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
+    new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert")
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index 369a847e7d4974b92daafcf90ed0510f1c4f2645..b6fb08b68e92b21a81f7617fe2ab93453d1572fc 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -18,5 +18,5 @@
         distributed with computer software, forming part of its documentation.
       %p
         We recommend you to
-        = link_to "add a README", new_readme_path, class: 'underlined-link'
+        = link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link'
         file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 2a0352a71b79a19a3acfa49292061756b13e1db5..a5dcd93f42e2049c0155cb90a20e29f4281030d4 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -27,5 +27,5 @@
       = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
       = hidden_field_tag 'last_commit_sha', @last_commit_sha
       = hidden_field_tag 'content', '', id: "file-content"
-      = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
+      = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
       = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml
index 34effac17b2d54588022ed92f0c4d02c153cb917..1f31496e73fd7bba9bccbea3583c04b8bcac3870 100644
--- a/app/views/projects/boards/components/_card.html.haml
+++ b/app/views/projects/boards/components/_card.html.haml
@@ -1,5 +1,6 @@
 %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
   ":index" => "index",
+  ":data-issue-id" => "issue.id",
   "@mousedown" => "mouseDown",
   "@mousemove" => "mouseMove",
   "@mouseup" => "showIssue($event)" }
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml
index 21c9563e9db9f9f5416df01820cb1df9c998f676..a08c7f2af096a3f94f96597174e23d2ca1b895bf 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml
@@ -1,11 +1,7 @@
 - if current_user
   .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
-    .title
+    %span.issuable-header-text.hide-collapsed.pull-left
       Notifications
-    %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
-      {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }}
-    .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" }
-      .unsubscribed{ "v-show" => "!issue.subscribed" }
-        You're not receiving notifications from this thread.
-      .subscribed{ "v-show" => "issue.subscribed" }
-        You're receiving notifications because you're subscribed to this thread.
+    %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
+      %span
+        {{issue.subscribed ? 'Unsubscribe' : 'Subscribe'}}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 2246316b54046afe042e69be279958c7ecd470ac..5fd664c7a93c2d029c136cb2742803e521f31b86 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -12,10 +12,10 @@
         = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
 
       .dropdown.inline
-        %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+        %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'}
           %span.light
             = projects_sort_options_hash[@sort]
-          = icon('caret-down')
+          = icon('chevron-down')
         %ul.dropdown-menu.dropdown-menu-align-right
           %li
             = link_to filter_branches_path(sort: sort_value_name) do
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index f55620469538a5b89ddcb271ee4ee726fa8a7a2a..d5004f6a066a8bcbda75bc9fe8fb031aa1cf1be9 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -116,7 +116,7 @@
         .title Stage
         %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
           %span.stage-selection More
-          = icon('caret-down')
+          = icon('chevron-down')
         %ul.dropdown-menu
           - @build.pipeline.stages.each do |stage|
             %li
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index d8cbfd7173a4774e912c41e2b267c166e0138261..108674dbba66bb872dca41fbc8c9164086b2e828 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -40,13 +40,12 @@
               This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
             - else
               This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
-              - if environment.last_deployment
-                View the most recent deployment #{deployment_link(environment.last_deployment)}.
+              View the most recent deployment #{deployment_link(environment.last_deployment)}.
           - elsif @build.complete? && !@build.success?
             The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed.
           - else
             This build is creating a deployment to #{environment_link_for_build(@build.project, @build)}
-            - if environment.last_deployment
+            - if environment.try(:last_deployment)
               and will overwrite the
               = link_to 'latest deployment', deployment_link(environment.last_deployment)
 
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 8d9c15d0dc6a1622a07fc6cea15ff3dd2a732d57..e75547c815f06a8fa71fda4087b3e17e2aee7dfc 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -66,8 +66,6 @@
     %td
       - if build.project
         = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project)
-
-  - if admin
     %td
       - if build.try(:runner)
         = runner_link(build.runner)
@@ -93,9 +91,8 @@
         %span #{time_ago_with_tooltip(build.finished_at)}
 
   %td.coverage
-    - if coverage
-      - if build.try(:coverage)
-        #{build.coverage}%
+    - if coverage && build.try(:coverage)
+      #{build.coverage}%
 
   %td
     .pull-right
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 120ba9ffcd230b2a39e881512f3ea13929cd29f6..6c33d80becd972938214e5d848a5cefb2018b938 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -9,7 +9,7 @@
             = icon('comment')
           \
           - if editable_diff?(diff_file)
-            - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {}
+            - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {}
             = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
                              blob: blob, link_opts: link_opts)
 
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index a3e4b5b777e5d87ecd62c071c5ebaaf95b67d51c..16c96b667141af9be80c633fe0ed5d0af3c9231e 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -25,7 +25,7 @@
         %a{href: "##{line_code}", data: { linenumber: link_text }}
     %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }<
       - if email
-        %pre= diff_line_content(line.text)
+        %pre= line.text
       - else
         = diff_line_content(line.text)
 
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 7a39064adc5a1d97439e32793f002c331559b69e..c0a83091c8ca618ebcf27e08771809e8803589cb 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -16,7 +16,7 @@
     %p
       Otherwise you can start with adding a
       = succeed ',' do
-        = link_to "README", new_readme_path, class: 'underlined-link'
+        = link_to "README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link'
       a
       = succeed ',' do
         = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index abf4f697f867732b3801af701c519f9cd9ac28ae..5ee3979c7e76e29753de557ab79f2f76e4415155 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -9,13 +9,13 @@
         spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
 
     .dropdown
-      %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
+      %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'}
         %span.light sort:
         - if @sort.present?
           = sort_options_hash[@sort]
         - else
           = sort_title_recently_created
-        = icon('caret-down')
+        = icon('chevron-down')
       %ul.dropdown-menu.dropdown-menu-align-right
         %li
           - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 0b99e9f8756789325a1d4bebb442ed952878f9ff..7f751d9ae2e87c032cee2abdb2e2d928098b541b 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -1,4 +1,12 @@
-%tr.generic_commit_status
+- admin = local_assigns.fetch(:admin, false)
+- ref = local_assigns.fetch(:ref, nil)
+- commit_sha = local_assigns.fetch(:commit_sha, nil)
+- retried = local_assigns.fetch(:retried, false)
+- pipeline_link = local_assigns.fetch(:pipeline_link, false)
+- stage = local_assigns.fetch(:stage, false)
+- coverage = local_assigns.fetch(:coverage, false)
+
+%tr.generic_commit_status{class: ('retried' if retried)}
   %td.status
     - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
       = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url)
@@ -8,14 +16,35 @@
   %td.generic_commit_status-link
     - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
       = link_to generic_commit_status.target_url do
-        %strong ##{generic_commit_status.id}
+        %span.build-link ##{generic_commit_status.id}
     - else
-      %strong ##{generic_commit_status.id}
+      %span.build-link ##{generic_commit_status.id}
 
-    - if defined?(retried) && retried
+    - if ref
+      - if generic_commit_status.ref
+        .icon-container
+          = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
+        = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+      - else
+        .light none
+      .icon-container.commit-icon
+        = custom_icon("icon_commit")
+
+    - if commit_sha
+      = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-id monospace"
+
+    - if retried
       = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
 
-  - if defined?(pipeline_link) && pipeline_link
+    .label-container
+      - if generic_commit_status.tags.any?
+        - generic_commit_status.tags.each do |tag|
+          %span.label.label-primary
+            = tag
+      - if retried
+        %span.label.label-warning retried
+
+  - if pipeline_link
     %td
       = link_to pipeline_path(generic_commit_status.pipeline) do
         %span.pipeline-id ##{generic_commit_status.pipeline.id}
@@ -25,25 +54,17 @@
       - else
         %span.monospace API
 
-  - if defined?(commit_sha) && commit_sha
-    %td
-      = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
-
-  - if defined?(ref) && ref
+  - if admin
     %td
-      - if generic_commit_status.ref
-        = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
-      - else
-        .light none
-
-  - if defined?(runner) && runner
+      - if generic_commit_status.project
+        = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project)
     %td
       - if generic_commit_status.try(:runner)
         = runner_link(generic_commit_status.runner)
       - else
         .light none
 
-  - if defined?(stage) && stage
+  - if stage
     %td
       = generic_commit_status.stage
 
@@ -51,24 +72,19 @@
     = generic_commit_status.name
 
   %td
-    - if generic_commit_status.tags.any?
-      - generic_commit_status.tags.each do |tag|
-        %span.label.label-primary
-          = tag
-    - if defined?(retried) && retried
-      %span.label.label-warning retried
-
-  %td.duration
     - if generic_commit_status.duration
-      = icon("clock-o")
-      = time_interval_in_words(generic_commit_status.duration)
+      %p.duration
+        = custom_icon("icon_timer")
+        = duration_in_numbers(generic_commit_status.duration)
 
-  %td.timestamp
     - if generic_commit_status.finished_at
-      = icon("calendar")
-      %span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
+      %p.finished-at
+        = icon("calendar")
+        %span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
 
-  - if defined?(coverage) && coverage
-    %td.coverage
-      - if generic_commit_status.try(:coverage)
-        #{generic_commit_status.coverage}%
+  %td.coverage
+    - if coverage && generic_commit_status.try(:coverage)
+      #{generic_commit_status.coverage}%
+
+  %td
+    -# empty column to match number of columns in ci/builds/_build.html.haml
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 747bfa554cba540f684ff6885dc569678ee90eff..d48923b422a3e12b0f7f04e461c75c9efa5bf20c 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -2,12 +2,12 @@
   %h2.merge-requests-title
     = pluralize(@merge_requests.count, 'Related Merge Request')
   %ul.unstyled-list.related-merge-requests
-    - has_any_ci = @merge_requests.any?(&:pipeline)
+    - has_any_ci = @merge_requests.any?(&:head_pipeline)
     - @merge_requests.each do |merge_request|
       %li
         %span.merge-request-ci-status
-          - if merge_request.pipeline
-            = render_pipeline_status(merge_request.pipeline)
+          - if merge_request.head_pipeline
+            = render_pipeline_status(merge_request.head_pipeline)
           - elsif has_any_ci
             = icon('blank fw')
         %span.merge-request-id
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 9ffcc48eb803a82283016ed0575ff2acfb7ad56e..fa189ae62d824073952a3d88b704994e99ac14ff 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -15,9 +15,9 @@
           = icon('ban')
           CLOSED
 
-      - if merge_request.pipeline
+      - if merge_request.head_pipeline
         %li
-          = render_pipeline_status(merge_request.pipeline)
+          = render_pipeline_status(merge_request.head_pipeline)
 
       - if merge_request.open? && merge_request.broken?
         %li
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 9c6f562f7db2f65a70503251bd97dd10cbe5be38..4a08ed045f4faaf87995646efc7b27d543b8e9f3 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -34,10 +34,11 @@
           = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
             Pipelines
             %span.badge= @pipelines.size
+      - if @pipeline.present?
         %li.builds-tab
           = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
             Builds
-            %span.badge= @statuses.size
+            %span.badge= @statuses_count
       %li.diffs-tab
         = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
           Changes
@@ -48,9 +49,10 @@
         = render "projects/merge_requests/show/commits"
       #diffs.diffs.tab-pane
         - # This tab is always loaded via AJAX
-      - if @pipelines.any?
+      - if @pipeline.present?
         #builds.builds.tab-pane
           = render "projects/merge_requests/show/builds"
+      - if @pipelines.any?
         #pipelines.pipelines.tab-pane
           = render "projects/merge_requests/show/pipelines"
 
@@ -65,5 +67,5 @@
 :javascript
   var merge_request = new MergeRequest({
     action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
-    buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}"
+    buildsLoaded: "#{@pipeline.present? ? 'true' : 'false'}"
   });
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index a497f418c7cf9cefa10345d6d08cb87de973686d..0e2975bd55116e579973f29b1250615cbe414818 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -59,15 +59,16 @@
                 = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
                   Commits
                   %span.badge= @commits_count
-            - if @pipeline
+            - if @pipelines.any?
               %li.pipelines-tab
                 = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
                   Pipelines
                   %span.badge= @pipelines.size
+            - if @pipeline.present?
               %li.builds-tab
                 = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
                   Builds
-                  %span.badge= @statuses.size
+                  %span.badge= @statuses_count
             %li.diffs-tab
               = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
                 Changes
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index eab48b78cb3d7e9db20248f1f4c45f28cc358f5f..5cc92595fe06285360f594359be9e6c8b5872425 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -27,7 +27,7 @@
                         version #{version_index(merge_request_diff)}
                     .monospace #{short_sha(merge_request_diff.head_commit_sha)}
                     %small
-                      #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
+                      #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
                       = time_ago_with_tooltip(merge_request_diff.created_at)
 
       - if @merge_request_diff.base_commit_sha
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 18c72ed875ca70dd3b3cd20e38ac4a912e89e985..6d9b91ad0e77c2b41ac097b8ba4f1f222d31a485 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,7 +1,7 @@
 - if @pipeline
   .mr-widget-heading
     - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
-      .ci_widget{ class: "ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
+      .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
         = ci_icon_for_status(status)
         %span
           Pipeline
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 20c93930abc32f2ef040a280d3e7f5c3035714d9..eee711dc5afc73041ecdc528eaf2629942c0f91c 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -11,7 +11,7 @@
       = render 'projects/merge_requests/widget/open/archived'
     - elsif @merge_request.branch_missing?
       = render 'projects/merge_requests/widget/open/missing_branch'
-    - elsif @merge_request.commits.blank?
+    - elsif @merge_request.has_no_commits?
       = render 'projects/merge_requests/widget/open/nothing'
     - elsif @merge_request.unchecked?
       = render 'projects/merge_requests/widget/open/check'
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 608fdf1c5f5eb3d60c1a12291e6ca7f96e03b49e..a8918c85dde36f4957a59fbc1b11a857a29cd664 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -14,7 +14,7 @@
     ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
     ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
     gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
-    ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}",
+    ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
     ci_message: {
       normal: "Build {{status}} for \"{{title}}\"",
       preparing: "{{status}} build for \"{{title}}\""
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 718314701f92a39d8e34e8a082ef44d5fea20c9e..3464e155a1bc2b8c5217cf32242a670e3af5f730 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -1,14 +1,17 @@
 .tabs-holder
-  %ul.nav-links.no-top.no-bottom
-    %li.active
-      = link_to "Pipeline", "#js-tab-pipeline", data: { target: '#js-tab-pipeline', action: 'pipeline', toggle: 'tab' }, class: 'pipeline-tab'
-    %li
-      = link_to "#js-tab-builds", data: { target: '#js-tab-builds', action: 'build', toggle: 'tab' }, class: 'builds-tab' do
+  %ul.pipelines-tabs.nav-links.no-top.no-bottom
+    %li.js-pipeline-tab-link
+      = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' },  class: 'pipeline-tab' do
+        Pipeline
+    %li.js-builds-tab-link
+      = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
         Builds
-        %span.badge= pipeline.statuses.count
+        %span.badge.js-builds-counter= pipeline.statuses.count
+
+
 
 .tab-content
-  #js-tab-pipeline.tab-pane.active
+  #js-tab-pipeline.tab-pane
     .build-content.middle-block.pipeline-graph
       .pipeline-visualization
         %ul.stage-column-list
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 8c6652a5f90f642f635d7db5c3f1ae0d1b5596af..29a41bc664bc7f792f426f997e4111058fa120cf 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -2,7 +2,7 @@
 - page_title "Pipeline"
 = render "projects/pipelines/head"
 
-%div{ class: container_class }
+%div.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
   - if @commit
     = render "projects/pipelines/info"
 
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml
index 7b7fa56d993f8c235701918b0625c19b195931af..22a3b884520767d533e49150717ec25edba811cd 100644
--- a/app/views/projects/pipelines_settings/_badge.html.haml
+++ b/app/views/projects/pipelines_settings/_badge.html.haml
@@ -1,4 +1,4 @@
-.row{ class: badge.title.gsub(' ', '-') }
+%div{ class: badge.title.gsub(' ', '-') }
   .col-lg-3.profile-settings-sidebar
     %h4.prepend-top-0
       = badge.title.capitalize
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 7a0d9dcc94f5a59f989a83a54dd7d7c64896cd30..1d39f3a75345c2290c5749ad433164a5d5ddabff 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -10,15 +10,16 @@
     .nav-controls
       = form_tag(filter_tags_path, method: :get) do
         = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
+
       .dropdown.inline
-        %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
+        %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} }
           %span.light
-            = @sort.humanize
-          = icon('caret-down')
+            = projects_sort_options_hash[@sort]
+          = icon('chevron-down')
         %ul.dropdown-menu.dropdown-menu-align-right
           %li
-            = link_to filter_tags_path(sort: nil) do
-              Name
+            = link_to filter_tags_path(sort: sort_value_name) do
+              = sort_title_name
             = link_to filter_tags_path(sort: sort_value_recently_updated) do
               = sort_title_recently_updated
             = link_to filter_tags_path(sort: sort_value_oldest_updated) do
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
index 07cee86ba4ce0c7bbc1eb10b328495ef476d3d91..c7cebf45160fc2fbd80c9a5aa3fe94b2b139b00c 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/projects/variables/_table.html.haml
@@ -12,8 +12,8 @@
       - @project.variables.order_key_asc.each do |variable|
         - if variable.id?
           %tr
-            %td= variable.key
-            %td= variable.value
+            %td.variable-key= variable.key
+            %td.variable-value{ "data-value" => variable.value }******
             %td
               = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
                 %span.sr-only
diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml
index 09bb54600afff97acd21f8e60e4df1c4ada15d76..3930370013184f87ea270ab39b8d6187cec52f18 100644
--- a/app/views/projects/variables/index.html.haml
+++ b/app/views/projects/variables/index.html.haml
@@ -15,3 +15,4 @@
         No variables found, add one with the form above.
     - else
       = render "table"
+      %button.btn.btn-info.js-btn-toggle-reveal-values{"data-status" => 'hidden'} Reveal Values
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 4e41a15d9f4e8cd96d2673e0766ce03454ae9713..c52527332bc543b72a5836c4a83f61d689f10a8e 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -34,9 +34,6 @@
     - if @page && @page.persisted?
       = f.submit 'Save changes', class: "btn-save btn"
       .pull-right
-        - if can?(current_user, :admin_wiki, @project)
-          = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do
-            Delete
         = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
     - else
       = f.submit 'Create page', class: "btn-create btn"
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
deleted file mode 100644
index afdef70e1cf05ffdffa0fc0c1ec32293be5a119e..0000000000000000000000000000000000000000
--- a/app/views/projects/wikis/_nav.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= content_for :sub_nav do
-  .scrolling-tabs-container.sub-nav-scroll
-    = render 'shared/nav_scroll'
-    .nav-links.sub-nav.scrolling-tabs
-      %ul{ class: (container_class) }
-        = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
-          = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
-
-        = nav_link(path: 'wikis#pages') do
-          = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project)
-
-        = nav_link(path: 'wikis#git_access') do
-          = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
-            Git Access
-
-      = render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..cad9c15a49eff91e76c868f78dbd2508c8a3a361
--- /dev/null
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -0,0 +1,23 @@
+%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar
+  .block.wiki-sidebar-header.append-bottom-default
+    %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
+      = icon('angle-double-right')
+
+    - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project)
+    = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
+      = succeed '&nbsp;' do
+        = icon('cloud-download')
+      Clone repository
+
+  .blocks-container
+    .block.block-first
+      %ul.wiki-pages
+        - @sidebar_wiki_pages.each do |wiki_page|
+          %li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
+            = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+              = wiki_page.title.capitalize
+    .block
+      = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
+        More Pages
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 679d6018befedc3f46a0aacd24d5ee40d7cfe8a0..8cf018da1b75bd8270fdcf0c303625a0570311e6 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,23 +1,35 @@
 - @no_container = true
 - page_title "Edit", @page.title.capitalize, "Wiki"
-= render 'nav'
 
 %div{ class: container_class }
-  .top-area
+  .wiki-page-header.has-sidebar-toggle
+    %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+      = icon('angle-double-left')
+
     .nav-text
-      %strong
+      %h2.wiki-page-title
         - if @page.persisted?
           = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
         - else
           = @page.title.capitalize
-      %span.light
-        &middot;
-        Edit Page
+        %span.light
+          &middot;
+          - if @page.persisted?
+            Edit Page
+          - else
+            Create Page
 
     .nav-controls
-      - if !(@page && @page.persisted?)
-        - if can?(current_user, :create_wiki, @project)
-          = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
-            New Page
+      - if can?(current_user, :create_wiki, @project)
+        = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+          New Page
+      - if @page.persisted?
+        = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+          Page History
+        - if can?(current_user, :admin_wiki, @project)
+          = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
+            Delete
 
   = render 'form'
+
+= render 'sidebar'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index b8811a28dd66cce0c58e3d57a6be4423415e617d..e25d6a48573959760ff68cad2a3baed593f712ee 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,34 +1,36 @@
 - @no_container = true
 - page_title "Git Access", "Wiki"
 
-= render 'nav'
 %div{ class: container_class }
-  .sub-header-block
-    %span.oneline
-      Git access for
+  .wiki-page-header.has-sidebar-toggle
+    %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+      = icon('angle-double-left')
+
+    .git-access-header
+      Clone repository
       %strong= @project_wiki.path_with_namespace
 
-    .pull-right
-      = render "shared/clone_panel", project: @project_wiki
+    = render "shared/clone_panel", project: @project_wiki
+
+  .wiki-git-access
+    %h3 Install Gollum
+    %pre.dark
+      :preserve
+        gem install gollum
 
-  .prepend-top-default
-    %fieldset
-      %legend Install Gollum:
-      %pre.dark
-        :preserve
-          gem install gollum
+    %h3 Clone your wiki
+    %pre.dark
+      :preserve
+        git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
+        cd #{h @project_wiki.path}
 
-      %legend Clone Your Wiki:
-      %pre.dark
-        :preserve
-          git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
-          cd #{h @project_wiki.path}
+    %h3 Start Gollum and edit locally
+    %pre.dark
+      :preserve
+        gollum
+        == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
+        >> Thin web server (v1.5.0 codename Knife)
+        >> Maximum connections set to 1024
+        >> Listening on 0.0.0.0:4567, CTRL+C to stop
 
-      %legend Start Gollum And Edit Locally:
-      %pre.dark
-        :preserve
-          gollum
-          == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
-          >> Thin web server (v1.5.0 codename Knife)
-          >> Maximum connections set to 1024
-          >> Listening on 0.0.0.0:4567, CTRL+C to stop
+= render 'sidebar'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 4c0b14e2c420e4bda836fc70760051077c6cf62a..dd7213622c19d3739fb46963acc9f7c1598d381d 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,13 +1,16 @@
 - page_title "History", @page.title.capitalize, "Wiki"
-= render 'nav'
+
 %div{ class: container_class }
-  .top-area
+  .wiki-page-header.has-sidebar-toggle
+    %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+      = icon('angle-double-left')
+
     .nav-text
-      %strong
+      %h2.wiki-page-title
         = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
-      %span.light
-        &middot;
-        History
+        %span.light
+          &middot;
+          History
 
   .table-holder
     %table.table
@@ -35,3 +38,5 @@
             %td
               %strong
                 = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+
+= render 'sidebar'
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 9c10acd4cb6b6373ab937cd01f4277524f61a05e..e1eaffc688454390ac2e04bd7758f815acae44a8 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,9 +1,18 @@
 - @no_container = true
 - page_title "Pages", "Wiki"
 
-= render 'nav'
-
 %div{ class: container_class }
+  .wiki-page-header
+
+    .nav-text
+      %h2.wiki-page-title
+        Wiki Pages
+
+    .nav-controls
+      = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do
+        = icon('cloud-download')
+        Clone repository
+
   %ul.content-list
     - @wiki_pages.each do |wiki_page|
       %li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 5cebb538cf563d51cf2fc97779a4eda0bc94b4f5..1b6dceee2413ea31807fa7a08fc97c68e5c54875 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,15 +1,19 @@
 - @no_container = true
 - page_title @page.title.capitalize, "Wiki"
-= render 'nav'
 
 %div{ class: container_class }
-  .top-area
+  .wiki-page-header.has-sidebar-toggle
+    %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
+      = icon('angle-double-left')
+
     .nav-text
-      %strong= @page.title.capitalize
+      %h2.wiki-page-title= @page.title.capitalize
 
       %span.wiki-last-edit-by
-        &middot;
-        last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+        Last edited by
+        %strong
+          #{@page.commit.author.name}
+        #{time_ago_with_tooltip(@page.commit.authored_date)}
 
     .nav-controls
       = render 'main_links'
@@ -19,8 +23,9 @@
       This is an old version of this page.
       You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
 
-
   .wiki-holder.prepend-top-default.append-bottom-default
     .wiki
       = preserve do
         = render_wiki_content(@page)
+
+= render 'sidebar'
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index ef1c0296d49fee373826f3dbfef1f9ae1070d740..938be20c7cf1da631ead533757e3ccbd953d347d 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -3,7 +3,7 @@
 - if params[:project_id].present?
   = hidden_field_tag :project_id, params[:project_id]
 .dropdown
-  %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } }
+  %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } }
     %span.dropdown-toggle-text
       Group:
       - if @group.present?
@@ -18,7 +18,7 @@
     = dropdown_loading
 
 .dropdown.project-filter
-  %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+  %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
     %span.dropdown-toggle-text
       Project:
       - if @project.present?
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index ba25e09d638abd54bf1cc2ddced24148d9ad852d..0bc851b4256b082ca94fa43c27b848bc263fab26 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -13,8 +13,9 @@
       .input-group-addon
         = root_url
       = f.text_field :path, placeholder: 'open-source', class: 'form-control',
-        autofocus: local_assigns[:autofocus] || false,  pattern: "[a-zA-Z0-9-_]+",
-        required: true, title: 'Please choose a group name with no special characters.'
+        autofocus: local_assigns[:autofocus] || false, required: true,
+        pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE,
+        title: 'Please choose a group name with no special characters.'
 
     - if @group.persisted?
       .alert.alert-warning.prepend-top-10
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index baa6d5f8206ed93c95b53123749adbc844f82e42..26b349e8a624af90dfa7ef4f835b4951dd170a88 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,4 +1,4 @@
-- if @issues.reorder(nil).any?
+- if @issues.to_a.any?
   - @issues.group_by(&:project).each do |group|
     .panel.panel-default.panel-small
       - project = group[0]
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index ca3178395c1508f3a139ae04c9e8a50cc936aac5..2f3605b4d27547fdf103cdc11df6158d92b30d43 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,4 +1,4 @@
-- if @merge_requests.reorder(nil).any?
+- if @merge_requests.to_a.any?
   - @merge_requests.group_by(&:target_project).each do |group|
     .panel.panel-default.panel-small
       - project = group[0]
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 68e05cb72e17ecfc69d106bc55d0b07c8a829388..ede3c7090d75c5846f3c9eb37507c8ffbe091115 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,11 +1,11 @@
 .dropdown.inline.prepend-left-10
-  %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+  %button.dropdown-toggle{type: 'button', data: {toggle: 'dropdown'}}
     %span.light
     - if @sort.present?
       = sort_options_hash[@sort]
     - else
       = sort_title_recently_created
-    = icon('caret-down')
+    = icon('chevron-down')
   %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
     %li
       = link_to page_filter_path(sort: sort_value_priority, label: true) do
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index b7e5e928993c2379afc0c6f454094327d4274e92..e3503981afe220dd3fb559178217553f36d37f5f 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -29,9 +29,9 @@
         .filter-item.inline.labels-filter
           = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
 
-        - if issuable_filters_present
+        - if issuable_filter_present?
           .filter-item.inline.reset-filters
-            %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
+            %a{href: page_filter_path(without: issuable_filter_params)} Reset filters
 
         .pull-right
           - if boards_page
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 9b9ad5104449b2159371cdb1145797b545d699aa..2f05093f435b4d33a9a54b7f04373579a3dac845 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -16,20 +16,9 @@
   = render 'shared/issuable/form/template_selector', issuable: issuable
   = render 'shared/issuable/form/title', issuable: issuable, form: form
 
-.form-group.detail-page-description
-  = form.label :description, 'Description', class: 'control-label'
-  .col-sm-10
+= render 'shared/issuable/form/description', issuable: issuable, form: form
 
-    = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
-      = render 'projects/zen', f: form, attr: :description,
-                               classes: 'note-textarea',
-                               placeholder: "Write a comment or drag your files here...",
-                               supports_slash_commands: !issuable.persisted?
-      = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted?
-      .clearfix
-      .error-alert
-
-- if issuable.is_a?(Issue)
+- if issuable.respond_to?(:confidential)
   .form-group
     .col-sm-offset-2.col-sm-10
       .checkbox
@@ -37,38 +26,7 @@
           = form.check_box :confidential
           This issue is confidential and should only be visible to team members with at least Reporter access.
 
-- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
-  - has_due_date = issuable.has_attribute?(:due_date)
-  %hr
-  .row
-    %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
-      .form-group.issue-assignee
-        = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
-        .col-sm-10{ class: ("col-lg-8" if has_due_date) }
-          .issuable-form-select-holder
-            - if issuable.assignee_id
-              = form.hidden_field :assignee_id
-            = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
-              placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
-      .form-group.issue-milestone
-        = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
-        .col-sm-10{ class: ("col-lg-8" if has_due_date) }
-          .issuable-form-select-holder
-            = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
-      .form-group
-        - has_labels = @labels && @labels.any?
-        = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
-        = form.hidden_field :label_ids, multiple: true, value: ''
-        .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
-          .issuable-form-select-holder
-            = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label"
-    - if has_due_date
-      .col-lg-6
-        .form-group
-          = form.label :due_date, "Due date", class: "control-label"
-          .col-sm-10
-            .issuable-form-select-holder
-              = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
+= render 'shared/issuable/form/metadata', issuable: issuable, form: form
 
 - if issuable.can_move?(current_user)
   %hr
@@ -82,30 +40,7 @@
       title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
         = icon('question-circle')
 
-- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
-  %hr
-  - if @merge_request.new_record?
-    .form-group
-      = form.label :source_branch, class: 'control-label'
-      .col-sm-10
-        .issuable-form-select-holder
-          = form.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
-  .form-group
-    = form.label :target_branch, class: 'control-label'
-    .col-sm-10
-      .issuable-form-select-holder
-        = form.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
-      - if @merge_request.new_record?
-        &nbsp;
-        = link_to 'Change branches', mr_change_branches_path(@merge_request)
-  - if @merge_request.can_remove_source_branch?(current_user)
-    .form-group
-      .col-sm-10.col-sm-offset-2
-        .checkbox
-          = label_tag 'merge_request[force_remove_source_branch]' do
-            = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
-            = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
-            Remove source branch when merge request is accepted.
+= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
 
 - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
 .row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 1d778bc88dec457a043cf6c53e190707f1ee2bd6..22b5a6aa11bebb39a9f273de03656dc93564fea7 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -22,7 +22,7 @@
   %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
     %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) }
       = multi_label_name(selected, "Labels")
-    = icon('caret-down')
+    = icon('chevron-down')
   .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
     = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
     - if show_create && project && can?(current_user, :admin_label, project)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index f166fac105dc08903ed11630783a1d24767ffc4b..958f8413e1d7fcc4e633cd8f39054c9122e334cb 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -9,12 +9,12 @@
       %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } }
         = sidebar_gutter_toggle_icon
       - if current_user
-        %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } }
+        %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } }
           %span.js-issuable-todo-text
             - if todo
-              Mark Done
+              Mark done
             - else
-              Add Todo
+              Add todo
           = icon('spin spinner', class: 'hidden js-issuable-todo-loading')
 
     = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
@@ -144,16 +144,11 @@
         .block.light.subscription{data: {url: toggle_subscription_path(issuable)}}
           .sidebar-collapsed-icon
             = icon('rss')
-          .title.hide-collapsed
+          %span.issuable-header-text.hide-collapsed.pull-left
             Notifications
           - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
-          %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
+          %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
             %span= subscribed ? 'Unsubscribe' : 'Subscribe'
-          .subscription-status.hide-collapsed{data: {status: subscribtion_status}}
-            .unsubscribed{class: ( 'hidden' if subscribed )}
-              You're not receiving notifications from this thread.
-            .subscribed{class: ( 'hidden' unless subscribed )}
-              You're receiving notifications because you're subscribed to this thread.
 
       - project_ref = cross_project_reference(@project, issuable)
       .block.project-reference
@@ -170,6 +165,6 @@
       new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
       new LabelsSelect();
       new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
-      new Subscription('.subscription')
+      gl.Subscription.bindAll('.subscription');
       new gl.DueDateSelectors();
       sidebar = new Sidebar();
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b757893ea049a4821faa8c2c9b947c135802c794
--- /dev/null
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -0,0 +1,30 @@
+- issuable = local_assigns.fetch(:issuable)
+- form = local_assigns.fetch(:form)
+
+- return unless issuable.is_a?(MergeRequest)
+- return if issuable.closed_without_fork?
+
+%hr
+- if issuable.new_record?
+  .form-group
+    = form.label :source_branch, class: 'control-label'
+    .col-sm-10
+      .issuable-form-select-holder
+        = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 span2', disabled: true })
+.form-group
+  = form.label :target_branch, class: 'control-label'
+  .col-sm-10
+    .issuable-form-select-holder
+      = form.select(:target_branch, issuable.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: issuable.new_record?, data: { placeholder: "Select branch" }})
+    - if issuable.new_record?
+      &nbsp;
+      = link_to 'Change branches', mr_change_branches_path(issuable)
+
+- if issuable.can_remove_source_branch?(current_user)
+  .form-group
+    .col-sm-10.col-sm-offset-2
+      .checkbox
+        = label_tag 'merge_request[force_remove_source_branch]' do
+          = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
+          = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
+          Remove source branch when merge request is accepted.
diff --git a/app/views/shared/issuable/form/_description.html.haml b/app/views/shared/issuable/form/_description.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..dbace9ce401def7c371d6759f559a034a02dca7b
--- /dev/null
+++ b/app/views/shared/issuable/form/_description.html.haml
@@ -0,0 +1,15 @@
+- issuable = local_assigns.fetch(:issuable)
+- form = local_assigns.fetch(:form)
+
+.form-group.detail-page-description
+  = form.label :description, 'Description', class: 'control-label'
+  .col-sm-10
+
+    = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
+      = render 'projects/zen', f: form, attr: :description,
+                               classes: 'note-textarea',
+                               placeholder: "Write a comment or drag your files here...",
+                               supports_slash_commands: !issuable.persisted?
+      = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted?
+      .clearfix
+      .error-alert
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a47085230b89bc13efde4e1f949f05214e27f5cd
--- /dev/null
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -0,0 +1,38 @@
+- issuable = local_assigns.fetch(:issuable)
+
+- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+
+- has_due_date = issuable.has_attribute?(:due_date)
+- has_labels = @labels && @labels.any?
+- form = local_assigns.fetch(:form)
+
+%hr
+.row
+  %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
+    .form-group.issue-assignee
+      = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+      .col-sm-10{ class: ("col-lg-8" if has_due_date) }
+        .issuable-form-select-holder
+          - if issuable.assignee_id
+            = form.hidden_field :assignee_id
+          = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+            placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
+    .form-group.issue-milestone
+      = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
+      .col-sm-10{ class: ("col-lg-8" if has_due_date) }
+        .issuable-form-select-holder
+          = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
+    .form-group
+      - has_labels = @labels && @labels.any?
+      = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
+      = form.hidden_field :label_ids, multiple: true, value: ''
+      .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
+        .issuable-form-select-holder
+          = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label"
+  - if has_due_date
+    .col-lg-6
+      .form-group
+        = form.label :due_date, "Due date", class: "control-label"
+        .col-sm-10
+          .issuable-form-select-holder
+            = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 331727ba9d8dc9613820638b6fd0899ee20b487c..fccddb70d189d62d4a84d686d2bca54a008c355a 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -2,14 +2,33 @@ class AuthorizedProjectsWorker
   include Sidekiq::Worker
   include DedicatedSidekiqQueue
 
+  LEASE_TIMEOUT = 1.minute.to_i
+
   def self.bulk_perform_async(args_list)
     Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
   end
 
   def perform(user_id)
     user = User.find_by(id: user_id)
-    return unless user
 
-    user.refresh_authorized_projects
+    refresh(user) if user
+  end
+
+  def refresh(user)
+    lease_key = "refresh_authorized_projects:#{user.id}"
+    lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
+
+    until uuid = lease.try_obtain
+      # Keep trying until we obtain the lease. If we don't do so we may end up
+      # not updating the list of authorized projects properly. To prevent
+      # hammering Redis too much we'll wait for a bit between retries.
+      sleep(1)
+    end
+
+    begin
+      user.refresh_authorized_projects
+    ensure
+      Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+    end
   end
 end
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 071741fbacd2cd6e6aca6860b9731f9a3040cc31..e9a5bd7f24eb9be17e3c6059950859b4d550341a 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -10,9 +10,10 @@ class ProcessCommitWorker
 
   # project_id - The ID of the project this commit belongs to.
   # user_id - The ID of the user that pushed the commit.
-  # commit_sha - The SHA1 of the commit to process.
+  # commit_hash - Hash containing commit details to use for constructing a
+  #               Commit object without having to use the Git repository.
   # default - The data was pushed to the default branch.
-  def perform(project_id, user_id, commit_sha, default = false)
+  def perform(project_id, user_id, commit_hash, default = false)
     project = Project.find_by(id: project_id)
 
     return unless project
@@ -21,10 +22,7 @@ class ProcessCommitWorker
 
     return unless user
 
-    commit = find_commit(project, commit_sha)
-
-    return unless commit
-
+    commit = build_commit(project, commit_hash)
     author = commit.author || user
 
     process_commit_message(project, commit, user, author, default)
@@ -59,9 +57,18 @@ class ProcessCommitWorker
       update_all(first_mentioned_in_commit_at: commit.committed_date)
   end
 
-  private
+  def build_commit(project, hash)
+    date_suffix = '_date'
+
+    # When processing Sidekiq payloads various timestamps are stored as Strings.
+    # Commit in turn expects Time-like instances upon input, so we have to
+    # manually parse these values.
+    hash.each do |key, value|
+      if key.to_s.end_with?(date_suffix) && value.is_a?(String)
+        hash[key] = Time.parse(value)
+      end
+    end
 
-  def find_commit(project, sha)
-    project.commit(sha)
+    Commit.from_hash(hash, project)
   end
 end
diff --git a/bin/changelog b/bin/changelog
index b6586ebb6aae010adb486f269065b584a837d4ff..e07b1ad237ad8f4e2f968f9aaf0ded5971d8c517 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -158,7 +158,7 @@ class ChangelogEntry
   end
 
   def branch_name
-    @branch_name ||= %x{git symbolic-ref HEAD}.strip.sub(%r{\Arefs/heads/}, '')
+    @branch_name ||= %x{git symbolic-ref --short HEAD}.strip
   end
 end
 
diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof
new file mode 100755
index 0000000000000000000000000000000000000000..df79feb201dd2e7715692cae25a2537addbc3d42
--- /dev/null
+++ b/bin/rspec-stackprof
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+require 'stackprof'
+$:.unshift 'spec'
+require 'rails_helper'
+
+filename = ARGV[0].split('/').last
+interval = ENV.fetch('INTERVAL', 1000).to_i
+limit = ENV.fetch('LIMIT', 20)
+output_file = "tmp/#{filename}.dump"
+
+StackProf.run(mode: :wall, out: output_file, interval: interval) do
+  RSpec::Core::Runner.run(ARGV, $stderr, $stdout)
+end
+
+system("stackprof #{output_file} --text --limit #{limit}")
diff --git a/changelogs/unreleased/18546-update-wiki-page-design.yml b/changelogs/unreleased/18546-update-wiki-page-design.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c76e17340f24080220a1b5e94a6320ec67f6f7d6
--- /dev/null
+++ b/changelogs/unreleased/18546-update-wiki-page-design.yml
@@ -0,0 +1,4 @@
+---
+title: Update wiki page design
+merge_request: 7429
+author:
diff --git a/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7f1d40e7c21f32e61454ae4049ee4686baddd30b
--- /dev/null
+++ b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml
@@ -0,0 +1,4 @@
+---
+title: 'Make API::Helpers find a project with only one query'
+merge_request: 7714
+author:
diff --git a/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54bd313f075abc5d5d8eed7c7d184e2a5e0eec5b
--- /dev/null
+++ b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml
@@ -0,0 +1,4 @@
+---
+title: New `gitlab:workhorse:install` rake task
+merge_request: 6574
+author: 
diff --git a/changelogs/unreleased/22781-user-generated-permalinks.yml b/changelogs/unreleased/22781-user-generated-permalinks.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e46739e48e371c8d6bfa78d549cc849a52898eeb
--- /dev/null
+++ b/changelogs/unreleased/22781-user-generated-permalinks.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent DOM ID collisions resulting from user-generated content anchors
+merge_request: 7631
+author:
diff --git a/changelogs/unreleased/23500-enable-colorvariable.yml b/changelogs/unreleased/23500-enable-colorvariable.yml
new file mode 100644
index 0000000000000000000000000000000000000000..98e22a934b855acb754a6d9745268c282918f3b8
--- /dev/null
+++ b/changelogs/unreleased/23500-enable-colorvariable.yml
@@ -0,0 +1,4 @@
+---
+title: Enable ColorVariable in scss-lint
+merge_request: 
+author: Sam Rose
diff --git a/changelogs/unreleased/23718-backup-rake-task-human-readable.yml b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2e7583244ac52aa2f2241137d3b6fea92267d67c
--- /dev/null
+++ b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml
@@ -0,0 +1,4 @@
+---
+title: Add Human Readable format for rake backup
+merge_request: 7188
+author: David Gerő
diff --git a/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml b/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml
new file mode 100644
index 0000000000000000000000000000000000000000..855e4e1ba1dc57de30c6f723ad38ff43b6f85b67
--- /dev/null
+++ b/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml
@@ -0,0 +1,4 @@
+---
+title: Moved new projects button below new group button on the welcome screen
+merge_request: 7770
+author: 
diff --git a/changelogs/unreleased/24150-consistent-dropdown-styles.yml b/changelogs/unreleased/24150-consistent-dropdown-styles.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a328d796c43bbef1486200cabaf351b9c748fe26
--- /dev/null
+++ b/changelogs/unreleased/24150-consistent-dropdown-styles.yml
@@ -0,0 +1,4 @@
+---
+title: Homogenize filter and sort dropdown look'n'feel
+merge_request: 7583
+author: David Wagner
diff --git a/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml b/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml
deleted file mode 100644
index 1404748e83ee92b6d8a56449e9b90639eb627ba8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed import sources buttons to checkboxes
-merge_request: 7598
-author: Luke "Jared" Bennett
diff --git a/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml b/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml
deleted file mode 100644
index 28ca20c7dcca518a029fb0b2cb3782c0b3750703..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: If Build running change accept merge request when build succeeds button from orange to blue
-merge_request: 7577
-author:
diff --git a/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2227c81bd34e0ab2b1004e31e83f054f69c093ec
--- /dev/null
+++ b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml
@@ -0,0 +1,4 @@
+---
+title: Remove the help text under the sidebar subscribe button and style it inline
+merge_request: 7389
+author:
diff --git a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01b19a47ecdff2d6ca6b6243b8e614c57e4ae3d3
--- /dev/null
+++ b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml
@@ -0,0 +1,4 @@
+---
+title: Speed up issuable dashboards
+merge_request:
+author:
diff --git a/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml
new file mode 100644
index 0000000000000000000000000000000000000000..07cb53d5278e357a1118714325ca43c4f532ec02
--- /dev/null
+++ b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml
@@ -0,0 +1,4 @@
+---
+title: Update generic/external build status to match normal build status template
+merge_request: 7811
+author:
diff --git a/changelogs/unreleased/24726-remove-across-gitlab.yml b/changelogs/unreleased/24726-remove-across-gitlab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6436e4b688f415e04e57ebefeec900079e03b4a9
--- /dev/null
+++ b/changelogs/unreleased/24726-remove-across-gitlab.yml
@@ -0,0 +1,4 @@
+---
+title: 24726 Remove Across GitLab from side navigation
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/24739-collapsed-build-list-sorting.yml b/changelogs/unreleased/24739-collapsed-build-list-sorting.yml
deleted file mode 100644
index 036e606318f091f680b3451360802ec3a5a240c0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24739-collapsed-build-list-sorting.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Sort builds by name within pipeline graph
-merge_request: 7681
-author: 
diff --git a/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml b/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml
deleted file mode 100644
index 92dbbe3d1642d109d481727631846fbdb25cfc60..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix wrong template rendered when CI/CD settings aren't update successfully
-merge_request: 7665
-author: 
diff --git a/changelogs/unreleased/24814-pipeline-tabs.yml b/changelogs/unreleased/24814-pipeline-tabs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f85e7576905f98f3fab932f806ae9d4ed9f94e82
--- /dev/null
+++ b/changelogs/unreleased/24814-pipeline-tabs.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Cicking on tabs on pipeline page should set URL
+merge_request: 7709
+author:
diff --git a/changelogs/unreleased/24844-environments-date.yml b/changelogs/unreleased/24844-environments-date.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2bc23d40a68cffd57bf5cec9a28985dc3ba3bd30
--- /dev/null
+++ b/changelogs/unreleased/24844-environments-date.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes Environments displaying incorrect date since 8.14 upgrade
+merge_request:
+author:
diff --git a/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml b/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml
deleted file mode 100644
index 9bdb941113568d7705f0efdbcd9c7279d7b63e48..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Correctly determine mergeability of MR with no discussions
-merge_request: 
-author: 
diff --git a/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml b/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4d4019e770eb77cc057960f901a767f10e6c3f24
--- /dev/null
+++ b/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml
@@ -0,0 +1,4 @@
+---
+title: Don't display prompt to add SSH keys if SSH protocol is disabled
+merge_request: 7840
+author: Andrew Smith (EspadaV8)
diff --git a/changelogs/unreleased/24999-fix-project-avatar-alignment.yml b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7af812e73595c81c526fab2cbf0f27ca0d0c3ec4
--- /dev/null
+++ b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Adjust the width of project avatars to fix alignment within their container
+merge_request: 
+author: Ryan Harris
diff --git a/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cc8b0e28277a661786704d54515944bcb236511f
--- /dev/null
+++ b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml
@@ -0,0 +1,4 @@
+---
+title: Sentence cased the nav tab headers on the project dashboard page
+merge_request: 
+author: Ryan Harris
diff --git a/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2c3ba1dfe44764b443e9490a7ea6e8073ee4fc5a
--- /dev/null
+++ b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml
@@ -0,0 +1,4 @@
+---
+title: Adds hoverstates for collapsed Issue/Merge Request sidebar
+merge_request: !7777
+author:
diff --git a/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml b/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7b5810f1bfdf5fdcb855d13234ff16853d90295
--- /dev/null
+++ b/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml
@@ -0,0 +1,4 @@
+---
+title: Redirect to sign-in page when unauthenticated user tries to create a snippet
+merge_request: 7786
+author:
diff --git a/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml
new file mode 100644
index 0000000000000000000000000000000000000000..862de7c5db17126ae858207af34c2cdff25ca485
--- /dev/null
+++ b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml
@@ -0,0 +1,4 @@
+---
+title: Do not raise error in AutocompleteController#users when not authorized
+merge_request: 7817
+author: Semyon Pupkov
diff --git a/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml b/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1799fad1631c6444e57e06453b2aad43f8a03810
--- /dev/null
+++ b/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Adjusted margins for Build Status and Coverage Report rows to match those of
+  the CI/CD Pipeline row
+merge_request: 
+author: Ryan Harris
diff --git a/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..58efd9113f2f1b5c1222b923173814891fd902de
--- /dev/null
+++ b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml
@@ -0,0 +1,4 @@
+---
+title: Don't change relative URLs to absolute URLs in the Help page
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml b/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7f1c417bc777a57e0ed8da1da8b8fdf005632dde
--- /dev/null
+++ b/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fixes "ActionView::Template::Error: undefined method `text?` for nil:NilClass"
+  on MR pages'
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/25264-ref-commit.yml b/changelogs/unreleased/25264-ref-commit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..13a33da9801c7ad6b6c5f91d16599dbee6667187
--- /dev/null
+++ b/changelogs/unreleased/25264-ref-commit.yml
@@ -0,0 +1,4 @@
+---
+title: Change ref property to commitRef in vue commit component
+merge_request: 7901
+author:
diff --git a/changelogs/unreleased/4269-public-api.yml b/changelogs/unreleased/4269-public-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..560bc6a4f1343b7130547dce4c486766f5b28ae6
--- /dev/null
+++ b/changelogs/unreleased/4269-public-api.yml
@@ -0,0 +1,4 @@
+---
+title: Allow public access to some Project API endpoints
+merge_request: 7843
+author:
diff --git a/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml b/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9dd04d3f089a98a41f05108455ea94885542fede
--- /dev/null
+++ b/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml
@@ -0,0 +1,3 @@
+title: Add setting to enable/disable HTML emails
+merge_request: 7749
+author:
diff --git a/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml b/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml
deleted file mode 100644
index 7d49c639a43ec6aeaab18f6d28826cf7f7e428b1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Last minute CI Style tweaks for 8.14
-merge_request: 7643
-author:
diff --git a/changelogs/unreleased/api-branch-status.yml b/changelogs/unreleased/api-branch-status.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5763345a2201b8d4692da34119bfde4d3fa0f89
--- /dev/null
+++ b/changelogs/unreleased/api-branch-status.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Expose merge status for branch API'
+merge_request:
+author: Robert Schilling
diff --git a/changelogs/unreleased/cleanup-common_utils-js.yml b/changelogs/unreleased/cleanup-common_utils-js.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54d81b76c28c146994fcc5a39020016b7bef1c9e
--- /dev/null
+++ b/changelogs/unreleased/cleanup-common_utils-js.yml
@@ -0,0 +1,4 @@
+---
+title: Clean up common_utils.js
+merge_request: 7318
+author: winniehell
diff --git a/changelogs/unreleased/comments-fixture.yml b/changelogs/unreleased/comments-fixture.yml
new file mode 100644
index 0000000000000000000000000000000000000000..824c1c88a60df4192ff9f18e4dacad1d068b760f
--- /dev/null
+++ b/changelogs/unreleased/comments-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for notes_spec
+merge_request: 7683
+author: winniehell
diff --git a/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml b/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f0d9ff0c34ffad5a38fe97e7da20ccd645d3fc8a
--- /dev/null
+++ b/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml
@@ -0,0 +1,4 @@
+---
+title: Create dynamic fixture for build_spec
+merge_request: 7589
+author: winniehell
diff --git a/changelogs/unreleased/disable-calendar-deselection.yml b/changelogs/unreleased/disable-calendar-deselection.yml
deleted file mode 100644
index 060797bba3469cdabe8fffb13b2d3309101617dc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/disable-calendar-deselection.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix deselecting calendar days on contribution graph
-merge_request: 6453
-author: ClemMakesApps
diff --git a/changelogs/unreleased/fix-build-without-trace-exceptions.yml b/changelogs/unreleased/fix-build-without-trace-exceptions.yml
deleted file mode 100644
index 3b95e96e212fd232bd2830231f9bf7767c0e0d34..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-build-without-trace-exceptions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix exceptions when loading build trace
-merge_request: 7658
-author: 
diff --git a/changelogs/unreleased/fix-cancelling-pipelines.yml b/changelogs/unreleased/fix-cancelling-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c21e663093aef6c42ee17c25ef51f4a2415e05af
--- /dev/null
+++ b/changelogs/unreleased/fix-cancelling-pipelines.yml
@@ -0,0 +1,4 @@
+---
+title: Fix cancelling created or external pipelines
+merge_request: 7508
+author:
diff --git a/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e37841e80c328ae76fcf633fbf53dc66f3a2ab29
--- /dev/null
+++ b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml
@@ -0,0 +1,4 @@
+---
+title: Create builds in transaction to avoid empty pipelines
+merge_request: 7742
+author: 
diff --git a/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml b/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml
deleted file mode 100644
index 6ed16c6d72251ac7258eaef173f8ab9ff0db6789..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix cycle analytics plan stage when commits are missing
-merge_request: 
-author: 
diff --git a/changelogs/unreleased/fix-github-branch-formatter.yml b/changelogs/unreleased/fix-github-branch-formatter.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c8698f507de0a4ecaf294e0352b21b98fadffc21
--- /dev/null
+++ b/changelogs/unreleased/fix-github-branch-formatter.yml
@@ -0,0 +1,4 @@
+---
+title: Fix branch validation for GitHub PR where repo/fork was renamed/deleted
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml b/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml
deleted file mode 100644
index 4f007be86249f20ef9897d3e0d319fd72897fe4b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Sidekiq stats in the admin area will now show correctly on different platforms
-merge_request:
-author: blackst0ne
diff --git a/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml b/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml
new file mode 100644
index 0000000000000000000000000000000000000000..73d8a52e001333c216991f05aebeea3378de4107
--- /dev/null
+++ b/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml
@@ -0,0 +1,4 @@
+---
+title: Add git diff context to notifications of new notes on merge requests
+merge_request:
+author: Heidi Hoopes
diff --git a/changelogs/unreleased/improve-invite-accept-page.yml b/changelogs/unreleased/improve-invite-accept-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8a09a5ae42f0f385a72edca877d66c568d966294
--- /dev/null
+++ b/changelogs/unreleased/improve-invite-accept-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add note to the invite page when the logged in user email is not the same as the invitation
+merge_request:
+author:
diff --git a/changelogs/unreleased/issuable_filters_present-refactor.yml b/changelogs/unreleased/issuable_filters_present-refactor.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c131f9cb68e22768193bcfadaefb73b6da7e1739
--- /dev/null
+++ b/changelogs/unreleased/issuable_filters_present-refactor.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor issuable_filters_present to reduce duplications
+merge_request: 7776
+author: Semyon Pupkov
diff --git a/changelogs/unreleased/issue-24534.yml b/changelogs/unreleased/issue-24534.yml
new file mode 100644
index 0000000000000000000000000000000000000000..14d6730d3f6488455a526e0d4f12e473379c8615
--- /dev/null
+++ b/changelogs/unreleased/issue-24534.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unnecessary sentences for status codes in the API documentation
+merge_request: 
+author: Luis Alonso Chavez Armendariz
diff --git a/changelogs/unreleased/issue-boards-dragging-fix.yml b/changelogs/unreleased/issue-boards-dragging-fix.yml
deleted file mode 100644
index 565e09b930b1ef88d6f7afa68c59625bc72d0566..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-boards-dragging-fix.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed issue boards dragging card removing random issues
-merge_request: 
-author: 
diff --git a/changelogs/unreleased/issue-boards-scrollable-element.yml b/changelogs/unreleased/issue-boards-scrollable-element.yml
new file mode 100644
index 0000000000000000000000000000000000000000..90edc30e7917213aa937b836d5743f425cd7e666
--- /dev/null
+++ b/changelogs/unreleased/issue-boards-scrollable-element.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed issue boards scrolling with a lot of lists & issues
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/issue_24363.yml b/changelogs/unreleased/issue_24363.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0298890b4772f0adb16c1b260181c3d75fa37b5c
--- /dev/null
+++ b/changelogs/unreleased/issue_24363.yml
@@ -0,0 +1,4 @@
+---
+title: Fix appearance in error pages
+merge_request: 
+author: Luis Alonso Chavez Armendariz
diff --git a/changelogs/unreleased/issue_24748.yml b/changelogs/unreleased/issue_24748.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4c1df542c539a67869d2921e42811457181471c2
--- /dev/null
+++ b/changelogs/unreleased/issue_24748.yml
@@ -0,0 +1,4 @@
+---
+title: Fix title case to sentence case
+merge_request: 
+author: Luis Alonso Chavez Armendariz
diff --git a/changelogs/unreleased/issue_24958.yml b/changelogs/unreleased/issue_24958.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dbbbbf9d28d89bf6af3c6cecce12c06c88178e9d
--- /dev/null
+++ b/changelogs/unreleased/issue_24958.yml
@@ -0,0 +1,4 @@
+---
+title: Fix bad selection on dropdown menu for tags filter
+merge_request: 
+author: Luis Alonso Chavez Armendariz
diff --git a/changelogs/unreleased/jej-22869.yml b/changelogs/unreleased/jej-22869.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9d2edcfee42c9626ec30717ba77f96daad64004e
--- /dev/null
+++ b/changelogs/unreleased/jej-22869.yml
@@ -0,0 +1,4 @@
+---
+title: Fix information disclosure in `Projects::BlobController#update`
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml b/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml
new file mode 100644
index 0000000000000000000000000000000000000000..844fba9a1075aacdc0f3a8059377f8e24b7bdfd7
--- /dev/null
+++ b/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml
@@ -0,0 +1,4 @@
+---
+title: Fix missing access checks on issue lookup using IssuableFinder
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml b/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0b6f50052c0316d4aee75376106a8f9d3472b0e
--- /dev/null
+++ b/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml
@@ -0,0 +1,4 @@
+---
+title: Replace issue access checks with use of IssuableFinder
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7ec2c2055431ff57c37c41e278c61f0e4f3aefc
--- /dev/null
+++ b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml
@@ -0,0 +1,4 @@
+---
+title: Move admin spam spinach test to Rspec
+merge_request: 7708
+author: Semyon Pupkov
diff --git a/changelogs/unreleased/mr-origin-7855.yml b/changelogs/unreleased/mr-origin-7855.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0fdc6153d55b8cc921473bc5f67851dc8f35c327
--- /dev/null
+++ b/changelogs/unreleased/mr-origin-7855.yml
@@ -0,0 +1,4 @@
+---
+title: Provides a sensible default message when adding a README to a project
+merge_request: 7903
+author: 
diff --git a/changelogs/unreleased/process-commit-worker-improvements.yml b/changelogs/unreleased/process-commit-worker-improvements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0038c6e34e69c18bfa4324c9601596c456e6143d
--- /dev/null
+++ b/changelogs/unreleased/process-commit-worker-improvements.yml
@@ -0,0 +1,4 @@
+---
+title: Pass commit data to ProcessCommitWorker to reduce Git overhead
+merge_request: 7744
+author: 
diff --git a/changelogs/unreleased/readme-link-fix.yml b/changelogs/unreleased/readme-link-fix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..211d3b80c3ad8ab1ad18a6131560bf2492ad0307
--- /dev/null
+++ b/changelogs/unreleased/readme-link-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fix broken README.md UX guide link.
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/remove-backup-strategies.yml b/changelogs/unreleased/remove-backup-strategies.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9f034613c2cef78e2925b616be6af9f8d9e14681
--- /dev/null
+++ b/changelogs/unreleased/remove-backup-strategies.yml
@@ -0,0 +1,4 @@
+---
+title: Stop supporting Google and Azure as backup strategies
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml b/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6e02998b3a87221193e00dbbd313c49ff0a2d88b
--- /dev/null
+++ b/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml
@@ -0,0 +1,5 @@
+---
+title: Changed eslint airbnb config to the base airbnb config and corrected eslintrc
+  plugins and envs
+merge_request: 7470
+author: Luke "Jared" Bennett
diff --git a/changelogs/unreleased/removing_unnecessary_indexes.yml b/changelogs/unreleased/removing_unnecessary_indexes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01314ab558562c4ba37a0c1de8b096e1a8d890d3
--- /dev/null
+++ b/changelogs/unreleased/removing_unnecessary_indexes.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unnecessary database indices
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/right-sidebar-fixture.yml b/changelogs/unreleased/right-sidebar-fixture.yml
new file mode 100644
index 0000000000000000000000000000000000000000..46a3e459fefad7890141ceeb9d5a0b1d81ff53ab
--- /dev/null
+++ b/changelogs/unreleased/right-sidebar-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for right_sidebar_spec
+merge_request: 7687
+author: winniehell
diff --git a/changelogs/unreleased/shortcuts-issuable-fixture.yml b/changelogs/unreleased/shortcuts-issuable-fixture.yml
new file mode 100644
index 0000000000000000000000000000000000000000..88945600886e31d9058400669cdc9c88cb9c167b
--- /dev/null
+++ b/changelogs/unreleased/shortcuts-issuable-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for shortcuts_issuable_spec
+merge_request: 7685
+author: winniehell
diff --git a/changelogs/unreleased/update-api-spec-files.yml b/changelogs/unreleased/update-api-spec-files.yml
new file mode 100644
index 0000000000000000000000000000000000000000..349d866cf227f8b8570bf6ba5b9557c415a7ab5c
--- /dev/null
+++ b/changelogs/unreleased/update-api-spec-files.yml
@@ -0,0 +1,4 @@
+---
+title: Update API spec files to describe the correct class
+merge_request: 
+author: Livier
diff --git a/changelogs/unreleased/use-st-commits-where-possible.yml b/changelogs/unreleased/use-st-commits-where-possible.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e4395461560c6851918b0f7770fadc7bf760032c
--- /dev/null
+++ b/changelogs/unreleased/use-st-commits-where-possible.yml
@@ -0,0 +1,5 @@
+---
+title: Replace references to MergeRequestDiff#commits with st_commits when we care
+  only about the number of commits
+merge_request: 7668
+author:
diff --git a/changelogs/unreleased/zen-mode-fixture.yml b/changelogs/unreleased/zen-mode-fixture.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bec6f6e6dba73b85021028ee2930a64c50ca9320
--- /dev/null
+++ b/changelogs/unreleased/zen-mode-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for zen_mode_spec
+merge_request: 7686
+author: winniehell
diff --git a/changelogs/unreleased/zj-expose-coverage-pipelines.yml b/changelogs/unreleased/zj-expose-coverage-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..34e4926e58a0ba8771f23792ce6795b44caced90
--- /dev/null
+++ b/changelogs/unreleased/zj-expose-coverage-pipelines.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: expose pipeline coverage'
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/zj-fix-label-creation-non-members.yml b/changelogs/unreleased/zj-fix-label-creation-non-members.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ae4824f82fa8b6210b652b6f9bf41334fefad0da
--- /dev/null
+++ b/changelogs/unreleased/zj-fix-label-creation-non-members.yml
@@ -0,0 +1,4 @@
+---
+title: Non members cannot create labels through the API
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/zj-issue-search-slash-command.yml b/changelogs/unreleased/zj-issue-search-slash-command.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de41c39d545b4be9347e4673be12b8732a278599
--- /dev/null
+++ b/changelogs/unreleased/zj-issue-search-slash-command.yml
@@ -0,0 +1,4 @@
+---
+title: Add issue search slash command
+merge_request: 
+author: 
diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb
index 0da584626eeaef2bc4e0f91f4c1bb7b712f0d85c..6979f4641b013e591adff9d92e22618ff9f0ca40 100644
--- a/config/initializers/ar_monkey_patch.rb
+++ b/config/initializers/ar_monkey_patch.rb
@@ -52,6 +52,23 @@ module ActiveRecord
           raise
         end
       end
+
+      # This is patched because we need it to query `lock_version IS NULL`
+      # rather than `lock_version = 0` whenever lock_version is NULL.
+      def relation_for_destroy
+        return super unless locking_enabled?
+
+        column_name = self.class.locking_column
+        super.where(self.class.arel_table[column_name].eq(self[column_name]))
+      end
+    end
+
+    # This is patched because we want `lock_version` default to `NULL`
+    # rather than `0`
+    class LockingType < SimpleDelegator
+      def type_cast_from_database(value)
+        super
+      end
     end
   end
 end
diff --git a/config/initializers/email_template_interceptor.rb b/config/initializers/email_template_interceptor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f195ca9bcd6a8df109e96d294a1270be85964560
--- /dev/null
+++ b/config/initializers/email_template_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/email_template_interceptor.rb
+ActionMailer::Base.register_interceptor(EmailTemplateInterceptor)
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index b87b31d9697bc7dd4bd03a7f12f320e175a79054..1d7a3f03ace85b9c0d89b995701ef783f7cf84ed 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -61,5 +61,5 @@ begin
       end
     end
   end
-rescue Redis::BaseError, SocketError
+rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
 end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 1336484a39993fc5e71d2aa5b703b9d249aea6ca..0754f0ec3b0a1cca0a6b98a6cc2d820f834fd763 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -129,6 +129,7 @@ constraints(ProjectUrlConstrainer.new) do
         member do
           post :cancel
           post :retry
+          get :builds
         end
       end
 
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 18a2df7c059ea9bdb6623984c9c2b33a69b8a23f..a984eda5ab5900a1b80de211903510a29f9f530d 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,5 +1,4 @@
 require 'sidekiq/testing'
-require './db/fixtures/support/serialized_transaction'
 
 Sidekiq::Testing.inline! do
   Gitlab::Seeder.quiet do
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index 04c3690e152086cecf767da56cffad0500a1b780..5c2a03fec3f09a2621aab18b7c368af15d0aa65f 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,5 +1,4 @@
 require 'sidekiq/testing'
-require './db/fixtures/support/serialized_transaction'
 
 Sidekiq::Testing.inline! do
   Gitlab::Seeder.quiet do
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 7b3908fae9827b4cd7637797e906a92299ea0c8c..916ee8dbac8a6cc937550fcd70cfd59c7b407c4c 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -1,6 +1,5 @@
 require 'sidekiq/testing'
 require './spec/support/test_env'
-require './db/fixtures/support/serialized_transaction'
 
 class Gitlab::Seeder::CycleAnalytics
   def initialize(project, perf: false)
diff --git a/db/fixtures/support/serialized_transaction.rb b/db/fixtures/support/serialized_transaction.rb
deleted file mode 100644
index d3305b661e59de52f2eabeb0aa38ab67c7e2e6ab..0000000000000000000000000000000000000000
--- a/db/fixtures/support/serialized_transaction.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'gitlab/database'
-
-module Gitlab
-  module Database
-    def self.serialized_transaction
-      connection.transaction { yield }
-    end
-  end
-end
diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb
index 66eb11a4b2b77a185adb47f99094f8e8db0ef126..065a5e08243bf40da5bd48e3e659f53cf32d901f 100644
--- a/db/migrate/20130319214458_create_forked_project_links.rb
+++ b/db/migrate/20130319214458_create_forked_project_links.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateForkedProjectLinks < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :forked_project_links do |t|
       t.integer :forked_to_project_id, null: false
       t.integer :forked_from_project_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
     add_index :forked_project_links, :forked_to_project_id, unique: true
   end
diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb
index 7d6662d358ad4c70ef1088c96a7557d915f80d7a..8b9662a27c38b5661cf6e457dc67adda472aaf98 100644
--- a/db/migrate/20130506090604_create_deploy_keys_projects.rb
+++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateDeployKeysProjects < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :deploy_keys_projects do |t|
       t.integer :deploy_key_id, null: false
       t.integer :project_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb
index 45cff93fe4ad9051f42887dd9b1743f53092fd86..4ba7d0c9461c4699ab3f38f6e0f33a9c8269142c 100644
--- a/db/migrate/20130617095603_create_users_groups.rb
+++ b/db/migrate/20130617095603_create_users_groups.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class CreateUsersGroups < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :users_groups do |t|
       t.integer :group_access, null: false
       t.integer :group_id, null: false
       t.integer :user_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb
index bd9d40a50db7c28e1b3d9700132c189d8371b79e..efccb2aa938b69a0e5b4fb21dc75cad9b6736cdc 100644
--- a/db/migrate/20130711063759_create_project_group_links.rb
+++ b/db/migrate/20130711063759_create_project_group_links.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateProjectGroupLinks < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :project_group_links do |t|
       t.integer :project_id, null: false
       t.integer :group_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb
index ce37a8e2708655dbd3da66ff486318f5fbd2ee21..ad2549e53afe88410837b3217fd2d3b417317f8d 100644
--- a/db/migrate/20131112114325_create_broadcast_messages.rb
+++ b/db/migrate/20131112114325_create_broadcast_messages.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class CreateBroadcastMessages < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :broadcast_messages do |t|
       t.text :message, null: false
@@ -7,7 +9,7 @@ class CreateBroadcastMessages < ActiveRecord::Migration
       t.datetime :ends_at
       t.integer :alert_type
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb
index 395c3edfc7972a260e895852016dd48950bff35d..6c7a92b69503b02ef873f1946f7d859f61b3b905 100644
--- a/db/migrate/20140122112253_create_merge_request_diffs.rb
+++ b/db/migrate/20140122112253_create_merge_request_diffs.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class CreateMergeRequestDiffs < ActiveRecord::Migration
+  DOWNTIME = false
+
   def up
     create_table :merge_request_diffs do |t|
       t.string :state, null: false, default: 'collected'
@@ -7,7 +9,7 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration
       t.text :st_diffs, null: true
       t.integer :merge_request_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     if ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb
index 571beb19cdd6e111bb20c796da134514a2fd88e6..51886f8fc89e57c58b1601e0d608dea55720dcd7 100644
--- a/db/migrate/20140209025651_create_emails.rb
+++ b/db/migrate/20140209025651_create_emails.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateEmails < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :emails do |t|
       t.integer  :user_id, null: false
       t.string   :email, null: false
-      
-      t.timestamps
+
+      t.timestamps null: true
     end
 
     add_index :emails, :user_id
diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb
index 32dd99e83be5eb2d1eee63bddec8e681afac8608..d4f3fe5ac6226bf03fbc5666d3b4b446c5ce3f63 100644
--- a/db/migrate/20140625115202_create_users_star_projects.rb
+++ b/db/migrate/20140625115202_create_users_star_projects.rb
@@ -1,10 +1,12 @@
 # rubocop:disable all
 class CreateUsersStarProjects < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :users_star_projects do |t|
       t.integer :project_id, null: false
       t.integer :user_id, null: false
-      t.timestamps
+      t.timestamps null: true
     end
     add_index :users_star_projects, :user_id
     add_index :users_star_projects, :project_id
diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb
index df0f8cb9f0308725538cf7679b316f144abd9efb..66d20e741a6e3b63f5316ecdf27058336e8703f3 100644
--- a/db/migrate/20140729134820_create_labels.rb
+++ b/db/migrate/20140729134820_create_labels.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class CreateLabels < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :labels do |t|
       t.string :title
       t.string :color
       t.integer :project_id
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb
index fa5992605f81bff2bf0df5c9118390c9c3b0f855..dacd9f2e4b6a9d9eba6075733976d94c5cc7062b 100644
--- a/db/migrate/20140729140420_create_label_links.rb
+++ b/db/migrate/20140729140420_create_label_links.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class CreateLabelLinks < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :label_links do |t|
       t.integer :label_id
       t.integer :target_id
       t.string :target_type
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb
index bc3c1bb61e4182a2e7a5d20e29d28df2ff91ac1b..0f76bb0ef7960f4c9918b314a294e2fefc745da6 100644
--- a/db/migrate/20140914113604_add_members_table.rb
+++ b/db/migrate/20140914113604_add_members_table.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class AddMembersTable < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :members do |t|
       t.integer :access_level, null: false
@@ -9,7 +11,7 @@ class AddMembersTable < ActiveRecord::Migration
       t.integer :notification_level, null: false
       t.string  :type
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :members, :type
diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb
index aff8e94e5be670a58467d81f2e77c6aca827363c..d2ab326ef1fb936ee7a46f6f78c9f1a17f9416e4 100644
--- a/db/migrate/20140914173417_remove_old_member_tables.rb
+++ b/db/migrate/20140914173417_remove_old_member_tables.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class RemoveOldMemberTables < ActiveRecord::Migration
+  DOWNTIME = false
+
   def up
     drop_table :users_groups
     drop_table :users_projects
@@ -12,7 +14,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration
       t.integer :user_id, null: false
       t.integer :notification_level, null: false, default: 3
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     create_table :users_projects do |t|
@@ -21,7 +23,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration
       t.integer :user_id, null: false
       t.integer :notification_level, null: false, default: 3
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb
index 3884228456fc177f857e705940cc9b8bc464e109..52d70b4a0ac693ae75bac5a4e719acd33d64db20 100644
--- a/db/migrate/20141118150935_add_audit_event.rb
+++ b/db/migrate/20141118150935_add_audit_event.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class AddAuditEvent < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :audit_events do |t|
       t.integer :author_id, null: false
@@ -13,7 +15,7 @@ class AddAuditEvent < ActiveRecord::Migration
       # Details for the event
       t.text  :details
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :audit_events, :author_id
diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb
index b323ffe96f5085319982fa8a42d28836a732190b..17e45a77291ffc41cc5c0e04407ba0623d0e9b57 100644
--- a/db/migrate/20141216155758_create_doorkeeper_tables.rb
+++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class CreateDoorkeeperTables < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :oauth_applications do |t|
       t.string  :name,         null: false
@@ -7,7 +9,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration
       t.string  :secret,       null: false
       t.text    :redirect_uri, null: false
       t.string  :scopes,       null: false, default: ''
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :oauth_applications, :uid, unique: true
diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb
index dfa2f765357b37a21df0f788128026688779b4e2..0e4c66ca8c02a6194de16ac240878d247249442a 100644
--- a/db/migrate/20150108073740_create_application_settings.rb
+++ b/db/migrate/20150108073740_create_application_settings.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class CreateApplicationSettings < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :application_settings do |t|
       t.integer :default_projects_limit
@@ -8,7 +10,7 @@ class CreateApplicationSettings < ActiveRecord::Migration
       t.boolean :gravatar_enabled
       t.text :sign_in_text
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb
index 8adb193b27fc40f837f7a781aa0b7f7827d64c63..a9a8435330df2f34b8a69a2029dc4be00e8bfaae 100644
--- a/db/migrate/20150313012111_create_subscriptions_table.rb
+++ b/db/migrate/20150313012111_create_subscriptions_table.rb
@@ -1,15 +1,17 @@
 # rubocop:disable all
 class CreateSubscriptionsTable < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :subscriptions do |t|
       t.integer :user_id
       t.references :subscribable, polymorphic: true
       t.boolean :subscribed
-      
-      t.timestamps
+
+      t.timestamps null: true
     end
 
-    add_index :subscriptions, 
+    add_index :subscriptions,
               [:subscribable_id, :subscribable_type, :user_id],
               unique: true,
               name: 'subscriptions_user_id_and_ref_fields'
diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb
index 3c749b5d9a9b969dd768f946f5705fafc7bb7d5d..52aed9e1d1d79faaa814db60b6fc41449e0c58b5 100644
--- a/db/migrate/20150806104937_create_abuse_reports.rb
+++ b/db/migrate/20150806104937_create_abuse_reports.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class CreateAbuseReports < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :abuse_reports do |t|
       t.integer :reporter_id
       t.integer :user_id
       t.text :message
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb
index 745b52e2b24bf52bbb4960eec8be439956490d65..db6fa27199bd9a2edb8d73c84411b5247952a893 100644
--- a/db/migrate/20151103134857_create_lfs_objects.rb
+++ b/db/migrate/20151103134857_create_lfs_objects.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateLfsObjects < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :lfs_objects do |t|
       t.string :oid, null: false, unique: true
       t.integer :size, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb
index 3178e85b899b16167e53a9a95f8c4591ef41f2f0..5af1c39fd9cfcf7e7a49895b3759b9e7e7c02d8e 100644
--- a/db/migrate/20151103134958_create_lfs_objects_projects.rb
+++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb
@@ -1,11 +1,13 @@
 # rubocop:disable all
 class CreateLfsObjectsProjects < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :lfs_objects_projects do |t|
       t.integer :lfs_object_id, null: false
       t.integer :project_id, null: false
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :lfs_objects_projects, :project_id
diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb
index 145b8db1486177f06cb972235e60db6e086ac64f..34dd7a10942bbc67a846ba2a2e54d0af6601494a 100644
--- a/db/migrate/20151105094515_create_releases.rb
+++ b/db/migrate/20151105094515_create_releases.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class CreateReleases < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :releases do |t|
       t.string :tag
       t.text :description
       t.integer :project_id
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :releases, :project_id
diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb
index 20573b01351acd2184ebafe70d0373ed37e3159d..cd3ad0e4cd8d9137712bdb49591d6adc9e2d9bb6 100644
--- a/db/migrate/20160212123307_create_tasks.rb
+++ b/db/migrate/20160212123307_create_tasks.rb
@@ -1,5 +1,7 @@
 # rubocop:disable all
 class CreateTasks < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :tasks do |t|
       t.references :user, null: false, index: true
@@ -9,7 +11,7 @@ class CreateTasks < ActiveRecord::Migration
       t.integer :action, null: false
       t.string :state, null: false, index: true
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb
index a3bee9b1bc69c2557a15e9beae5ed5e1f94864f1..0d252e5044e8a523778f91b1f6947b384ca6cec3 100644
--- a/db/migrate/20160416180807_add_award_emoji.rb
+++ b/db/migrate/20160416180807_add_award_emoji.rb
@@ -1,12 +1,14 @@
 # rubocop:disable all
 class AddAwardEmoji < ActiveRecord::Migration
+  DOWNTIME = false
+
   def change
     create_table :award_emoji do |t|
       t.string :name
       t.references :user
       t.references :awardable, polymorphic: true
 
-      t.timestamps
+      t.timestamps null: true
     end
 
     add_index :award_emoji, :user_id
diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb
index 2d76a015a08615f3044c988dfde670463036180a..343953826f0bcce4bba763f77ee0146097b03f5c 100644
--- a/db/migrate/20160831214002_create_project_features.rb
+++ b/db/migrate/20160831214002_create_project_features.rb
@@ -10,7 +10,7 @@ class CreateProjectFeatures < ActiveRecord::Migration
       t.integer  :snippets_access_level
       t.integer  :builds_access_level
 
-      t.timestamps
+      t.timestamps null: true
     end
   end
 end
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..453a44e271ac9561197b075fbc475e9cd9187660
--- /dev/null
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -0,0 +1,92 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  class Project < ActiveRecord::Base
+    def self.find_including_path(id)
+      select("projects.*, CONCAT(namespaces.path, '/', projects.path) AS path_with_namespace").
+        joins('INNER JOIN namespaces ON namespaces.id = projects.namespace_id').
+        find_by(id: id)
+    end
+
+    def repository_storage_path
+      Gitlab.config.repositories.storages[repository_storage]
+    end
+
+    def repository_path
+      File.join(repository_storage_path, read_attribute(:path_with_namespace) + '.git')
+    end
+
+    def repository
+      @repository ||= Rugged::Repository.new(repository_path)
+    end
+  end
+
+  DOWNTIME = true
+  DOWNTIME_REASON = 'Existing workers will error until they are using a newer version of the code'
+
+  disable_ddl_transaction!
+
+  def up
+    Sidekiq.redis do |redis|
+      new_jobs = []
+
+      while job = redis.lpop('queue:process_commit')
+        payload = JSON.load(job)
+        project = Project.find_including_path(payload['args'][0])
+
+        next unless project
+
+        begin
+          commit = project.repository.lookup(payload['args'][2])
+        rescue Rugged::OdbError
+          next
+        end
+
+        hash = {
+          id: commit.oid,
+          message: commit.message,
+          parent_ids: commit.parent_ids,
+          authored_date: commit.author[:time],
+          author_name: commit.author[:name],
+          author_email: commit.author[:email],
+          committed_date: commit.committer[:time],
+          committer_email: commit.committer[:email],
+          committer_name: commit.committer[:name]
+        }
+
+        payload['args'][2] = hash
+
+        new_jobs << JSON.dump(payload)
+      end
+
+      redis.multi do |multi|
+        new_jobs.each do |j|
+          multi.lpush('queue:process_commit', j)
+        end
+      end
+    end
+  end
+
+  def down
+    Sidekiq.redis do |redis|
+      new_jobs = []
+
+      while job = redis.lpop('queue:process_commit')
+        payload = JSON.load(job)
+
+        payload['args'][2] = payload['args'][2]['id']
+
+        new_jobs << JSON.dump(payload)
+      end
+
+      redis.multi do |multi|
+        new_jobs.each do |j|
+          multi.lpush('queue:process_commit', j)
+        end
+      end
+    end
+  end
+end
diff --git a/db/migrate/20161128142110_remove_unnecessary_indexes.rb b/db/migrate/20161128142110_remove_unnecessary_indexes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9deab19782e01277768c5cc4d2e3d3b1e59dfecd
--- /dev/null
+++ b/db/migrate/20161128142110_remove_unnecessary_indexes.rb
@@ -0,0 +1,33 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveUnnecessaryIndexes < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  DOWNTIME = false
+
+  def up
+    remove_index :labels, column: :group_id if index_exists?(:labels, :group_id)
+    remove_index :award_emoji, column: :user_id if index_exists?(:award_emoji, :user_id)
+    remove_index :ci_builds, column: :commit_id if index_exists?(:ci_builds, :commit_id)
+    remove_index :deployments, column: :project_id if index_exists?(:deployments, :project_id)
+    remove_index :deployments, column: ["project_id", "environment_id"] if index_exists?(:deployments, ["project_id", "environment_id"])
+    remove_index :lists, column: :board_id if index_exists?(:lists, :board_id)
+    remove_index :milestones, column: :project_id if index_exists?(:milestones, :project_id)
+    remove_index :notes, column: :project_id if index_exists?(:notes, :project_id)
+    remove_index :users_star_projects, column: :user_id if index_exists?(:users_star_projects, :user_id)
+  end
+
+  def down
+    add_concurrent_index :labels, :group_id
+    add_concurrent_index :award_emoji, :user_id
+    add_concurrent_index :ci_builds, :commit_id
+    add_concurrent_index :deployments, :project_id
+    add_concurrent_index :deployments, ["project_id", "environment_id"]
+    add_concurrent_index :lists, :board_id
+    add_concurrent_index :milestones, :project_id
+    add_concurrent_index :notes, :project_id
+    add_concurrent_index :users_star_projects, :user_id
+  end
+end
diff --git a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c59241d0feef21aeb87a42dd5bc7ffd1ce2a505
--- /dev/null
+++ b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddHtmlEmailsEnabledToApplicationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    add_column :application_settings, :html_emails_enabled, :boolean, default: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b3c49b5259705b7b78e92cbd5465e33f99461f0f..0d510c8a269f1d83d62cd039e8c5b6a3f9435a67 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: 20161118183841) do
+ActiveRecord::Schema.define(version: 20161128161412) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -106,6 +106,7 @@ ActiveRecord::Schema.define(version: 20161118183841) do
     t.integer "housekeeping_incremental_repack_period", default: 10, null: false
     t.integer "housekeeping_full_repack_period", default: 50, null: false
     t.integer "housekeeping_gc_period", default: 200, null: false
+    t.boolean "html_emails_enabled", default: true
   end
 
   create_table "audit_events", force: :cascade do |t|
@@ -131,7 +132,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
 
   add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
   add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
-  add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree
 
   create_table "boards", force: :cascade do |t|
     t.integer "project_id", null: false
@@ -219,7 +219,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
   add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
   add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
-  add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
   add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree
   add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
   add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
@@ -409,9 +408,7 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   end
 
   add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree
-  add_index "deployments", ["project_id", "environment_id"], name: "index_deployments_on_project_id_and_environment_id", using: :btree
   add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree
-  add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree
 
   create_table "emails", force: :cascade do |t|
     t.integer "user_id", null: false
@@ -569,7 +566,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   end
 
   add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
-  add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree
 
   create_table "lfs_objects", force: :cascade do |t|
     t.string "oid", null: false
@@ -600,7 +596,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   end
 
   add_index "lists", ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree
-  add_index "lists", ["board_id"], name: "index_lists_on_board_id", using: :btree
   add_index "lists", ["label_id"], name: "index_lists_on_label_id", using: :btree
 
   create_table "members", force: :cascade do |t|
@@ -726,7 +721,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
   add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
   add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
-  add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
   add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree
   add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
@@ -789,7 +783,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
   add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
   add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
   add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
-  add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
   add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
 
   create_table "notification_settings", force: :cascade do |t|
@@ -1242,7 +1235,6 @@ ActiveRecord::Schema.define(version: 20161118183841) do
 
   add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree
   add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
-  add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
 
   create_table "web_hooks", force: :cascade do |t|
     t.string "url", limit: 2000
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index d3f216fb3bfd76510dd8ae78091c115a80075f75..b8b63df091e677c4ef659a8229b2f44f2de4fd35 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -221,7 +221,7 @@ Tip: If you want to limit access to the nested members of an Active Directory
 group you can use the following syntax:
 
 ```
-(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
+(memberOf=CN=My Group,DC=Example,DC=com)
 ```
 
 Please note that GitLab does not support the custom filter syntax used by
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
index 64353f7282b2b3b14613eb61a721b26382e961c8..3ba8387c7f0e7c14fa87dc16cb0953f1147c928d 100644
--- a/doc/administration/build_artifacts.md
+++ b/doc/administration/build_artifacts.md
@@ -84,7 +84,7 @@ _The artifacts are stored by default in
 ## Set the maximum file size of the artifacts
 
 Provided the artifacts are enabled, you can change the maximum file size of the
-artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration#maximum-artifacts-size).
+artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size).
 
 [reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
 [restart gitlab]: restart_gitlab.md "How to restart GitLab"
diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md
index ea308b54d62d6d79a996b4f254fb23b02c3b6eec..dee3e3840807477e904ac640c1e140c0db87de3b 100644
--- a/doc/api/access_requests.md
+++ b/doc/api/access_requests.md
@@ -18,8 +18,6 @@
 
 Gets a list of access requests viewable by the authenticated user.
 
-Returns `200` if the request succeeds.
-
 ```
 GET /groups/:id/access_requests
 GET /projects/:id/access_requests
@@ -61,8 +59,6 @@ Example response:
 
 Requests access for the authenticated user to a group or project.
 
-Returns `201` if the request succeeds.
-
 ```
 POST /groups/:id/access_requests
 POST /projects/:id/access_requests
@@ -94,8 +90,6 @@ Example response:
 
 Approves an access request for the given user.
 
-Returns `201` if the request succeeds.
-
 ```
 PUT /groups/:id/access_requests/:user_id/approve
 PUT /projects/:id/access_requests/:user_id/approve
@@ -129,8 +123,6 @@ Example response:
 
 Denies an access request for the given user.
 
-Returns `200` if the request succeeds.
-
 ```
 DELETE /groups/:id/access_requests/:user_id
 DELETE /projects/:id/access_requests/:user_id
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 06111f4ab671a4d73c7f6878500b4395a2793653..58092bdd400890e3102f7c24fb45daea2691fb34 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -158,7 +158,7 @@ Example Response:
 ### Delete an award emoji
 
 Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award. Status code 200 on success, 401 if unauthorized.
+admins or the author of the award.
 
 ```
 DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id
@@ -331,7 +331,7 @@ Example Response:
 ### Delete an award emoji
 
 Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award. Status code 200 on success, 401 if unauthorized.
+admins or the author of the award.
 
 ```
 DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 28681719f431256f479ce364477e3ddf6dbce8ec..c83db6df80cd3440b36f1886d1e82db1304e38e8 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -148,10 +148,6 @@ Example response:
 
 Creates a new Issue Board list.
 
-If the operation is successful, a status code of `200` and the newly-created
-list is returned. If an error occurs, an error number and a message explaining
-the reason is returned.
-
 ```
 POST /projects/:id/boards/:board_id/lists
 ```
@@ -184,10 +180,6 @@ Example response:
 
 Updates an existing Issue Board list. This call is used to change list position.
 
-If the operation is successful, a code of `200` and the updated board list is
-returned. If an error occurs, an error number and a message explaining the
-reason is returned.
-
 ```
 PUT /projects/:id/boards/:board_id/lists/:list_id
 ```
@@ -220,8 +212,6 @@ Example response:
 ## Delete a board list
 
 Only for admins and project owners. Soft deletes the board list in question.
-If the operation is successful, a status code `200` is returned. In case you cannot
-destroy this board list, or it is not present, code `404` is given.
 
 ```
 DELETE /projects/:id/boards/:board_id/lists/:list_id
diff --git a/doc/api/branches.md b/doc/api/branches.md
index f68eeb9f86bb89193fa72acadabca8f79a325b1f..ffcfea41453928ca1b62b0b3e34f7d2f33be5060 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -22,6 +22,7 @@ Example response:
 [
   {
     "name": "master",
+    "merged": false,
     "protected": true,
     "developers_can_push": false,
     "developers_can_merge": false,
@@ -65,6 +66,7 @@ Example response:
 ```json
 {
   "name": "master",
+  "merged": false,
   "protected": true,
   "developers_can_push": false,
   "developers_can_merge": false,
@@ -123,6 +125,7 @@ Example response:
     ]
   },
   "name": "master",
+  "merged": false,
   "protected": true,
   "developers_can_push": true,
   "developers_can_merge": true
@@ -166,6 +169,7 @@ Example response:
     ]
   },
   "name": "master",
+  "merged": false,
   "protected": false,
   "developers_can_push": false,
   "developers_can_merge": false
@@ -206,15 +210,13 @@ Example response:
     ]
   },
   "name": "newbranch",
+  "merged": false,
   "protected": false,
   "developers_can_push": false,
   "developers_can_merge": false
 }
 ```
 
-It returns `200` if it succeeds or `400` if failed with an error message
-explaining the reason.
-
 ## Delete repository branch
 
 ```
@@ -226,8 +228,7 @@ DELETE /projects/:id/repository/branches/:branch
 | `id`      | integer | yes | The ID of a project |
 | `branch`  | string  | yes | The name of the branch |
 
-It returns `200` if it succeeds, `404` if the branch to be deleted does not exist
-or `400` for other reasons. In case of an error, an explaining message is provided.
+In case of an error, an explaining message is provided.
 
 ```bash
 curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
@@ -253,7 +254,6 @@ DELETE /projects/:id/repository/merged_branches
 | --------- | ---- | -------- | ----------- |
 | `id`      | integer | yes | The ID of a project |
 
-It returns `200` to indicate deletion of all merged branches was started.
 
 ```bash
 curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/merged_branches"
diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md
index c3a9207a3ae51ecec296abe3162830cc8f19eee5..a3e9c01f335e9dde73a8d5287d9d992c84578bd9 100644
--- a/doc/api/broadcast_messages.md
+++ b/doc/api/broadcast_messages.md
@@ -62,10 +62,6 @@ Example response:
 
 ## Create a broadcast message
 
-Responds with `400 Bad request` when the `message` parameter is missing or the
-`color` or `font` values are invalid, and `201 Created` when the broadcast
-message was successfully created.
-
 ```
 POST /broadcast_messages
 ```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 134263d27b4b1187621e9658b843255d5334288b..16f8e32c82aef78b9aeca38168830c443f6c7670 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -315,10 +315,6 @@ Example response:
 
 Creates a new project issue.
 
-If the operation is successful, a status code of `200` and the newly-created
-issue is returned. If an error occurs, an error number and a message explaining
-the reason is returned.
-
 ```
 POST /projects/:id/issues
 ```
@@ -377,10 +373,6 @@ Example response:
 Updates an existing project issue. This call is also used to mark an issue as
 closed.
 
-If the operation is successful, a code of `200` and the updated issue is
-returned. If an error occurs, an error number and a message explaining the
-reason is returned.
-
 ```
 PUT /projects/:id/issues/:issue_id
 ```
@@ -439,8 +431,6 @@ Example response:
 ## Delete an issue
 
 Only for admins and project owners. Soft deletes the issue in question.
-If the operation is successful, a status code `200` is returned. In case you cannot
-destroy this issue, or it is not present, code `404` is given.
 
 ```
 DELETE /projects/:id/issues/:issue_id
@@ -457,9 +447,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git
 
 ## Move an issue
 
-Moves an issue to a different project. If the operation is successful, a status
-code `201` together with moved issue is returned. If the project, issue, or
-target project is not found, error `404` is returned. If the target project
+Moves an issue to a different project. If the target project
 equals the source project or the user has insufficient permissions to move an
 issue, error `400` together with an explaining error message is returned.
 
@@ -518,11 +506,9 @@ Example response:
 
 ## Subscribe to an issue
 
-Subscribes the authenticated user to an issue to receive notifications. If the
-operation is successful, status code `201` together with the updated issue is
-returned. If the user is already subscribed to the issue, the status code `304`
-is returned. If the project or issue is not found, status code `404` is
-returned.
+Subscribes the authenticated user to an issue to receive notifications. 
+If the user is already subscribed to the issue, the status code `304`
+is returned.
 
 ```
 POST /projects/:id/issues/:issue_id/subscription
@@ -576,10 +562,8 @@ Example response:
 ## Unsubscribe from an issue
 
 Unsubscribes the authenticated user from the issue to not receive notifications
-from it. If the operation is successful, status code `200` together with the
-updated issue is returned. If the user is not subscribed to the issue, the
-status code `304` is returned. If the project or issue is not found, status code
-`404` is returned.
+from it. If the user is not subscribed to the issue, the
+status code `304` is returned.
 
 ```
 DELETE /projects/:id/issues/:issue_id/subscription
@@ -633,8 +617,7 @@ Example response:
 
 ## Create a todo
 
-Manually creates a todo for the current user on an issue. If the request is
-successful, status code `200` together with the created todo is returned. If
+Manually creates a todo for the current user on an issue. If
 there already exists a todo for the user on that issue, status code `304` is
 returned.
 
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 78686fdcad4d13ee67ec6ae3182e42178de4d234..863b28c23b7194dd5aeb5961541734fd56773baf 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -82,9 +82,6 @@ Example response:
 
 Creates a new label for the given repository with the given name and color.
 
-It returns 200 if the label was successfully created, 400 for wrong parameters
-and 409 if the label already exists.
-
 ```
 POST /projects/:id/labels
 ```
@@ -121,10 +118,6 @@ Example response:
 
 Deletes a label with a given name.
 
-It returns 200 if the label was successfully deleted, 400 for wrong parameters
-and 404 if the label does not exist.
-In case of an error, an additional error message is returned.
-
 ```
 DELETE /projects/:id/labels
 ```
@@ -159,10 +152,6 @@ Example response:
 Updates an existing label with new name or new color. At least one parameter
 is required, to update the label.
 
-It returns 200 if the label was successfully deleted, 400 for wrong parameters
-and 404 if the label does not exist.
-In case of an error, an additional error message is returned.
-
 ```
 PUT /projects/:id/labels
 ```
@@ -199,11 +188,9 @@ Example response:
 
 ## Subscribe to a label
 
-Subscribes the authenticated user to a label to receive notifications. If the
-operation is successful, status code `201` together with the updated label is
-returned. If the user is already subscribed to the label, the status code `304`
-is returned. If the project or label is not found, status code `404` is
-returned.
+Subscribes the authenticated user to a label to receive notifications. 
+If the user is already subscribed to the label, the status code `304`
+is returned.
 
 ```
 POST /projects/:id/labels/:label_id/subscription
@@ -237,10 +224,8 @@ Example response:
 ## Unsubscribe from a label
 
 Unsubscribes the authenticated user from a label to not receive notifications
-from it. If the operation is successful, status code `200` together with the
-updated label is returned. If the user is not subscribed to the label, the
-status code `304` is returned. If the project or label is not found, status code
-`404` is returned.
+from it. If the user is not subscribed to the label, the
+status code `304` is returned.
 
 ```
 DELETE /projects/:id/labels/:label_id/subscription
diff --git a/doc/api/members.md b/doc/api/members.md
index 6535e9a7801df3afc35d8a8b6835d29388a86f91..5dcb2a5f60aa00236efd8633e639f62be60cc65a 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -16,8 +16,6 @@ The access levels are defined in the `Gitlab::Access` module. Currently, these l
 
 Gets a list of group or project members viewable by the authenticated user.
 
-Returns `200` if the request succeeds.
-
 ```
 GET /groups/:id/members
 GET /projects/:id/members
@@ -60,8 +58,6 @@ Example response:
 
 Gets a member of a group or project.
 
-Returns `200` if the request succeeds.
-
 ```
 GET /groups/:id/members/:user_id
 GET /projects/:id/members/:user_id
@@ -95,8 +91,6 @@ Example response:
 
 Adds a member to a group or project.
 
-Returns `201` if the request succeeds.
-
 ```
 POST /groups/:id/members
 POST /projects/:id/members
@@ -131,8 +125,6 @@ Example response:
 
 Updates a member of a group or project.
 
-Returns `200` if the request succeeds.
-
 ```
 PUT /groups/:id/members/:user_id
 PUT /projects/:id/members/:user_id
@@ -167,8 +159,6 @@ Example response:
 
 Removes a user from a group or project.
 
-Returns `200` if the request succeeds.
-
 ```
 DELETE /groups/:id/members/:user_id
 DELETE /projects/:id/members/:user_id
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 4cc385e36fe2bd29847266aca33c97c539d62059..12d24543bbe29e70e2703073bf53e7ee3eff0180 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -338,9 +338,6 @@ Parameters:
 }
 ```
 
-If the operation is successful, 200 and the newly created merge request is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
-
 ## Update MR
 
 Updates an existing merge request. You can change the target branch, title, or even close the MR.
@@ -415,14 +412,9 @@ Parameters:
 }
 ```
 
-If the operation is successful, 200 and the updated merge request is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
-
 ## Delete a merge request
 
 Only for admins and project owners. Soft deletes the merge request in question.
-If the operation is successful, a status code `200` is returned. In case you cannot
-destroy this merge request, or it is not present, code `404` is given.
 
 ```
 DELETE /projects/:id/merge_requests/:merge_request_id
@@ -441,15 +433,14 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git
 
 Merge changes submitted with MR using this API.
 
-If the merge succeeds you'll get a `200 OK`.
 
-If it has some conflicts and can not be merged - you'll get a 405 and the error message 'Branch cannot be merged'
+If it has some conflicts and can not be merged - you'll get a `405` and the error message 'Branch cannot be merged'
 
-If merge request is already merged or closed - you'll get a 406 and the error message 'Method Not Allowed'
+If merge request is already merged or closed - you'll get a `406` and the error message 'Method Not Allowed'
 
-If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a 409 and the error message 'SHA does not match HEAD of source branch'
+If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch'
 
-If you don't have permissions to accept this merge request - you'll get a 401
+If you don't have permissions to accept this merge request - you'll get a `401`
 
 ```
 PUT /projects/:id/merge_requests/:merge_request_id/merge
@@ -521,13 +512,11 @@ Parameters:
 
 ## Cancel Merge When Build Succeeds
 
-If successful you'll get `200 OK`.
+If you don't have permissions to accept this merge request - you'll get a `401`
 
-If you don't have permissions to accept this merge request - you'll get a 401
+If the merge request is already merged or closed - you get `405` and error message 'Method Not Allowed'
 
-If the merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
-
-In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error.
+In case the merge request is not set to be merged when the build succeeds, you'll also get a `406` error.
 ```
 PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds
 ```
@@ -671,11 +660,8 @@ Example response when an external issue tracker (e.g. JIRA) is used:
 
 ## Subscribe to a merge request
 
-Subscribes the authenticated user to a merge request to receive notification. If
-the operation is successful, status code `201` together with the updated merge
-request is returned. If the user is already subscribed to the merge request, the
-status code `304` is returned. If the project or merge request is not found,
-status code `404` is returned.
+Subscribes the authenticated user to a merge request to receive notification. If the user is already subscribed to the merge request, the
+status code `304` is returned.
 
 ```
 POST /projects/:id/merge_requests/:merge_request_id/subscription
@@ -748,10 +734,8 @@ Example response:
 ## Unsubscribe from a merge request
 
 Unsubscribes the authenticated user from a merge request to not receive
-notifications from that merge request. If the operation is successful, status
-code `200` together with the updated merge request is returned. If the user is
-not subscribed to the merge request, the status code `304` is returned. If the
-project or merge request is not found, status code `404` is returned.
+notifications from that merge request. If the user is
+not subscribed to the merge request, the status code `304` is returned.
 
 ```
 DELETE /projects/:id/merge_requests/:merge_request_id/subscription
@@ -823,9 +807,8 @@ Example response:
 
 ## Create a todo
 
-Manually creates a todo for the current user on a merge request. If the
-request is successful, status code `200` together with the created todo is
-returned. If there already exists a todo for the user on that merge request,
+Manually creates a todo for the current user on a merge request. 
+If there already exists a todo for the user on that merge request,
 status code `304` is returned.
 
 ```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 58d40eecf3e3b33390d89e531713f6a3c8e16a05..214dfa4068da957cbc9a0d75b2f5e91f008e316a 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -21,7 +21,7 @@ Parameters:
 [
   {
     "id": 302,
-    "body": "Status changed to closed",
+    "body": "closed",
     "attachment": null,
     "author": {
       "id": 1,
@@ -109,8 +109,7 @@ Parameters:
 
 ### Delete an issue note
 
-Deletes an existing note of an issue. On success, this API method returns 200
-and the deleted note. If the note does not exist, the API returns 404.
+Deletes an existing note of an issue.
 
 ```
 DELETE /projects/:id/issues/:issue_id/notes/:note_id
@@ -234,8 +233,7 @@ Parameters:
 
 ### Delete a snippet note
 
-Deletes an existing note of a snippet. On success, this API method returns 200
-and the deleted note. If the note does not exist, the API returns 404.
+Deletes an existing note of a snippet.
 
 ```
 DELETE /projects/:id/snippets/:snippet_id/notes/:note_id
@@ -364,8 +362,7 @@ Parameters:
 
 ### Delete a merge request note
 
-Deletes an existing note of a merge request. On success, this API method returns
-200 and the deleted note. If the note does not exist, the API returns 404.
+Deletes an existing note of a merge request.
 
 ```
 DELETE /projects/:id/merge_requests/:merge_request_id/notes/:note_id
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 6455c333faf63730467eff3c84cb86c2fd0d0563..82351ae688f2e22e3ac74a7302515dbccf26ccf2 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -41,7 +41,8 @@ Example of response
     "started_at": null,
     "finished_at": null,
     "committed_at": null,
-    "duration": null
+    "duration": null,
+    "coverage": "30.0"
   },
   {
     "id": 48,
@@ -64,7 +65,8 @@ Example of response
     "started_at": null,
     "finished_at": null,
     "committed_at": null,
-    "duration": null
+    "duration": null,
+    "coverage": null
   }
 ]
 ```
@@ -110,7 +112,8 @@ Example of response
   "started_at": null,
   "finished_at": "2016-08-11T11:32:35.145Z",
   "committed_at": null,
-  "duration": null
+  "duration": null,
+  "coverage": "30.0"
 }
 ```
 
@@ -155,7 +158,8 @@ Example of response
   "started_at": null,
   "finished_at": null,
   "committed_at": null,
-  "duration": null
+  "duration": null,
+  "coverage": null
 }
 ```
 
@@ -200,7 +204,8 @@ Response:
   "started_at": null,
   "finished_at": "2016-08-11T11:32:35.145Z",
   "committed_at": null,
-  "duration": null
+  "duration": null,
+  "coverage": null
 }
 ```
 
@@ -245,7 +250,8 @@ Response:
   "started_at": null,
   "finished_at": "2016-08-11T11:32:35.145Z",
   "committed_at": null,
-  "duration": null
+  "duration": null,
+  "coverage": null
 }
 ```
 
diff --git a/doc/api/projects.md b/doc/api/projects.md
index de5d3b07c21c74223a6bc40646f2362eaff9127a..132be644b5981c4dfc24f6b9ad1f89b4ec4d06b7 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -624,7 +624,9 @@ Parameters:
 | `user_id` | integer | yes | The user ID of the project owner |
 | `name` | string | yes | The name of the new project |
 | `path` | string | no | Custom repository name for new project. By default generated based on name |
+| `default_branch` | string | no | `master` by default |
 | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) |
+| `default_branch` | string | no | `master` by default |
 | `description` | string | no | Short project description |
 | `issues_enabled` | boolean | no | Enable issues for this project |
 | `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
@@ -644,7 +646,7 @@ Parameters:
 
 ### Edit project
 
-Updates an existing project
+Updates an existing project.
 
 ```
 PUT /projects/:id
@@ -657,6 +659,7 @@ Parameters:
 | `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
 | `name` | string | yes | The name of the project |
 | `path` | string | no | Custom repository name for the project. By default generated based on name |
+| `default_branch` | string | no | `master` by default |
 | `description` | string | no | Short project description |
 | `issues_enabled` | boolean | no | Enable issues for this project |
 | `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
@@ -674,9 +677,6 @@ Parameters:
 | `lfs_enabled` | boolean | no | Enable LFS |
 | `request_access_enabled` | boolean | no | Allow users to request member access |
 
-On success, method returns 200 with the updated project. If parameters are
-invalid, 400 is returned.
-
 ### Fork project
 
 Forks a project into the user namespace of the authenticated user or the one provided.
@@ -694,8 +694,7 @@ Parameters:
 
 ### Star a project
 
-Stars a given project. Returns status code `201` and the project on success and
-`304` if the project is already starred.
+Stars a given project. Returns status code `304` if the project is already starred.
 
 ```
 POST /projects/:id/star
@@ -765,8 +764,7 @@ Example response:
 
 ### Unstar a project
 
-Unstars a given project. Returns status code `200` and the project on success
-and `304` if the project is not starred.
+Unstars a given project. Returns status code `304` if the project is not starred.
 
 ```
 DELETE /projects/:id/star
@@ -837,10 +835,6 @@ Example response:
 Archives the project if the user is either admin or the project owner of this project. This action is
 idempotent, thus archiving an already archived project will not change the project.
 
-Status code 201 with the project as body is given when successful, in case the user doesn't
-have the proper access rights, code 403 is returned. Status 404 is returned if the project
-doesn't exist, or is hidden to the user.
-
 ```
 POST /projects/:id/archive
 ```
@@ -926,10 +920,6 @@ Example response:
 Unarchives the project if the user is either admin or the project owner of this project. This action is
 idempotent, thus unarchiving an non-archived project will not change the project.
 
-Status code 201 with the project as body is given when successful, in case the user doesn't
-have the proper access rights, code 403 is returned. Status 404 is returned if the project
-doesn't exist, or is hidden to the user.
-
 ```
 POST /projects/:id/unarchive
 ```
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 1bc6a24e914656115fcfb3e57974d79627ef7dd1..b8c9eb2c9a8d595a3cc1476f3a71df74c81a6021 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -60,7 +60,7 @@ Parameters:
 
 - `file_path` (required) - Full path to new file. Ex. lib/class.rb
 - `branch_name` (required) - The name of branch
-- `encoding` (optional) - 'text' or 'base64'. Text is default.
+- `encoding` (optional) - Change encoding to 'base64'. Default is text.
 - `author_email` (optional) - Specify the commit author's email address
 - `author_name` (optional) - Specify the commit author's name
 - `content` (required) - File content
@@ -89,7 +89,7 @@ Parameters:
 
 - `file_path` (required) - Full path to file. Ex. lib/class.rb
 - `branch_name` (required) - The name of branch
-- `encoding` (optional) - 'text' or 'base64'. Text is default.
+- `encoding` (optional) - Change encoding to 'base64'. Default is text.
 - `author_email` (optional) - Specify the commit author's email address
 - `author_name` (optional) - Specify the commit author's name
 - `content` (required) - New file content
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index efd23d514bc637350b2b2d52076427e30345e427..3fb8b73be6d475ff47adae4ef8774cd7c89e70d0 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -108,8 +108,7 @@ Example response:
 
 ## Delete system hook
 
-Deletes a system hook. It returns `200 OK` if the hooks is deleted and
-`404 Not Found` if the hook is not found.
+Deletes a system hook.
 
 ---
 
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 398b080e3f672b6f7ffe4ede3806ad8d9aa693d7..14573d48fe4776e80f221117874a0aea96d4ade3 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -40,9 +40,7 @@ Parameters:
 
 ## Get a single repository tag
 
-Get a specific repository tag determined by its name. It returns `200` together
-with the tag information if the tag exists. It returns `404` if the tag does not
-exist.
+Get a specific repository tag determined by its name.
 
 ```
 GET /projects/:id/repository/tags/:tag_name
@@ -124,14 +122,12 @@ Parameters:
 The message will be `nil` when creating a lightweight tag otherwise
 it will contain the annotation.
 
-It returns 201 if the operation succeed. In case of an error,
-405 with an explaining error message is returned.
+In case of an error,
+status code `405` with an explaining error message is returned.
 
 ## Delete a tag
 
-Deletes a tag of a repository with given name. On success, this API method
-returns 200 with the name of the deleted tag. If the tag does not exist, the
-API returns 404.
+Deletes a tag of a repository with given name.
 
 ```
 DELETE /projects/:id/repository/tags/:tag_name
@@ -150,9 +146,8 @@ Parameters:
 
 ## Create a new release
 
-Add release notes to the existing git tag. It returns 201 if the release is
-created successfully. If the tag does not exist, 404 is returned. If there
-already exists a release for the given tag, 409 is returned.
+Add release notes to the existing git tag. If there
+already exists a release for the given tag, status code `409` is returned.
 
 ```
 POST /projects/:id/repository/tags/:tag_name/release
@@ -173,9 +168,7 @@ Parameters:
 
 ## Update a release
 
-Updates the release notes of a given release. It returns 200 if the release is
-successfully updated. If the tag or the release does not exist, it returns 404
-with a proper error message.
+Updates the release notes of a given release.
 
 ```
 PUT /projects/:id/repository/tags/:tag_name/release
diff --git a/doc/api/users.md b/doc/api/users.md
index b38c335490a18b26fa5dc2a7e9ac4e6bcba1efe1..52a6b691610996ba15a7104951122c3483781b5b 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -271,8 +271,8 @@ Parameters:
 - `can_create_group` (optional) - User can create groups - true or false
 - `external` (optional)         - Flags the user as external - true or false(default)
 
-Note, at the moment this method does only return a 404 error,
-even in cases where a 409 (Conflict) would be more appropriate,
+Note, at the moment this method does only return a `404` error,
+even in cases where a `409` (Conflict) would be more appropriate,
 e.g. when renaming the email address to some existing one.
 
 ## User deletion
@@ -449,8 +449,6 @@ Parameters:
 - `title` (required) - new SSH Key's title
 - `key` (required)   - new SSH key
 
-Will return created key with status `201 Created` on success, or `404 Not found` on fail.
-
 ## Delete SSH key for current user
 
 Deletes key owned by currently authenticated user.
@@ -581,8 +579,6 @@ Parameters:
 - `id` (required)    - id of specified user
 - `email` (required) - email address
 
-Will return created email with status `201 Created` on success, or `404 Not found` on fail.
-
 ## Delete email for current user
 
 Deletes email owned by currently authenticated user.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 545cc72682db2f1e92029746ab28cc4b4e05e97d..db236ce2a66723851f1549f13d92a23e57345553 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -21,6 +21,7 @@
 - [CI services (linked docker containers)](services/README.md)
 - [CI/CD pipelines settings](../user/project/pipelines/settings.md)
 - [Review Apps](review_apps/index.md)
+- [Git submodules](git_submodules.md) Using Git submodules in your CI jobs
 
 ## Breaking changes
 
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 89088cf9b838eb4405a3aea4e2cbb60c51356ec1..28141cced3b403280c10f2f904dc465c03fad9c2 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -270,12 +270,16 @@ which can be avoided if a different driver is used, for example `overlay`.
 
 ## Using the GitLab Container Registry
 
-> **Note:**
-This feature requires GitLab 8.8 and GitLab Runner 1.2.
-
-Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../user/project/container_registry.md). For example, if you're using
-docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look:
+> **Notes:**
+- This feature requires GitLab 8.8 and GitLab Runner 1.2.
+- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
+  to pass a personal access token instead of your password in order to login to
+  GitLab's Container Registry.
 
+Once you've built a Docker image, you can push it up to the built-in
+[GitLab Container Registry](../../user/project/container_registry.md). For example,
+if you're using docker-in-docker on your runners, this is how your `.gitlab-ci.yml`
+could look like:
 
 ```yaml
  build:
@@ -354,10 +358,20 @@ deploy:
 ```
 
 Some things you should be aware of when using the Container Registry:
-* You must log in to the container registry before running commands. Putting this in `before_script` will run it before each build job.
-* Using `docker build --pull` makes sure that Docker fetches any changes to base images before building just in case your cache is stale. It takes slightly longer, but means you don’t get stuck without security patches to base images.
-* Doing an explicit `docker pull` before each `docker run` makes sure to fetch the latest image that was just built. This is especially important if you are using multiple runners that cache images locally. Using the git SHA in your image tag makes this less necessary since each build will be unique and you shouldn't ever have a stale image, but it's still possible if you re-build a given commit after a dependency has changed.
-* You don't want to build directly to `latest` in case there are multiple builds happening simultaneously.
+
+- You must log in to the container registry before running commands. Putting
+  this in `before_script` will run it before each build job.
+- Using `docker build --pull` makes sure that Docker fetches any changes to base
+  images before building just in case your cache is stale. It takes slightly
+  longer, but means you don’t get stuck without security patches to base images.
+- Doing an explicit `docker pull` before each `docker run` makes sure to fetch
+  the latest image that was just built. This is especially important if you are
+  using multiple runners that cache images locally. Using the git SHA in your
+  image tag makes this less necessary since each build will be unique and you
+  shouldn't ever have a stale image, but it's still possible if you re-build a
+  given commit after a dependency has changed.
+- You don't want to build directly to `latest` in case there are multiple builds
+  happening simultaneously.
 
 [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
 [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
new file mode 100644
index 0000000000000000000000000000000000000000..1d782200cca1203aecc9cfbd7cad30f984240249
--- /dev/null
+++ b/doc/ci/git_submodules.md
@@ -0,0 +1,86 @@
+# Using Git submodules with GitLab CI
+
+> **Notes:**
+- GitLab 8.12 introduced a new [CI build permissions model][newperms] and you
+  are encouraged to upgrade your GitLab instance if you haven't done already.
+  If you are **not** using GitLab 8.12 or higher, you would need to work your way
+  around submodules in order to access the sources of e.g., `gitlab.com/group/project`
+  with the use of [SSH keys](ssh_keys/README.md).
+- With GitLab 8.12 onward, your permissions are used to evaluate what a CI build
+  can access. More information about how this system works can be found in the
+  [Build permissions model](../user/permissions.md#builds-permissions).
+- The HTTP(S) Git protocol [must be enabled][gitpro] in your GitLab instance.
+
+## Configuring the `.gitmodules` file
+
+If dealing with [Git submodules][gitscm], your project will probably have a file
+named `.gitmodules`.
+
+Let's consider the following example:
+
+1. Your project is located at `https://gitlab.com/secret-group/my-project`.
+1. To checkout your sources you usually use an SSH address like
+   `git@gitlab.com:secret-group/my-project.git`.
+1. Your project depends on `https://gitlab.com/group/project`, which you want
+   to include as a submodule.
+
+If you are using GitLab 8.12+ and your submodule is on the same GitLab server,
+you must update your `.gitmodules` file to use **relative URLs**.
+Since Git allows the usage of relative URLs for your `.gitmodules` configuration,
+this easily allows you to use HTTP(S) for cloning all your CI builds and SSH
+for all your local checkouts. The `.gitmodules` would look like:
+
+```ini
+[submodule "project"]
+  path = project
+  url = ../../group/project.git
+```
+
+The above configuration will instruct Git to automatically deduce the URL that
+should be used when cloning sources. Whether you use HTTP(S) or SSH, Git will use
+that same channel and it will allow to make all your CI builds use HTTP(S)
+(because GitLab CI only uses HTTP(S) for cloning your sources), and all your local
+clones will continue using SSH.
+
+For all other submodules not located on the same GitLab server, use the full
+HTTP(S) protocol URL:
+
+```ini
+[submodule "project-x"]
+  path = project-x
+  url = https://gitserver.com/group/project-x.git
+```
+
+Once `.gitmodules` is correctly configured, you can move on to
+[configuring your `.gitlab-ci.yml`](#using-git-submodules-in-your-ci-jobs).
+
+## Using Git submodules in your CI jobs
+
+There are a few steps you need to take in order to make submodules work
+correctly with your CI builds:
+
+1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file)
+   for the submodules located in the same GitLab server.
+1. Then, use `git submodule sync/update` in `before_script`:
+
+    ```yaml
+    before_script:
+      - git submodule sync --recursive
+      - git submodule update --init --recursive
+    ```
+
+    `--recursive` should be used in either both or none (`sync/update`) depending on
+    whether you have recursive submodules.
+
+The rationale to set the `sync` and `update` in `before_script` is because of
+the way Git submodules work. On a fresh Runner workspace, Git will set the
+submodule URL including the token in `.git/config`
+(or `.git/modules/<submodule>/config`) based on `.gitmodules` and the current
+remote URL. On subsequent builds on the same Runner, `.git/config` is cached
+and already contains a full URL for the submodule, corresponding to the previous
+build, and to **a token from a previous build**. `sync` allows to force updating
+the full URL.
+
+[gitpro]: ../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
+[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules "Git submodules documentation"
+[newperms]: ../user/project/new_ci_build_permissions_model.md
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index cf7c55f75f243743dc499dbc5f8824acacf126f0..efca05af7b8aea01f6d814cc36ab85cdf73addbe 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -6,7 +6,7 @@
 GitLab 8.12 has a completely redesigned build permissions system.
 Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#build-triggers).
 
-Triggers can be used to force a rebuild of a specific branch, tag or commit,
+Triggers can be used to force a rebuild of a specific `ref` (branch or tag)
 with an API call.
 
 ## Add a trigger
@@ -29,6 +29,10 @@ irreversible.
 
 ## Trigger a build
 
+> **Note**:
+Valid refs are only the branches and tags. If you pass a commit SHA as a ref,
+it will not trigger a build.
+
 To trigger a build you need to send a `POST` request to GitLab's API endpoint:
 
 ```
@@ -36,8 +40,8 @@ POST /projects/:id/trigger/builds
 ```
 
 The required parameters are the trigger's `token` and the Git `ref` on which
-the trigger will be performed. Valid refs are the branch, the tag or the commit
-SHA. The `:id` of a project can be found by [querying the API](../../api/projects.md)
+the trigger will be performed. Valid refs are the branch and the tag. The `:id`
+of a project can be found by [querying the API](../../api/projects.md)
 or by visiting the **Triggers** page which provides self-explanatory examples.
 
 When a rebuild is triggered, the information is exposed in GitLab's UI under
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index c5c23b5c0b813bc63884c6eadfa25e17780ce308..e1fb8102b67af08d68444c8222c303d782ed09ad 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -74,6 +74,32 @@ experience, refactors the existing code). Then:
 - If you set the MR to "Merge When Build Succeeds", you should take over
   subsequent revisions for anything that would be spotted after that.
 
+## The right balance
+
+One of the most difficult things during code review is finding the right
+balance in how deep the reviewer can interfere with the code created by a
+reviewee.
+
+- Learning how to find the right balance takes time; that is why we have
+  minibosses that become merge request endbosses after some time spent on
+  reviewing merge requests.
+- Finding bugs and improving code style is important, but thinking about good
+  design is important as well. Building abstractions and good design is what
+  makes it possible to hide complexity and makes future changes easier.
+- Asking the reviewee to change the design sometimes means the complete rewrite
+  of the contributed code. It's usually a good idea to ask another merge
+  request endboss before doing it, but have the courage to do it when you
+  believe it is important.
+- There is a difference in doing things right and doing things right now.
+  Ideally, we should do the former, but in the real world we need the latter as
+  well. A good example is a security fix which should be released as soon as
+  possible. Asking the reviewee to do the major refactoring in the merge
+  request that is an urgent fix should be avoided.
+- Doing things well today is usually better than doing something perfectly
+  tomorrow. Shipping a kludge today is usually worse than doing something well
+  tomorrow. When you are not able to find the right balance, ask other people
+  about their opinion.
+
 ## Credits
 
 Largely based on the [thoughtbot code review guide].
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index b137e6ae82e98e0c02a7c72d84481da2ce44e5d6..fc948a7a116d7695d70bfbeaf64cc5b2651937b8 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -113,6 +113,77 @@ merge request.
   add an alternative text: `[identifier]: https://example.com "Alternative text"`
   that appears when hovering your mouse on a link
 
+### Linking to inline docs
+
+Sometimes it's needed to link to the built-in documentation that GitLab provides
+under `/help`. This is normally done in files inside the `app/views/` directory
+with the help of the `help_page_path` helper method.
+
+In its simplest form, the HAML code to generate a link to the `/help` page is:
+
+```haml
+= link_to 'Help page', help_page_path('user/permissions')
+```
+
+The `help_page_path` contains the path to the document you want to link to with
+the following conventions:
+
+- it is relative to the `doc/` directory in the GitLab repository
+- the `.md` extension must be omitted
+- it must not end with a slash (`/`)
+
+Below are some special cases where should be used depending on the context.
+You can combine one or more of the following:
+
+1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
+   method:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
+    ```
+
+1. **Opening links in a new tab.** This should be the default behavior:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
+    ```
+
+1. **Linking to a circle icon.** Usually used in settings where a long
+   description cannot be used, like near checkboxes. You can basically use
+   any font awesome icon, but prefer the `question-circle`:
+
+    ```haml
+    = link_to icon('question-circle'), help_page_path('user/permissions')
+    ```
+
+1. **Using a button link.** Useful in places where text would be out of context
+   with the rest of the page layout:
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions'),  class: 'btn btn-info'
+    ```
+
+1. **Underlining a link.**
+
+    ```haml
+    = link_to 'Help page', help_page_path('user/permissions'), class: 'underlined-link'
+    ```
+
+1. **Using links inline of some text.**
+
+    ```haml
+    Description to #{link_to 'Help page', help_page_path('user/permissions')}.
+    ```
+
+1. **Adding a period at the end of the sentence.** Useful when you don't want
+   the period to be part of the link:
+
+    ```haml
+    = succeed '.' do
+      Learn more in the
+      = link_to 'Help page', help_page_path('user/permissions')
+    ```
+
 ## Images
 
 - Place images in a separate directory named `img/` in the same directory where
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 7bfc9cb361f3373c9164a6e4513a960778dd5b09..0f78e8238af6bf8ce70ff8a3fbf4bdd54bb73b58 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -141,51 +141,3 @@ in an initializer._
 ### Further reading
 
 - Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting)
-
-## ID-based CSS selectors need to be a bit more specific
-
-Normally, because HTML `id` attributes need to be unique to the page, it's
-perfectly fine to write some JavaScript like the following:
-
-```javascript
-$('#js-my-selector').hide();
-```
-
-However, there's a feature of GitLab's Markdown processing that [automatically
-adds anchors to header elements][ToC Processing], with the `id` attribute being
-automatically generated based on the content of the header.
-
-Unfortunately, this feature makes it possible for user-generated content to
-create a header element with the same `id` attribute we're using in our
-selector, potentially breaking the JavaScript behavior. A user could break the
-above example with the following Markdown:
-
-```markdown
-## JS My Selector
-```
-
-Which gets converted to the following HTML:
-
-```html
-<h2>
-  <a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a>
-  JS My Selector
-</h2>
-```
-
-[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37
-
-### Solution
-
-The current recommended fix for this is to make our selectors slightly more
-specific:
-
-```javascript
-$('div#js-my-selector').hide();
-```
-
-### Further reading
-
-- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908)
-- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023)
-- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024)
diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md
index b7e6387838e9919a0a878914580741cc5b532916..568dedf1669bf9ae39682da61c9c637a592cf56b 100644
--- a/doc/development/limit_ee_conflicts.md
+++ b/doc/development/limit_ee_conflicts.md
@@ -143,109 +143,162 @@ to resolve when you add the indentation to the equation.
 For instance this kind of thing:
 
 ```haml
+.form-group.detail-page-description
+  = form.label :description, 'Description', class: 'control-label'
+  .col-sm-10
+    = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
+      = render 'projects/zen', f: form, attr: :description,
+                               classes: 'note-textarea',
+                               placeholder: "Write a comment or drag your files here...",
+                               supports_slash_commands: !issuable.persisted?
+      = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted?
+      .clearfix
+      .error-alert
+- if issuable.is_a?(Issue)
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      .checkbox
+        = form.label :confidential do
+          = form.check_box :confidential
+          This issue is confidential and should only be visible to team members with at least Reporter access.
 - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
   - has_due_date = issuable.has_attribute?(:due_date)
   %hr
   .row
     %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
       .form-group.issue-assignee
-        = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+        = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
         .col-sm-10{ class: ("col-lg-8" if has_due_date) }
           .issuable-form-select-holder
             - if issuable.assignee_id
-              = f.hidden_field :assignee_id
+              = form.hidden_field :assignee_id
             = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
               placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
       .form-group.issue-milestone
-        = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
+        = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
         .col-sm-10{ class: ("col-lg-8" if has_due_date) }
           .issuable-form-select-holder
             = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
       .form-group
         - has_labels = @labels && @labels.any?
-        = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
-        = f.hidden_field :label_ids, multiple: true, value: ''
+        = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
+        = form.hidden_field :label_ids, multiple: true, value: ''
         .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
           .issuable-form-select-holder
-            = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
-
+            = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
       - if issuable.respond_to?(:weight)
+        - weight_options = Issue.weight_options
+        - weight_options.delete(Issue::WEIGHT_ALL)
+        - weight_options.delete(Issue::WEIGHT_ANY)
         .form-group
-          = f.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
+          = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
             Weight
           .col-sm-10{ class: ("col-lg-8" if has_due_date) }
-            = f.select :weight, issues_weight_options(issuable.weight, edit: true), { include_blank: true },
-              { class: 'select2 js-select2', data: { placeholder: "Select weight" }}
-
+            .issuable-form-select-holder
+              - if issuable.weight
+                = form.hidden_field :weight
+              = dropdown_tag(issuable.weight || "Weight", options: { title: "Select weight", toggle_class: 'js-weight-select js-issuable-form-weight', dropdown_class: "dropdown-menu-selectable dropdown-menu-weight",
+                placeholder: "Search weight", data: { field_name: "#{issuable.class.model_name.param_key}[weight]" , default_label: "Weight" } }) do
+                %ul
+                  - weight_options.each do |weight|
+                    %li
+                      %a{href: "#", data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight)}
+                        = weight
     - if has_due_date
       .col-lg-6
         .form-group
-          = f.label :due_date, "Due date", class: "control-label"
+          = form.label :due_date, "Due date", class: "control-label"
           .col-sm-10
             .issuable-form-select-holder
-              = f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
+              = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
 ```
 
 could be simplified by using partials:
 
 ```haml
-= render 'metadata_form', issuable: issuable
+= render 'shared/issuable/form/description', issuable: issuable, form: form
+
+- if issuable.respond_to?(:confidential)
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      .checkbox
+        = form.label :confidential do
+          = form.check_box :confidential
+          This issue is confidential and should only be visible to team members with at least Reporter access.
+
+= render 'shared/issuable/form/metadata', issuable: issuable, form: form
 ```
 
-and then the `_metadata_form.html.haml` could be as follows:
+and then the `app/views/shared/issuable/form/_metadata.html.haml` could be as follows:
 
 ```haml
+- issuable = local_assigns.fetch(:issuable)
+
 - return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
 
 - has_due_date = issuable.has_attribute?(:due_date)
+- has_labels = @labels && @labels.any?
+- form = local_assigns.fetch(:form)
+
 %hr
 .row
   %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
     .form-group.issue-assignee
-      = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+      = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
       .col-sm-10{ class: ("col-lg-8" if has_due_date) }
         .issuable-form-select-holder
           - if issuable.assignee_id
-            = f.hidden_field :assignee_id
+            = form.hidden_field :assignee_id
           = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
-            placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
+            placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
     .form-group.issue-milestone
-      = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
+      = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
       .col-sm-10{ class: ("col-lg-8" if has_due_date) }
         .issuable-form-select-holder
           = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
     .form-group
       - has_labels = @labels && @labels.any?
-      = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
-      = f.hidden_field :label_ids, multiple: true, value: ''
+      = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
+      = form.hidden_field :label_ids, multiple: true, value: ''
       .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
         .issuable-form-select-holder
-          = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
+          = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
 
-    = render 'weight_form', issuable: issuable, has_due_date: has_due_date
+    = render "shared/issuable/form/weight", issuable: issuable, form: form
 
   - if has_due_date
     .col-lg-6
       .form-group
-        = f.label :due_date, "Due date", class: "control-label"
+        = form.label :due_date, "Due date", class: "control-label"
         .col-sm-10
           .issuable-form-select-holder
-            = f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
+            = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
 ```
 
-and then the `_weight_form.html.haml` could be as follows:
+and then the `app/views/shared/issuable/form/_weight.html.haml` could be as follows:
 
 ```haml
+- issuable = local_assigns.fetch(:issuable)
+
 - return unless issuable.respond_to?(:weight)
 
 - has_due_date = issuable.has_attribute?(:due_date)
+- form = local_assigns.fetch(:form)
 
 .form-group
-  = f.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
+  = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
     Weight
   .col-sm-10{ class: ("col-lg-8" if has_due_date) }
-    = f.select :weight, issues_weight_options(issuable.weight, edit: true), { include_blank: true },
-      { class: 'select2 js-select2', data: { placeholder: "Select weight" }}
+    .issuable-form-select-holder
+      - if issuable.weight
+        = form.hidden_field :weight
+
+      = weight_dropdown_tag(issuable, toggle_class: 'js-issuable-form-weight') do
+        %ul
+          - Issue.weight_options.each do |weight|
+            %li
+              %a{ href: '#', data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight) }
+                = weight
 ```
 
 Note:
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 8337c2d9cb35b23ce683058518491acbbec2c056..5c43ae7b79ae26cf9bf9cd26480556ae3b80ff5f 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -101,6 +101,116 @@ In short:
 5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's
    `Benchmark` module.
 
+## Profiling
+
+By collecting snapshots of process state at regular intervals, profiling allows
+you to see where time is spent in a process. The [StackProf](https://github.com/tmm1/stackprof)
+gem is included in GitLab's development environment, allowing you to investigate
+the behaviour of suspect code in detail.
+
+It's important to note that profiling an application *alters its performance*,
+and will generally be done *in an unrepresentative environment*. In particular,
+a method is not necessarily troublesome just because it is executed many times,
+or takes a long time to execute. Profiles are tools you can use to better
+understand what is happening in an application - using that information wisely
+is up to you!
+
+Keeping that in mind, to create a profile, identify (or create) a spec that
+exercises the troublesome code path, then run it using the `bin/rspec-stackprof`
+helper, e.g.:
+
+```
+$ LIMIT=10 bin/rspec-stackprof spec/policies/project_policy_spec.rb
+8/8 |====== 100 ======>| Time: 00:00:18
+
+Finished in 18.19 seconds (files took 4.8 seconds to load)
+8 examples, 0 failures
+
+==================================
+ Mode: wall(1000)
+ Samples: 17033 (5.59% miss rate)
+ GC: 1901 (11.16%)
+==================================
+    TOTAL    (pct)     SAMPLES    (pct)     FRAME
+     6000  (35.2%)        2566  (15.1%)     Sprockets::Cache::FileStore#get
+     2018  (11.8%)         888   (5.2%)     ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache
+     1338   (7.9%)         640   (3.8%)     ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#execute
+     3125  (18.3%)         394   (2.3%)     Sprockets::Cache::FileStore#safe_open
+      913   (5.4%)         301   (1.8%)     ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache
+      288   (1.7%)         288   (1.7%)     ActiveRecord::Attribute#initialize
+      246   (1.4%)         246   (1.4%)     Sprockets::Cache::FileStore#safe_stat
+      295   (1.7%)         193   (1.1%)     block (2 levels) in class_attribute
+      187   (1.1%)         187   (1.1%)     block (4 levels) in class_attribute
+```
+
+You can limit the specs that are run by passing any arguments `rspec` would
+normally take.
+
+The output is sorted by the `Samples` column by default. This is the number of
+samples taken where the method is the one currently being executed. The `Total`
+column shows the number of samples taken where the method, or any of the methods
+it calls, were being executed.
+
+To create a graphical view of the call stack:
+
+```shell
+$ stackprof tmp/project_policy_spec.rb.dump --graphviz > project_policy_spec.dot
+$ dot -Tsvg project_policy_spec.dot > project_policy_spec.svg
+```
+
+To load the profile in [kcachegrind](https://kcachegrind.github.io/):
+
+```
+$ stackprof tmp/project_policy_spec.dump --callgrind > project_policy_spec.callgrind
+$ kcachegrind project_policy_spec.callgrind # Linux
+$ qcachegrind project_policy_spec.callgrind # Mac
+```
+
+It may be useful to zoom in on a specific method, e.g.:
+
+```
+$ stackprof tmp/project_policy_spec.rb.dump --method warm_asset_cache
+TestEnv#warm_asset_cache (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/spec/support/test_env.rb:164)
+  samples:     0 self (0.0%)  /   6288 total (36.9%)
+  callers:
+    6288  (  100.0%)  block (2 levels) in <top (required)>
+  callees (6288 total):
+    6288  (  100.0%)  Capybara::RackTest::Driver#visit
+  code:
+                                  |   164  |   def warm_asset_cache
+                                  |   165  |     return if warm_asset_cache?
+                                  |   166  |     return unless defined?(Capybara)
+                                  |   167  |
+ 6288   (36.9%)                   |   168  |     Capybara.current_session.driver.visit '/'
+                                  |   169  |   end
+$ stackprof tmp/project_policy_spec.rb.dump --method BasePolicy#abilities
+BasePolicy#abilities (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/app/policies/base_policy.rb:79)
+  samples:     0 self (0.0%)  /     50 total (0.3%)
+  callers:
+      25  (   50.0%)  BasePolicy.abilities
+      25  (   50.0%)  BasePolicy#collect_rules
+  callees (50 total):
+      25  (   50.0%)  ProjectPolicy#rules
+      25  (   50.0%)  BasePolicy#collect_rules
+  code:
+                                  |    79  |   def abilities
+                                  |    80  |     return RuleSet.empty if @user && @user.blocked?
+                                  |    81  |     return anonymous_abilities if @user.nil?
+   50    (0.3%)                   |    82  |     collect_rules { rules }
+                                  |    83  |   end
+```
+
+Since the profile includes the work done by the test suite as well as the
+application code, these profiles can be used to investigate slow tests as well.
+However, for smaller runs (like this example), this means that the cost of
+setting up the test suite will tend to dominate.
+
+It's also possible to modify the application code in-place to output profiles
+whenever a particular code path is triggered without going through the test
+suite first. See the
+[StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md)
+for details.
+
 ## Importance of Changes
 
 When working on performance improvements, it's important to always ask yourself
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 6106e47daa0a90740674b9de4326d17d07f72336..dbea6b9c9aa76c790bfd252fd9042e4d224327d8 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -63,6 +63,8 @@ the command line via `bundle exec teaspoon`, or via a web browser at
 - Use `.method` to describe class methods and `#method` to describe instance
   methods.
 - Use `context` to test branching logic.
+- Use multi-line `do...end` blocks for `before` and `after`, even when it would
+  fit on a single line.
 - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
 - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
 - Don't supply the `:each` argument to hooks since it's the default.
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index 764c33557144481b07bee6ba2c8ed833a45471fa..8e51edd23efc03d72f8b61301638f841c3da8f17 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -43,7 +43,7 @@ Primary links are blue in their rest state. Secondary links (such as the time st
 
 #### Hover
 
-An underline should always be added on hover. A gray link becomes blue on hover.
+On hover, an underline should be added and the color should change. Both the primary and secondary link should become the darker blue color on hover.
 
 #### Focus
 
@@ -72,9 +72,7 @@ Secondary buttons are for alternative commands. They should be conveyed by a bu
 ### Icon and text treatment
 Text should be in sentence case, where only the first word is capitalized. "Create issue" is correct, not "Create Issue". Buttons should only contain an icon or a text, not both.
 
->>>
-TODO: Rationalize this. Ensure that we still believe this.
->>>
+> TODO: Rationalize this. Ensure that we still believe this.
 
 ### Colors
 Follow the color guidance on the [basics](basics.md#color) page. The default color treatment is the white/grey button.
@@ -85,13 +83,13 @@ Follow the color guidance on the [basics](basics.md#color) page. The default col
 
 Dropdowns are used to allow users to choose one (or many) options from a list of options. If this list of options is more 20, there should generally be a way to search through and filter the options (see the complex filter dropdowns below.)
 
->>>
-TODO: Will update this section when the new filters UI is implemented.
->>>
+> TODO: Will update this section when the new filters UI is implemented.
 
 ![Dropdown states](img/components-dropdown.png)
 
+### Max size
 
+The max height for dropdowns should target **10-15 items**. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height.
 
 ---
 
@@ -164,9 +162,7 @@ Cover blocks are generally used to create a heading element for a page, such as
 
 ## Panels
 
->>>
-TODO: Catalog how we are currently using panels and rationalize how they relate to alerts
->>>
+> TODO: Catalog how we are currently using panels and rationalize how they relate to alerts
 
 ![Panels](img/components-panels.png)
 
@@ -174,9 +170,7 @@ TODO: Catalog how we are currently using panels and rationalize how they relate
 
 ## Alerts
 
->>>
-TODO: Catalog how we are currently using alerts
->>>
+> TODO: Catalog how we are currently using alerts
 
 ![Alerts](img/components-alerts.png)
 
diff --git a/doc/development/ux_guide/img/components-anchorlinks.png b/doc/development/ux_guide/img/components-anchorlinks.png
index 7dd6a8a387672648bec4afbaa798f064bf6d2002..4a9c730566c698c7b2eb421f25e5665442b024e1 100644
Binary files a/doc/development/ux_guide/img/components-anchorlinks.png and b/doc/development/ux_guide/img/components-anchorlinks.png differ
diff --git a/doc/install/installation.md b/doc/install/installation.md
index b5e2640b3806cd616e88d0fd95a0720aacf8349c..4b0c585e51e3c6803bac67fc34a6c67851acbb84 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -176,16 +176,16 @@ We recommend using a PostgreSQL database. For MySQL check the
     sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
     ```
 
-1. Create the GitLab production database and grant all privileges on database:
+1. Create the `pg_trgm` extension (required for GitLab 8.6+):
 
     ```bash
-    sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
+    sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
     ```
 
-1. Create the `pg_trgm` extension (required for GitLab 8.6+):
+1. Create the GitLab production database and grant all privileges on database:
 
     ```bash
-    sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
+    sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
     ```
 
 1. Try connecting to the new database with the new user:
@@ -396,15 +396,13 @@ GitLab Shell is an SSH access and repository management software developed speci
 
 ### Install gitlab-workhorse
 
-GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
-If you are not using Linux you may have to run `gmake` instead of
-`make` below.
+GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
+following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse`
+which is the recommended location.
 
-    cd /home/git
-    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
-    cd gitlab-workhorse
-    sudo -u git -H git checkout v1.0.0
-    sudo -u git -H make
+    cd /home/git/gitlab
+
+    sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
 
 ### Initialize Database and Activate Advanced Features
 
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 556d71b8b7643936db8688a2437647c4189cc12f..9122dc62e39f984ed9d5e16b7d3fb5edc93a7ede 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -123,7 +123,7 @@ To be able to access repositories on Bitbucket, GitLab will automatically
 register your public key with Bitbucket as a deploy key for the repositories to
 be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which
 translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to
-`/home/git/.ssh/bitbucket_rsa.pub` for installations from source.
+`/home/git/.ssh/bitbucket_rsa` for installations from source.
 
 ---
 
@@ -199,7 +199,7 @@ Your GitLab server is now able to connect to Bitbucket over SSH. You should be
 able to see the "Import projects from Bitbucket" option on the New Project page
 enabled.
 
-## Acknowledgemts
+## Acknowledgements
 
 Special thanks to the writer behind the following article:
 
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
new file mode 100644
index 0000000000000000000000000000000000000000..e2f136bcc3594efda3b1e51b790e49327ce896a6
--- /dev/null
+++ b/doc/integration/jira.md
@@ -0,0 +1,3 @@
+# GitLab JIRA integration
+
+This document was moved to [project_services/jira](../project_services/jira.md).
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 7484bc2295ec12bb84b4fb70b81aeae58fed90f1..f42bb6a81a235a7ecb694a9d7ea8193d2195588a 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -88,7 +88,7 @@ It uses the [Fog library](http://fog.io/) to perform the upload.
 In the example below we use Amazon S3 for storage, but Fog also lets you use
 [other storage providers](http://fog.io/storage/). GitLab
 [imports cloud drivers](https://gitlab.com/gitlab-org/gitlab-ce/blob/30f5b9a5b711b46f1065baf755e413ceced5646b/Gemfile#L88)
-for AWS, Azure, Google, OpenStack Swift and Rackspace as well. A local driver is
+for AWS, OpenStack Swift and Rackspace as well. A local driver is
 [also available](#uploading-to-locally-mounted-shares).
 
 For omnibus packages:
@@ -353,7 +353,7 @@ restore:
 
 ```shell
 # This command will overwrite the contents of your GitLab database!
-sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
+sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186_2014_02_27
 ```
 
 Restart and check GitLab:
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index c0084d9d59c21add44506acc5c56fa000dd0e592..8c0d3f78b55b92c15fa496d1035c594ffaa18a70 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -166,7 +166,7 @@ See [smtp_settings.rb.sample] as an example.
 Ensure you're still up-to-date with the latest init script changes:
 
     sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-    
+
 For Ubuntu 16.04.1 LTS:
 
     sudo systemctl daemon-reload
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index 46ea19d11d0c422c92163c9647b3dc06a32ec9ef..a0e895773ce002c146480334ab2f3a25c2dff376 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -84,7 +84,7 @@ GitLab 8.1.
 ```bash
 cd /home/git/gitlab-workhorse
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v1.0.0
+sudo -u git -H git checkout v1.0.1
 sudo -u git -H make
 ```
 
diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md
new file mode 100644
index 0000000000000000000000000000000000000000..576b943b98c5e4f629f123930bfa29650eb15485
--- /dev/null
+++ b/doc/update/8.14-to-8.15.md
@@ -0,0 +1,202 @@
+# From 8.14 to 8.15
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+    sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+We will continue supporting Ruby < 2.3 for the time being but we recommend you
+upgrade to Ruby 2.3 if you're running a source installation, as this is the same
+version that ships with our Omnibus package.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
+echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711  ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
+cd ruby-2.3.1
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-15-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-15-stable-ee
+```
+
+### 5. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v4.0.0
+```
+
+### 6. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
+```
+
+### 7. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+```
+
+### 8. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-13-stable:config/gitlab.yml.example origin/8-15-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
+the GitLab server during `git gc`.
+
+```sh
+sudo -u git -H git config --global repack.writeBitmaps true
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-15-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-15-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+    sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+For Ubuntu 16.04.1 LTS:
+
+    sudo systemctl daemon-reload
+
+### 9. Start application
+
+    sudo service gitlab start
+    sudo service nginx restart
+
+### 10. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+    sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.14)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.13 to 8.14](8.13-to-8.14.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 60729316cde31583f227f2e86ad9f8932eff3c56..e98c40ca4c0a9ceb94a926bfba02d6954d2005ce 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -45,10 +45,9 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca
 ### 4. Update gitlab-workhorse to the corresponding version
 
 ```bash
-cd /home/git/gitlab-workhorse
-sudo -u git -H git fetch
-sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
-sudo -u git -H make
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
 ```
 
 ### 5. Install libs, migrations, etc.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index cea78864df21d1d9a3083bfcaf9dd0dffc630ed7..39fe2409a295beb00ad58e3d5d64aa0c7ed4423d 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -141,10 +141,9 @@ instance and project. In addition, all admins can use the admin interface under
 | See events in the system              |                 |             |          | ✓      |
 | Admin interface                       |                 |             |          | ✓      |
 
-### Build permissions
-
-> Changed in GitLab 8.12.
+### Builds permissions
 
+>**Note:**
 GitLab 8.12 has a completely redesigned build permissions system.
 Read all about the [new model and its implications][new-mod].
 
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index b205fea2c40ca562b20870a64d19676b9e1a18df..47a4a3f85d0e722d45cae83feef7daaaf468d63e 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -4,13 +4,15 @@
 
 ---
 
-> **Note**
-Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
-versions earlier than 1.10.
->
-This document is about the user guide. To learn how to enable GitLab Container
-Registry across your GitLab instance, visit the
-[administrator documentation](../../administration/container_registry.md).
+>**Notes:**
+- Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
+  versions earlier than 1.10.
+- This document is about the user guide. To learn how to enable GitLab Container
+  Registry across your GitLab instance, visit the
+  [administrator documentation](../../administration/container_registry.md).
+- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
+  to pass a personal access token instead of your password in order to login to
+  GitLab's Container Registry.
 
 With the Docker Container Registry integrated into GitLab, every project can
 have its own space to store its Docker images.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 60b7bec2ba7277c7111812a5c174dc8720037f71..320faff65c5216c19345155708500a69cd9e0be1 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -34,9 +34,9 @@ as created be the pusher (local push or via the UI) and any build created in thi
 pipeline will have the permissions of the pusher.
 
 This allows us to make it really easy to evaluate the access for all projects
-that have Git submodules or are using container images that the pusher would
-have access too. **The permission is granted only for time that build is running.
-The access is revoked after the build is finished.**
+that have [Git submodules][gitsub] or are using container images that the pusher
+would have access too. **The permission is granted only for time that build is
+running. The access is revoked after the build is finished.**
 
 ## Types of users
 
@@ -141,7 +141,7 @@ with GitLab 8.12.
 With the new build permissions model, there is now an easy way to access all
 dependent source code in a project. That way, we can:
 
-1. Access a project's Git submodules
+1. Access a project's [Git submodules][gitsub]
 1. Access private container images
 1. Access project's and submodule LFS objects
 
@@ -179,119 +179,25 @@ As a user:
 
 ### Git submodules
 
->
-It often happens that while working on one project, you need to use another
-project from within it; perhaps it’s a library that a third party developed or
-you’re developing a project separately and are using it in multiple parent
-projects.
-A common issue arises in these scenarios: you want to be able to treat the two
-projects as separate yet still be able to use one from within the other.
->
-_Excerpt from the [Git website][git-scm] about submodules._
-
-If dealing with submodules, your project will probably have a file named
-`.gitmodules`. And this is how it usually looks like:
-
-```
-[submodule "tools"]
-	path = tools
-	url = git@gitlab.com/group/tools.git
-```
-
-> **Note:**
-If you are **not** using GitLab 8.12 or higher, you would need to work your way
-around this issue in order to access the sources of `gitlab.com/group/tools`
-(e.g., use [SSH keys](../ssh_keys/README.md)).
->
-With GitLab 8.12 onward, your permissions are used to evaluate what a CI build
-can access. More information about how this system works can be found in the
-[Build permissions model](../../user/permissions.md#builds-permissions).
-
-To make use of the new changes, you have to update your `.gitmodules` file to
-use a relative URL.
-
-Let's consider the following example:
-
-1. Your project is located at `https://gitlab.com/secret-group/my-project`.
-1. To checkout your sources you usually use an SSH address like
-   `git@gitlab.com:secret-group/my-project.git`.
-1. Your project depends on `https://gitlab.com/group/tools`.
-1. You have the `.gitmodules` file with above content.
-
-Since Git allows the usage of relative URLs for your `.gitmodules` configuration,
-this easily allows you to use HTTP for cloning all your CI builds and SSH
-for all your local checkouts.
-
-For example, if you change the `url` of your `tools` dependency, from
-`git@gitlab.com/group/tools.git` to `../../group/tools.git`, this will instruct
-Git to automatically deduce the URL that should be used when cloning sources.
-Whether you use HTTP or SSH, Git will use that same channel and it will allow
-to make all your CI builds use HTTPS (because GitLab CI uses HTTPS for cloning
-your sources), and all your local clones will continue using SSH.
-
-Given the above explanation, your `.gitmodules` file should eventually look
-like this:
-
-```
-[submodule "tools"]
-	path = tools
-	url = ../../group/tools.git
-```
-
-However, you have to explicitly tell GitLab CI to clone your submodules as this
-is not done automatically. You can achieve that by adding a `before_script`
-section to your `.gitlab-ci.yml`:
-
-```
-before_script:
-  - git submodule update --init --recursive
-
-test:
-  script:
-    - run-my-tests
-```
-
-This will make GitLab CI initialize (fetch) and update (checkout) all your
-submodules recursively.
-
-If Git does not use the newly added relative URLs but still uses your old URLs,
-you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`,
-prior to running `git submodule update --init --recursive`. This transfers the
-changes from your `.gitmodules` file into the `.git` folder, which is kept by
-runners between runs.
-
-In case your environment or your Docker image doesn't have Git installed,
-you have to either ask your Administrator or install the missing dependency
-yourself:
-
-```
-# Debian / Ubuntu
-before_script:
-  - apt-get update -y
-  - apt-get install -y git-core
-  - git submodule update --init --recursive
-
-# CentOS / RedHat
-before_script:
-  - yum install git
-  - git submodule update --init --recursive
-
-# Alpine
-before_script:
-  - apk add -U git
-  - git submodule update --init --recursive
-```
+To properly configure submodules with GitLab CI, read the
+[Git submodules documentation][gitsub].
 
 ### Container Registry
 
 With the update permission model we also extended the support for accessing
 Container Registries for private projects.
 
-> **Note:**
-As GitLab Runner 1.6 doesn't yet incorporate the introduced changes for
-permissions, this makes the `image:` directive to not work with private projects
-automatically. The manual configuration by an Administrator is required to use
-private images. We plan to remove that limitation in one of the upcoming releases.
+> **Notes:**
+- GitLab Runner versions prior to 1.8 don't incorporate the introduced changes
+  for permissions. This makes the `image:` directive to not work with private
+  projects automatically and it needs to be configured manually on Runner's host
+  with a predefined account (for example administrator's personal account with
+  access token created explicitly for this purpose). This issue is resolved with
+  latest changes in GitLab Runner 1.8 which receives GitLab credentials with
+  build data.
+- Starting with GitLab 8.12, if you have 2FA enabled in your account, you need
+  to pass a personal access token instead of your password in order to login to
+  GitLab's Container Registry.
 
 Your builds can access all container images that you would normally have access
 to. The only implication is that you can push to the Container Registry of the
@@ -310,7 +216,7 @@ test:
 [build permissions]: ../permissions.md#builds-permissions
 [comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302
 [ext]: ../permissions.md#external-users
-[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
+[gitsub]: ../../ci/git_submodules.md
 [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
 [triggers]: ../../ci/triggers/README.md
 [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
diff --git a/doc/web_hooks/ssl.png b/doc/web_hooks/ssl.png
index a552888ed96a2ffd47efe081796cbaa9485a2423..21ddec4ebdf73209bc64de8dedc8436242c39a61 100644
Binary files a/doc/web_hooks/ssl.png and b/doc/web_hooks/ssl.png differ
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 33c1a79d59cb4e671478968f296d1eeffda423a7..1659dd1f6cb066c1282dcf513e8deea4fc064a74 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -1,17 +1,21 @@
 # Webhooks
 
-_**Note:**
-Starting from GitLab 8.5:_
+>**Note:**
+Starting from GitLab 8.5:
+- the `repository` key is deprecated in favor of the `project` key
+- the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key
+- the `project.http_url` key is deprecated in favor of the `project.git_http_url` key
 
-- _the `repository` key is deprecated in favor of the `project` key_
-- _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_
-- _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_
+Project webhooks allow you to trigger a URL if for example new code is pushed or
+a new issue is created. You can configure webhooks to listen for specific events
+like pushes, issues or merge requests. GitLab will send a POST request with data
+to the webhook URL.
 
-Project webhooks allow you to trigger an URL if new code is pushed or a new issue is created.
+Webhooks can be used to update an external issue tracker, trigger CI builds,
+update a backup mirror, or even deploy to your production server.
 
-You can configure webhooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the webhook URL.
-
-Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
+Navigate to the webhooks page by choosing **Webhooks** from your project's
+settings which can be found under the wheel icon in the upper right corner.
 
 ## Webhook endpoint tips
 
@@ -26,21 +30,27 @@ GitLab webhooks keep in mind the following things:
     you are writing a low-level hook this is important to remember.
 -   GitLab ignores the HTTP status code returned by your endpoint.
 
-## Secret Token
+## Secret token
 
-If you specify a secret token, it will be sent with the hook request in the `X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify that the request is legitimate.
+If you specify a secret token, it will be sent with the hook request in the
+`X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify
+that the request is legitimate.
 
-## SSL Verification
+## SSL verification
 
 By default, the SSL certificate of the webhook endpoint is verified based on
-an internal list of Certificate Authorities,
-which means the certificate cannot be self-signed.
+an internal list of Certificate Authorities, which means the certificate cannot
+be self-signed.
 
 You can turn this off in the webhook settings in your GitLab projects.
 
 ![SSL Verification](ssl.png)
 
-## Push events
+## Events
+
+Below are described the supported events.
+
+### Push events
 
 Triggered when you push to the repository except when pushing tags.
 
@@ -121,7 +131,7 @@ X-Gitlab-Event: Push Hook
 }
 ```
 
-## Tag events
+### Tag events
 
 Triggered when you create (or delete) tags to the repository.
 
@@ -174,7 +184,7 @@ X-Gitlab-Event: Tag Push Hook
 }
 ```
 
-## Issues events
+### Issues events
 
 Triggered when a new issue is created or an existing issue was updated/closed/reopened.
 
@@ -240,7 +250,7 @@ X-Gitlab-Event: Issue Hook
   }
 }
 ```
-## Comment events
+### Comment events
 
 Triggered when a new comment is made on commits, merge requests, issues, and code snippets.
 The note data will be stored in `object_attributes` (e.g. `note`, `noteable_type`). The
@@ -253,7 +263,7 @@ Valid target types:
 3. `issue`
 4. `snippet`
 
-### Comment on commit
+#### Comment on commit
 
 **Request header**:
 
@@ -332,7 +342,7 @@ X-Gitlab-Event: Note Hook
 }
 ```
 
-### Comment on merge request
+#### Comment on merge request
 
 **Request header**:
 
@@ -459,7 +469,7 @@ X-Gitlab-Event: Note Hook
 }
 ```
 
-### Comment on issue
+#### Comment on issue
 
 **Request header**:
 
@@ -534,7 +544,7 @@ X-Gitlab-Event: Note Hook
 }
 ```
 
-### Comment on code snippet
+#### Comment on code snippet
 
 **Request header**:
 
@@ -607,7 +617,7 @@ X-Gitlab-Event: Note Hook
 }
 ```
 
-## Merge request events
+### Merge request events
 
 Triggered when a new merge request is created, an existing merge request was updated/merged/closed or a commit is added in the source branch.
 
@@ -699,7 +709,7 @@ X-Gitlab-Event: Merge Request Hook
 }
 ```
 
-## Wiki Page events
+### Wiki Page events
 
 Triggered when a wiki page is created or edited.
 
@@ -737,9 +747,9 @@ X-Gitlab-Event: Wiki Page Hook
   },
   "wiki": {
     "web_url": "http://example.com/root/awesome-project/wikis/home",
-    "git_ssh_url": "git@example.com:root/awesome-project.wiki.git", 
-    "git_http_url": "http://example.com/root/awesome-project.wiki.git", 
-    "path_with_namespace": "root/awesome-project.wiki", 
+    "git_ssh_url": "git@example.com:root/awesome-project.wiki.git",
+    "git_http_url": "http://example.com/root/awesome-project.wiki.git",
+    "path_with_namespace": "root/awesome-project.wiki",
     "default_branch": "master"
   },
   "object_attributes": {
@@ -754,7 +764,7 @@ X-Gitlab-Event: Wiki Page Hook
 }
 ```
 
-## Pipeline events
+### Pipeline events
 
 Triggered on status change of Pipeline.
 
@@ -922,12 +932,70 @@ X-Gitlab-Event: Pipeline Hook
 }
 ```
 
-#### Example webhook receiver
+### Build events
+
+Triggered on status change of a Build.
+
+**Request Header**:
+
+```
+X-Gitlab-Event: Build Hook
+```
+
+**Request Body**:
+
+```json
+{
+  "object_kind": "build",
+  "ref": "gitlab-script-trigger",
+  "tag": false,
+  "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+  "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+  "build_id": 1977,
+  "build_name": "test",
+  "build_stage": "test",
+  "build_status": "created",
+  "build_started_at": null,
+  "build_finished_at": null,
+  "build_duration": null,
+  "build_allow_failure": false,
+  "project_id": 380,
+  "project_name": "gitlab-org/gitlab-test",
+  "user": {
+    "id": 3,
+    "name": "User",
+    "email": "user@gitlab.com"
+  },
+  "commit": {
+    "id": 2366,
+    "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+    "message": "test\n",
+    "author_name": "User",
+    "author_email": "user@gitlab.com",
+    "status": "created",
+    "duration": null,
+    "started_at": null,
+    "finished_at": null
+  },
+  "repository": {
+    "name": "gitlab_test",
+    "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
+    "description": "Atque in sunt eos similique dolores voluptatem.",
+    "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
+    "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
+    "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
+    "visibility_level": 20
+  }
+}
+```
+
+## Example webhook receiver
 
 If you want to see GitLab's webhooks in action for testing purposes you can use
-a simple echo script running in a console session.
+a simple echo script running in a console session. For the following script to
+work you need to have Ruby installed.
 
-Save the following file as `print_http_body.rb`.
+Save the following file as `print_http_body.rb`:
 
 ```ruby
 require 'webrick'
@@ -947,7 +1015,8 @@ Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
 8000`.  Then add your server as a webhook receiver in GitLab as
 `http://my.host:8000/`.
 
-When you press 'Test Hook' in GitLab, you should see something like this in the console.
+When you press 'Test Hook' in GitLab, you should see something like this in the
+console:
 
 ```
 {"before":"077a85dd266e6f3573ef7e9ef8ce3343ad659c4e","after":"95cd4a99e93bc4bbabacfa2cd10e6725b1403c60",<SNIP>}
diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png
index 59175ae44c5c9fe535acb8c6678e44891e3ae862..3fa37067d1ecbe740a9a298a59526d0c7af4af0a 100644
Binary files a/doc/workflow/img/todos_add_todo_sidebar.png and b/doc/workflow/img/todos_add_todo_sidebar.png differ
diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png
index aa35bb672ea97ea1028582741f717d8fbcd20b2f..a8e756a71db4211f1aa3434245e141f50af7fff6 100644
Binary files a/doc/workflow/img/todos_mark_done_sidebar.png and b/doc/workflow/img/todos_mark_done_sidebar.png differ
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 54e7ae19ea524df1b240e55859da97c1a45a43b4..1a8fc39bb335288ebf77147cba28d51c5b6ea02a 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -35,7 +35,7 @@ A Todo appears in your Todos dashboard when:
 ### Manually creating a Todo
 
 You can also add an issue or merge request to your Todos dashboard by clicking
-the "Add Todo" button in the issue or merge request sidebar.
+the "Add todo" button in the issue or merge request sidebar.
 
 ![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png)
 
@@ -69,7 +69,7 @@ corresponding **Done** button, and it will disappear from your Todo list.
 ![A Todo in the Todos dashboard](img/todo_list_item.png)
 
 A Todo can also be marked as done from the issue or merge request sidebar using
-the "Mark Done" button.
+the "Mark done" button.
 
 ![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png)
 
diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature
deleted file mode 100644
index 92a5389e3a4dc1f5f3f23dca9014569d653297b3..0000000000000000000000000000000000000000
--- a/features/admin/spam_logs.feature
+++ /dev/null
@@ -1,8 +0,0 @@
-Feature: Admin spam logs
-  Background:
-    Given I sign in as an admin
-    And spam logs exist
-
-  Scenario: Browse spam logs
-    When I visit spam logs page
-    Then I should see list of spam logs
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index 63ce3ccb536e4bd4c4dbe4e125b9ff04eecd2df7..a04228de03b3e7f247baa4471263b34f750ecaad 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -49,7 +49,6 @@ Feature: Project Wiki
   Scenario: View all pages
     Given I have an existing wiki page
     And I browse to that Wiki page
-    And I click on the "Pages" button
     Then I should see the existing page in the pages list
 
   Scenario: File exists in wiki repo
@@ -72,13 +71,11 @@ Feature: Project Wiki
   @javascript
   Scenario: New Wiki page that has a path
     Given I create a New page with paths
-    And I click on the "Pages" button
     Then I should see non-escaped link in the pages list
 
   @javascript
   Scenario: Edit Wiki page that has a path
     Given I create a New page with paths
-    And I click on the "Pages" button
     And I edit the Wiki page with a path
     Then I should see a non-escaped path
     And I should see the Editing page
@@ -88,7 +85,6 @@ Feature: Project Wiki
   @javascript
   Scenario: View the page history of a Wiki page that has a path
     Given I create a New page with paths
-    And I click on the "Pages" button
     And I view the page history of a Wiki page that has a path
     Then I should see a non-escaped path
     And I should see the page history
@@ -96,7 +92,6 @@ Feature: Project Wiki
   @javascript
   Scenario: View an old page version of a Wiki page
     Given I create a New page with paths
-    And I click on the "Pages" button
     And I edit the Wiki page with a path
     Then I should see a non-escaped path
     And I should see the Editing page
diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb
deleted file mode 100644
index ad825fd414c2da2f3bac140e927d8d5d2fa89064..0000000000000000000000000000000000000000
--- a/features/steps/admin/spam_logs.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedAdmin
-
-  step 'I should see list of spam logs' do
-    expect(page).to have_content('Spam Logs')
-    expect(page).to have_content spam_log.source_ip
-    expect(page).to have_content spam_log.noteable_type
-    expect(page).to have_content 'N'
-    expect(page).to have_content spam_log.title
-    expect(page).to have_content truncate(spam_log.description)
-    expect(page).to have_link('Remove user')
-    expect(page).to have_link('Block user')
-  end
-
-  step 'spam logs exist' do
-    create(:spam_log)
-  end
-
-  def spam_log
-    @spam_log ||= SpamLog.first
-  end
-
-  def truncate(description)
-    "#{spam_log.description[0...97]}..."
-  end
-end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index f728d243cdc17c55f6ffaa9e03671798244a3c8d..d2fa8cd39af603e40a97c1c1c7f42a60dfe0ee6c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -515,7 +515,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
 
   step 'I should see new target branch changes' do
     expect(page).to have_content 'Request to merge fix into feature'
-    expect(page).to have_content 'Target branch changed from merge-test to feature'
+    expect(page).to have_content 'changed target branch from merge-test to feature'
     wait_for_ajax
   end
 
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 2134dae168a5fc020d8d05faa9011d77a3e5d1b2..dee6a8a5558e97d4b36c787c5f27105e9b72a121 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -241,7 +241,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
 
     page.within(:css, ".nav-text") do
       expect(page).to have_content "Test"
-      expect(page).to have_content "Edit Page"
+      expect(page).to have_content "Create Page"
     end
   end
 
@@ -258,7 +258,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
     expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
 
     page.within(:css, ".nav-text") do
-      expect(page).to have_content "Edit"
+      expect(page).to have_content "Create"
       expect(page).to have_content "Api"
     end
   end
@@ -271,7 +271,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
     expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
 
     page.within(:css, ".nav-text") do
-      expect(page).to have_content "Edit"
+      expect(page).to have_content "Create"
       expect(page).to have_content "Rake"
     end
   end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 07a955b1a14bb0d68867fc5c1c5710ad2ba441b1..4cb0a21fbb44a4fe03e7d55fd75d91fd61bd3bab 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
     expect(page).to have_content "link test"
 
     click_link "link test"
-    expect(page).to have_content "Edit Page"
+    expect(page).to have_content "Create Page"
   end
 
   step 'I have an existing Wiki page' do
@@ -80,13 +80,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
     expect(page).to have_content "Page was successfully deleted"
   end
 
-  step 'I click on the "Pages" button' do
-    click_on "Pages"
-  end
-
   step 'I should see the existing page in the pages list' do
     expect(page).to have_content current_user.name
-    expect(page).to have_content @page.title
+    expect(find('.wiki-pages')).to have_content @page.title.capitalize
   end
 
   step 'I have an existing Wiki page with images linked on page' do
@@ -125,7 +121,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   step 'I should see the new wiki page form' do
     expect(current_path).to match('wikis/image.jpg')
     expect(page).to have_content('New Wiki Page')
-    expect(page).to have_content('Edit Page')
+    expect(page).to have_content('Create Page')
   end
 
   step 'I create a New page with paths' do
@@ -142,8 +138,8 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I edit the Wiki page with a path' do
-    expect(page).to have_content('three')
-    click_on 'three'
+    expect(find('.wiki-pages')).to have_content('Three')
+    click_on 'Three'
     expect(find('.nav-text')).to have_content('Three')
     click_on 'Edit'
   end
@@ -157,7 +153,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I view the page history of a Wiki page that has a path' do
-    click_on 'three'
+    click_on 'Three'
     click_on 'Page History'
   end
 
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index df9845ba569dac0788ce79c6e311246af309a87e..79dde620265d7a2f51145f20a1b44731df13a9a3 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -110,14 +110,14 @@ module SharedIssuable
   end
 
   step 'I sort the list by "Oldest updated"' do
-    find('button.dropdown-toggle.btn').click
+    find('button.dropdown-toggle').click
     page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
       click_link "Oldest updated"
     end
   end
 
   step 'I sort the list by "Least popular"' do
-    find('button.dropdown-toggle.btn').click
+    find('button.dropdown-toggle').click
 
     page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
       click_link 'Least popular'
@@ -125,7 +125,7 @@ module SharedIssuable
   end
 
   step 'I sort the list by "Most popular"' do
-    find('button.dropdown-toggle.btn').click
+    find('button.dropdown-toggle').click
 
     page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
       click_link 'Most popular'
@@ -179,7 +179,7 @@ module SharedIssuable
     project = Project.find_by(name: from_project_name)
 
     expect(page).to have_content(user_name)
-    expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
+    expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
   end
 
   def expect_sidebar_content(content)
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 56b36f7c46c4773242ef54bb7448b1ad3d2f0762..a036d9b884fbe685cf13ebac10860c04c4c30539 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -2,7 +2,7 @@ module SharedMarkdown
   include Spinach::DSL
 
   def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki")
-    node = find("#{parent} h#{level} a##{id}")
+    node = find("#{parent} h#{level} a#user-content-#{id}")
     expect(node[:href]).to eq "##{id}"
 
     # Work around a weird Capybara behavior where calling `parent` on a node
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index dae0d0f918cd53e16a467c970969b55de22a2c99..47372df152d59a77e6c900100ff7afbe26cdff9a 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -6,7 +6,15 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15
 
 Capybara.javascript_driver = :poltergeist
 Capybara.register_driver :poltergeist do |app|
-  Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
+  Capybara::Poltergeist::Driver.new(
+    app,
+    js_errors: true,
+    timeout: timeout,
+    window_size: [1366, 768],
+    phantomjs_options: [
+      '--load-images=no'
+    ]
+  )
 end
 
 Capybara.default_max_wait_time = timeout
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index f54d4f06627ea96e3946142f7ceabf0d79862663..492884d162bcb8d11312d003b16e21740e210e16 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -77,7 +77,7 @@ module API
         )
 
         begin
-          case params[:state].to_s
+          case params[:state]
           when 'pending'
             status.enqueue!
           when 'running'
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 0319d076ecb42318958a8f7e2dba70856b83eab9..2670a2d413a4cbb0f3cecdb8dd8569ee334fe797 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -48,7 +48,7 @@ module API
         requires :id, type: Integer, desc: 'The project ID'
         requires :branch_name, type: String, desc: 'The name of branch'
         requires :commit_message, type: String, desc: 'Commit message'
-        requires :actions, type: Array, desc: 'Actions to perform in commit'
+        requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
         optional :author_email, type: String, desc: 'Author email for commit'
         optional :author_name, type: String, desc: 'Author name for commit'
       end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 7a724487e0286e4b04d86ebc0d306cdc8be8522b..d5dfb8d00be24b97ff169f27d25f4cfbfb7064f2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -141,8 +141,12 @@ module API
         options[:project].repository.commit(repo_branch.dereferenced_target)
       end
 
+      expose :merged do |repo_branch, options|
+        options[:project].repository.merged_to_root_ref?(repo_branch.name)
+      end
+
       expose :protected do |repo_branch, options|
-        options[:project].protected_branch? repo_branch.name
+        options[:project].protected_branch?(repo_branch.name)
       end
 
       expose :developers_can_push do |repo_branch, options|
@@ -607,6 +611,7 @@ module API
       expose :user, with: Entities::UserBasic
       expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
       expose :duration
+      expose :coverage
     end
 
     class EnvironmentBasic < Grape::Entity
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 96510e651a319df34d589821566da19b4294795a..28f306e45f3fb06ef5a8ac2f33537b2e6b413848 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -23,140 +23,107 @@ module API
           branch_name: attrs[:branch_name]
         }
       end
+
+      params :simple_file_params do
+        requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
+        requires :branch_name, type: String, desc: 'The name of branch'
+        requires :commit_message, type: String, desc: 'Commit Message'
+        optional :author_email, type: String, desc: 'The email of the author'
+        optional :author_name, type: String, desc: 'The name of the author'
+      end
+
+      params :extended_file_params do
+        use :simple_file_params
+        requires :content, type: String, desc: 'File content'
+        optional :encoding, type: String, values: %w[base64], desc: 'File encoding'
+      end
     end
 
+    params do
+      requires :id, type: String, desc: 'The project ID'
+    end
     resource :projects do
-      # Get file from repository
-      # File content is Base64 encoded
-      #
-      # Parameters:
-      #   file_path (required) - The path to the file. Ex. lib/class.rb
-      #   ref (required) - The name of branch, tag or commit
-      #
-      # Example Request:
-      #   GET /projects/:id/repository/files
-      #
-      # Example response:
-      # {
-      #   "file_name": "key.rb",
-      #   "file_path": "app/models/key.rb",
-      #   "size": 1476,
-      #   "encoding": "base64",
-      #   "content": "IyA9PSBTY2hlbWEgSW5mb3...",
-      #   "ref": "master",
-      #   "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
-      #   "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
-      #   "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
-      # }
-      #
+      desc 'Get a file from repository'
+      params do
+        requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb'
+        requires :ref, type: String, desc: 'The name of branch, tag, or commit'
+      end
       get ":id/repository/files" do
         authorize! :download_code, user_project
 
-        required_attributes! [:file_path, :ref]
-        attrs = attributes_for_keys [:file_path, :ref]
-        ref = attrs.delete(:ref)
-        file_path = attrs.delete(:file_path)
-
-        commit = user_project.commit(ref)
-        not_found! 'Commit' unless commit
+        commit = user_project.commit(params[:ref])
+        not_found!('Commit') unless commit
 
         repo = user_project.repository
-        blob = repo.blob_at(commit.sha, file_path)
+        blob = repo.blob_at(commit.sha, params[:file_path])
+        not_found!('File') unless blob
 
-        if blob
-          blob.load_all_data!(repo)
-          status(200)
+        blob.load_all_data!(repo)
+        status(200)
 
-          {
-            file_name: blob.name,
-            file_path: blob.path,
-            size: blob.size,
-            encoding: "base64",
-            content: Base64.strict_encode64(blob.data),
-            ref: ref,
-            blob_id: blob.id,
-            commit_id: commit.id,
-            last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id
-          }
-        else
-          not_found! 'File'
-        end
+        {
+          file_name: blob.name,
+          file_path: blob.path,
+          size: blob.size,
+          encoding: "base64",
+          content: Base64.strict_encode64(blob.data),
+          ref: params[:ref],
+          blob_id: blob.id,
+          commit_id: commit.id,
+          last_commit_id: repo.last_commit_for_path(commit.sha, params[:file_path]).id
+        }
       end
 
-      # Create new file in repository
-      #
-      # Parameters:
-      #   file_path (required) - The path to new file. Ex. lib/class.rb
-      #   branch_name (required) - The name of branch
-      #   content (required) - File content
-      #   commit_message (required) - Commit message
-      #
-      # Example Request:
-      #   POST /projects/:id/repository/files
-      #
+      desc 'Create new file in repository'
+      params do
+        use :extended_file_params
+      end
       post ":id/repository/files" do
         authorize! :push_code, user_project
 
-        required_attributes! [:file_path, :branch_name, :content, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
-        result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
+        file_params = declared_params(include_missing: false)
+        result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute
 
         if result[:status] == :success
           status(201)
-          commit_response(attrs)
+          commit_response(file_params)
         else
           render_api_error!(result[:message], 400)
         end
       end
 
-      # Update existing file in repository
-      #
-      # Parameters:
-      #   file_path (optional) - The path to file. Ex. lib/class.rb
-      #   branch_name (required) - The name of branch
-      #   content (required) - File content
-      #   commit_message (required) - Commit message
-      #
-      # Example Request:
-      #   PUT /projects/:id/repository/files
-      #
+      desc 'Update existing file in repository'
+      params do
+        use :extended_file_params
+      end
       put ":id/repository/files" do
         authorize! :push_code, user_project
 
-        required_attributes! [:file_path, :branch_name, :content, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
-        result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
+        file_params = declared_params(include_missing: false)
+        result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute
 
         if result[:status] == :success
           status(200)
-          commit_response(attrs)
+          commit_response(file_params)
         else
           http_status = result[:http_status] || 400
           render_api_error!(result[:message], http_status)
         end
       end
 
-      # Delete existing file in repository
-      #
-      # Parameters:
-      #   file_path (optional) - The path to file. Ex. lib/class.rb
-      #   branch_name (required) - The name of branch
-      #   content (required) - File content
-      #   commit_message (required) - Commit message
-      #
-      # Example Request:
-      #   DELETE /projects/:id/repository/files
-      #
+      desc 'Delete an existing file in repository'
+      params do
+        use :simple_file_params
+      end
       delete ":id/repository/files" do
         authorize! :push_code, user_project
 
-        required_attributes! [:file_path, :branch_name, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name]
-        result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
+        file_params = declared_params(include_missing: false)
+        result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute
 
         if result[:status] == :success
           status(200)
-          commit_response(attrs)
+          commit_response(file_params)
         else
           render_api_error!(result[:message], 400)
         end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 48ad3b80ae0029b4905944a5b2fcd0b182249fc3..5315c22e1e42470f4cd8570bdcb7ee45c6abf771 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -33,7 +33,7 @@ module API
 
         groups = groups.search(params[:search]) if params[:search].present?
         groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
-        groups = groups.reorder(params[:order_by] => params[:sort].to_sym)
+        groups = groups.reorder(params[:order_by] => params[:sort])
 
         present paginate(groups), with: Entities::Group
       end
@@ -82,7 +82,7 @@ module API
                         :lfs_enabled, :request_access_enabled
       end
       put ':id' do
-        group = find_group(params[:id])
+        group = find_group!(params[:id])
         authorize! :admin_group, group
 
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
@@ -96,13 +96,13 @@ module API
         success Entities::GroupDetail
       end
       get ":id" do
-        group = find_group(params[:id])
+        group = find_group!(params[:id])
         present group, with: Entities::GroupDetail
       end
 
       desc 'Remove a group.'
       delete ":id" do
-        group = find_group(params[:id])
+        group = find_group!(params[:id])
         authorize! :admin_group, group
         DestroyGroupService.new(group, current_user).execute
       end
@@ -111,7 +111,7 @@ module API
         success Entities::Project
       end
       get ":id/projects" do
-        group = find_group(params[:id])
+        group = find_group!(params[:id])
         projects = GroupProjectsFinder.new(group).execute(current_user)
         projects = paginate projects
         present projects, with: Entities::Project, user: current_user
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2c593dbb4ea217e038c18683bbb3025ae2472c8f..7f94ede7940a8191ffc056b4a1a73cec2baa1f96 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -68,7 +68,7 @@ module API
     end
 
     def user_project
-      @project ||= find_project(params[:id])
+      @project ||= find_project!(params[:id])
     end
 
     def available_labels
@@ -76,7 +76,15 @@ module API
     end
 
     def find_project(id)
-      project = Project.find_with_namespace(id) || Project.find_by(id: id)
+      if id =~ /^\d+$/
+        Project.find_by(id: id)
+      else
+        Project.find_with_namespace(id)
+      end
+    end
+
+    def find_project!(id)
+      project = find_project(id)
 
       if can?(current_user, :read_project, project)
         project
@@ -97,7 +105,15 @@ module API
     end
 
     def find_group(id)
-      group = Group.find_by(path: id) || Group.find_by(id: id)
+      if id =~ /^\d+$/
+        Group.find_by(id: id)
+      else
+        Group.find_by(path: id)
+      end
+    end
+
+    def find_group!(id)
+      group = find_group(id)
 
       if can?(current_user, :read_group, group)
         group
@@ -112,9 +128,7 @@ module API
     end
 
     def find_project_issue(id)
-      issue = user_project.issues.find(id)
-      not_found! unless can?(current_user, :read_issue, issue)
-      issue
+      IssuesFinder.new(current_user, project_id: user_project.id).find(id)
     end
 
     def paginate(relation)
@@ -127,6 +141,10 @@ module API
       unauthorized! unless current_user
     end
 
+    def authenticate_non_get!
+      authenticate! unless %w[GET HEAD].include?(route.route_method)
+    end
+
     def authenticate_by_gitlab_shell_token!
       input = params['secret_token'].try(:chomp)
       unless Devise.secure_compare(secret_token, input)
@@ -135,6 +153,7 @@ module API
     end
 
     def authenticated_as_admin!
+      authenticate!
       forbidden! unless current_user.is_admin?
     end
 
@@ -182,20 +201,6 @@ module API
       ActionController::Parameters.new(attrs).permit!
     end
 
-    # Helper method for validating all labels against its names
-    def validate_label_params(params)
-      errors = {}
-
-      params[:labels].to_s.split(',').each do |label_name|
-        label = available_labels.find_or_initialize_by(title: label_name.strip)
-        next if label.valid?
-
-        errors[label.title] = label.errors
-      end
-
-      errors
-    end
-
     # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
     # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
     #
@@ -308,11 +313,6 @@ module API
     # Projects helpers
 
     def filter_projects(projects)
-      # If the archived parameter is passed, limit results accordingly
-      if params[:archived].present?
-        projects = projects.where(archived: to_boolean(params[:archived]))
-      end
-
       if params[:search].present?
         projects = projects.search(params[:search])
       end
@@ -321,25 +321,8 @@ module API
         projects = projects.search_by_visibility(params[:visibility])
       end
 
-      projects.reorder(project_order_by => project_sort)
-    end
-
-    def project_order_by
-      order_fields = %w(id name path created_at updated_at last_activity_at)
-
-      if order_fields.include?(params['order_by'])
-        params['order_by']
-      else
-        'created_at'
-      end
-    end
-
-    def project_sort
-      if params["sort"] == 'asc'
-        :asc
-      else
-        :desc
-      end
+      projects = projects.where(archived: params[:archived])
+      projects.reorder(params[:order_by] => params[:sort])
     end
 
     # file helpers
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 90114f6f6677de987ba665d356ed318318508199..d9cae1501f8b2f565cb49b25ef849c16c10692a0 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -2,7 +2,7 @@ module API
   module Helpers
     module MembersHelpers
       def find_source(source_type, id)
-        public_send("find_#{source_type}", id)
+        public_send("find_#{source_type}!", id)
       end
 
       def authorize_admin_source!(source_type, source)
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index eea5b91d4f91908669ddfc8c4bea81caaf87a2f9..049b4fb214cb85289cf411bf6f190f2be3562cfe 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -19,6 +19,15 @@ module API
       def filter_issues_milestone(issues, milestone)
         issues.includes(:milestone).where('milestones.title' => milestone)
       end
+
+      def issue_params
+        new_params = declared(params, include_parent_namespace: false, include_missing: false).to_h
+        new_params = new_params.with_indifferent_access
+        new_params.delete(:id)
+        new_params.delete(:issue_id)
+
+        new_params
+      end
     end
 
     resource :issues do
@@ -68,7 +77,7 @@ module API
       #   GET /groups/:id/issues?milestone=1.0.0
       #   GET /groups/:id/issues?milestone=1.0.0&state=closed
       get ":id/issues" do
-        group = find_group(params[:id])
+        group = find_group!(params[:id])
 
         params[:state] ||= 'opened'
         params[:group_id] = group.id
@@ -86,6 +95,10 @@ module API
       end
     end
 
+    params do
+      requires :id, type: String, desc: 'The ID of a project'
+    end
+
     resource :projects do
       # Get a list of project issues
       #
@@ -109,7 +122,7 @@ module API
       #   GET /projects/:id/issues?milestone=1.0.0&state=closed
       #   GET /issues?iid=42
       get ":id/issues" do
-        issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user)
+        issues = IssuesFinder.new(current_user, project_id: user_project.id).execute.inc_notes_with_associations
         issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
         issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
         issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
@@ -152,17 +165,10 @@ module API
       post ':id/issues' do
         required_attributes! [:title]
 
-        keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential]
+        keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels]
         keys << :created_at if current_user.admin? || user_project.owner == current_user
         attrs = attributes_for_keys(keys)
 
-        # Validate label names in advance
-        if (errors = validate_label_params(params)).any?
-          render_api_error!({ labels: errors }, 400)
-        end
-
-        attrs[:labels] = params[:labels] if params[:labels]
-
         # Convert and filter out invalid confidential flags
         attrs['confidential'] = to_boolean(attrs['confidential'])
         attrs.delete('confidential') if attrs['confidential'].nil?
@@ -180,41 +186,35 @@ module API
         end
       end
 
-      # Update an existing issue
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      #   issue_id (required) - The ID of a project issue
-      #   title (optional) - The title of an issue
-      #   description (optional) - The description of an issue
-      #   assignee_id (optional) - The ID of a user to assign issue
-      #   milestone_id (optional) - The ID of a milestone to assign issue
-      #   labels (optional) - The labels of an issue
-      #   state_event (optional) - The state event of an issue (close|reopen)
-      #   updated_at (optional) - Date time string, ISO 8601 formatted
-      #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
-      #   confidential (optional) - Boolean parameter if the issue should be confidential
-      # Example Request:
-      #   PUT /projects/:id/issues/:issue_id
+      desc 'Update an existing issue' do
+        success Entities::Issue
+      end
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+        requires :issue_id, type: Integer, desc: "The ID of a project issue"
+        optional :title, type: String, desc: 'The new title of the issue'
+        optional :description, type: String, desc: 'The description of an issue'
+        optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue'
+        optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
+        optional :labels, type: String, desc: 'The labels of an issue'
+        optional :state_event, type: String, values: ['close', 'reopen'], desc: 'The state event of an issue'
+        # TODO 9.0, use the Grape DateTime type here
+        optional :updated_at, type: String, desc: 'Date time string, ISO 8601 formatted'
+        optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY'
+        # TODO 9.0, use the Grape boolean type here
+        optional :confidential, type: String, desc: 'Boolean parameter if the issue should be confidential'
+      end
       put ':id/issues/:issue_id' do
         issue = user_project.issues.find(params[:issue_id])
         authorize! :update_issue, issue
-        keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential]
-        keys << :updated_at if current_user.admin? || user_project.owner == current_user
-        attrs = attributes_for_keys(keys)
-
-        # Validate label names in advance
-        if (errors = validate_label_params(params)).any?
-          render_api_error!({ labels: errors }, 400)
-        end
-
-        attrs[:labels] = params[:labels] if params[:labels]
 
         # Convert and filter out invalid confidential flags
-        attrs['confidential'] = to_boolean(attrs['confidential'])
-        attrs.delete('confidential') if attrs['confidential'].nil?
+        params[:confidential] = to_boolean(params[:confidential])
+        params.delete(:confidential) if params[:confidential].nil?
+
+        params.delete(:updated_at) unless current_user.admin? || user_project.owner == current_user
 
-        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
+        issue = ::Issues::UpdateService.new(user_project, current_user, issue_params).execute(issue)
 
         if issue.valid?
           present issue, with: Entities::Issue, current_user: current_user, project: user_project
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index e82651a157899551a6b0ccd04322ea7f2419846c..97baebc1d27ba2ed1f60c3b2af9f86883746d1d6 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -77,11 +77,6 @@ module API
 
         mr_params = declared_params
 
-        # Validate label names in advance
-        if (errors = validate_label_params(mr_params)).any?
-          render_api_error!({ labels: errors }, 400)
-        end
-
         merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
 
         if merge_request.valid?
@@ -157,11 +152,6 @@ module API
 
           mr_params = declared_params(include_missing: false)
 
-          # Validate label names in advance
-          if (errors = validate_label_params(mr_params)).any?
-            render_api_error!({ labels: errors }, 400)
-          end
-
           merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request)
 
           if merge_request.valid?
@@ -202,7 +192,7 @@ module API
             should_remove_source_branch: params[:should_remove_source_branch]
           }
 
-          if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active?
+          if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active?
             ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
               execute(merge_request)
           else
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index ddfde178d30be932f94afe801ee9f37872bf8adc..2929d2157dc72b16fac0b4fdb5c22b189f3e20cd 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,293 +1,295 @@
 module API
   # Projects API
   class Projects < Grape::API
-    before { authenticate! }
+    include PaginationParams
+
+    before { authenticate_non_get! }
+
+    helpers do
+      params :optional_params do
+        optional :description, type: String, desc: 'The description of the project'
+        optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
+        optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
+        optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
+        optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled'
+        optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+        optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+        optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+        optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
+        optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
+        optional :visibility_level, type: Integer, values: [
+          Gitlab::VisibilityLevel::PRIVATE,
+          Gitlab::VisibilityLevel::INTERNAL,
+          Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.'
+        optional :public_builds, type: Boolean, desc: 'Perform public builds'
+        optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+        optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+        optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
+      end
 
-    resource :projects, requirements: { id: /[^\/]+/ } do
-      helpers do
-        def map_public_to_visibility_level(attrs)
-          publik = attrs.delete(:public)
-          if publik.present? && !attrs[:visibility_level].present?
-            publik = to_boolean(publik)
-            # Since setting the public attribute to private could mean either
-            # private or internal, use the more conservative option, private.
-            attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
-          end
-          attrs
+      def map_public_to_visibility_level(attrs)
+        publik = attrs.delete(:public)
+        if !publik.nil? && !attrs[:visibility_level].present?
+          # Since setting the public attribute to private could mean either
+          # private or internal, use the more conservative option, private.
+          attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
         end
+        attrs
       end
+    end
 
-      # Get a projects list for authenticated user
-      #
-      # Example Request:
-      #   GET /projects
-      get do
-        projects = current_user.authorized_projects
-        projects = filter_projects(projects)
-        projects = paginate projects
-        entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
+    resource :projects do
+      helpers do
+        params :sort_params do
+          optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+                              default: 'created_at', desc: 'Return projects ordered by field'
+          optional :sort, type: String, values: %w[asc desc], default: 'desc',
+                          desc: 'Return projects sorted in ascending and descending order'
+        end
 
-        present projects, with: entity, user: current_user
+        params :filter_params do
+          optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+          optional :visibility, type: String, values: %w[public internal private],
+                                desc: 'Limit by visibility'
+          optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+          use :sort_params
+        end
+
+        params :create_params do
+          optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
+          optional :import_url, type: String, desc: 'URL from which the project is imported'
+        end
       end
 
-      # Get a list of visible projects for authenticated user
-      #
-      # Example Request:
-      #   GET /projects/visible
+      desc 'Get a list of visible projects for authenticated user' do
+        success Entities::BasicProjectDetails
+      end
+      params do
+        optional :simple, type: Boolean, default: false,
+                          desc: 'Return only the ID, URL, name, and path of each project'
+        use :filter_params
+        use :pagination
+      end
       get '/visible' do
         projects = ProjectsFinder.new.execute(current_user)
         projects = filter_projects(projects)
-        projects = paginate projects
+        entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
+
+        present paginate(projects), with: entity, user: current_user
+      end
+
+      desc 'Get a projects list for authenticated user' do
+        success Entities::BasicProjectDetails
+      end
+      params do
+        optional :simple, type: Boolean, default: false,
+                          desc: 'Return only the ID, URL, name, and path of each project'
+        use :filter_params
+        use :pagination
+      end
+      get do
+        authenticate!
+
+        projects = current_user.authorized_projects
+        projects = filter_projects(projects)
         entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
 
-        present projects, with: entity, user: current_user
+        present paginate(projects), with: entity, user: current_user
       end
 
-      # Get an owned projects list for authenticated user
-      #
-      # Example Request:
-      #   GET /projects/owned
+      desc 'Get an owned projects list for authenticated user' do
+        success Entities::BasicProjectDetails
+      end
+      params do
+        use :filter_params
+        use :pagination
+      end
       get '/owned' do
+        authenticate!
+
         projects = current_user.owned_projects
         projects = filter_projects(projects)
-        projects = paginate projects
-        present projects, with: Entities::ProjectWithAccess, user: current_user
+
+        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
       end
 
-      # Gets starred project for the authenticated user
-      #
-      # Example Request:
-      #   GET /projects/starred
+      desc 'Gets starred project for the authenticated user' do
+        success Entities::BasicProjectDetails
+      end
+      params do
+        use :filter_params
+        use :pagination
+      end
       get '/starred' do
+        authenticate!
+
         projects = current_user.viewable_starred_projects
         projects = filter_projects(projects)
-        projects = paginate projects
-        present projects, with: Entities::Project, user: current_user
+
+        present paginate(projects), with: Entities::Project, user: current_user
       end
 
-      # Get all projects for admin user
-      #
-      # Example Request:
-      #   GET /projects/all
+      desc 'Get all projects for admin user' do
+        success Entities::BasicProjectDetails
+      end
+      params do
+        use :filter_params
+        use :pagination
+      end
       get '/all' do
         authenticated_as_admin!
+
         projects = Project.all
         projects = filter_projects(projects)
-        projects = paginate projects
-        present projects, with: Entities::ProjectWithAccess, user: current_user
+
+        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
       end
 
-      # Get a single project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   GET /projects/:id
-      get ":id" do
-        present user_project, with: Entities::ProjectWithAccess, user: current_user,
-                              user_can_admin_project: can?(current_user, :admin_project, user_project)
+      desc 'Search for projects the current user has access to' do
+        success Entities::Project
+      end
+      params do
+        requires :query, type: String, desc: 'The project name to be searched'
+        use :sort_params
+        use :pagination
+      end
+      get "/search/:query" do
+        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
+        projects = search_service.objects('projects', params[:page])
+        projects = projects.reorder(params[:order_by] => params[:sort])
+
+        present paginate(projects), with: Entities::Project
       end
 
-      # Get events for a single project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   GET /projects/:id/events
-      get ":id/events" do
-        events = paginate user_project.events.recent
-        present events, with: Entities::Event
-      end
-
-      # Create new project
-      #
-      # Parameters:
-      #   name (required)                   - name for new project
-      #   description (optional)            - short project description
-      #   issues_enabled (optional)
-      #   merge_requests_enabled (optional)
-      #   builds_enabled (optional)
-      #   wiki_enabled (optional)
-      #   snippets_enabled (optional)
-      #   container_registry_enabled (optional)
-      #   shared_runners_enabled (optional)
-      #   namespace_id (optional)           - defaults to user namespace
-      #   public (optional)                 - if true same as setting visibility_level = 20
-      #   visibility_level (optional)       - 0 by default
-      #   import_url (optional)
-      #   public_builds (optional)
-      #   lfs_enabled (optional)
-      #   request_access_enabled (optional) - Allow users to request member access
-      # Example Request
-      #   POST /projects
+      desc 'Create new project' do
+        success Entities::Project
+      end
+      params do
+        requires :name, type: String, desc: 'The name of the project'
+        optional :path, type: String, desc: 'The path of the repository'
+        use :optional_params
+        use :create_params
+      end
       post do
-        required_attributes! [:name]
-        attrs = attributes_for_keys [:builds_enabled,
-                                     :container_registry_enabled,
-                                     :description,
-                                     :import_url,
-                                     :issues_enabled,
-                                     :lfs_enabled,
-                                     :merge_requests_enabled,
-                                     :name,
-                                     :namespace_id,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :path,
-                                     :public,
-                                     :public_builds,
-                                     :request_access_enabled,
-                                     :shared_runners_enabled,
-                                     :snippets_enabled,
-                                     :visibility_level,
-                                     :wiki_enabled,
-                                     :only_allow_merge_if_all_discussions_are_resolved]
-        attrs = map_public_to_visibility_level(attrs)
-        @project = ::Projects::CreateService.new(current_user, attrs).execute
-        if @project.saved?
-          present @project, with: Entities::Project,
-                            user_can_admin_project: can?(current_user, :admin_project, @project)
+        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+        project = ::Projects::CreateService.new(current_user, attrs).execute
+
+        if project.saved?
+          present project, with: Entities::Project,
+                           user_can_admin_project: can?(current_user, :admin_project, project)
         else
-          if @project.errors[:limit_reached].present?
-            error!(@project.errors[:limit_reached], 403)
+          if project.errors[:limit_reached].present?
+            error!(project.errors[:limit_reached], 403)
           end
-          render_validation_error!(@project)
+          render_validation_error!(project)
         end
       end
 
-      # Create new project for a specified user.  Only available to admin users.
-      #
-      # Parameters:
-      #   user_id (required)                - The ID of a user
-      #   name (required)                   - name for new project
-      #   description (optional)            - short project description
-      #   default_branch (optional)         - 'master' by default
-      #   issues_enabled (optional)
-      #   merge_requests_enabled (optional)
-      #   builds_enabled (optional)
-      #   wiki_enabled (optional)
-      #   snippets_enabled (optional)
-      #   container_registry_enabled (optional)
-      #   shared_runners_enabled (optional)
-      #   public (optional)                 - if true same as setting visibility_level = 20
-      #   visibility_level (optional)
-      #   import_url (optional)
-      #   public_builds (optional)
-      #   lfs_enabled (optional)
-      #   request_access_enabled (optional) - Allow users to request member access
-      # Example Request
-      #   POST /projects/user/:user_id
+      desc 'Create new project for a specified user. Only available to admin users.' do
+        success Entities::Project
+      end
+      params do
+        requires :name, type: String, desc: 'The name of the project'
+        requires :user_id, type: Integer, desc: 'The ID of a user'
+        optional :default_branch, type: String, desc: 'The default branch of the project'
+        use :optional_params
+        use :create_params
+      end
       post "user/:user_id" do
         authenticated_as_admin!
-        user = User.find(params[:user_id])
-        attrs = attributes_for_keys [:builds_enabled,
-                                     :default_branch,
-                                     :description,
-                                     :import_url,
-                                     :issues_enabled,
-                                     :lfs_enabled,
-                                     :merge_requests_enabled,
-                                     :name,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :public,
-                                     :public_builds,
-                                     :request_access_enabled,
-                                     :shared_runners_enabled,
-                                     :snippets_enabled,
-                                     :visibility_level,
-                                     :wiki_enabled,
-                                     :only_allow_merge_if_all_discussions_are_resolved]
-        attrs = map_public_to_visibility_level(attrs)
-        @project = ::Projects::CreateService.new(user, attrs).execute
-        if @project.saved?
-          present @project, with: Entities::Project,
-                            user_can_admin_project: can?(current_user, :admin_project, @project)
+        user = User.find_by(id: params.delete(:user_id))
+        not_found!('User') unless user
+
+        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+        project = ::Projects::CreateService.new(user, attrs).execute
+
+        if project.saved?
+          present project, with: Entities::Project,
+                           user_can_admin_project: can?(current_user, :admin_project, project)
         else
-          render_validation_error!(@project)
+          render_validation_error!(project)
         end
       end
+    end
+
+    params do
+      requires :id, type: String, desc: 'The ID of a project'
+    end
+    resource :projects, requirements: { id: /[^\/]+/ } do
+      desc 'Get a single project' do
+        success Entities::ProjectWithAccess
+      end
+      get ":id" do
+        entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
+        present user_project, with: entity, user: current_user,
+                              user_can_admin_project: can?(current_user, :admin_project, user_project)
+      end
 
-      # Fork new project for the current user or provided namespace.
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      #   namespace (optional) - The ID or name of the namespace that the project will be forked into.
-      # Example Request
-      #   POST /projects/fork/:id
+      desc 'Get events for a single project' do
+        success Entities::Event
+      end
+      params do
+        use :pagination
+      end
+      get ":id/events" do
+        present paginate(user_project.events.recent), with: Entities::Event
+      end
+
+      desc 'Fork new project for the current user or provided namespace.' do
+        success Entities::Project
+      end
+      params do
+        optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
+      end
       post 'fork/:id' do
-        attrs = {}
-        namespace_id = params[:namespace]
+        fork_params = declared_params(include_missing: false)
+        namespace_id = fork_params[:namespace]
 
         if namespace_id.present?
-          namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id)
+          fork_params[:namespace] = if namespace_id =~ /^\d+$/
+                                      Namespace.find_by(id: namespace_id)
+                                    else
+                                      Namespace.find_by_path_or_name(namespace_id)
+                                    end
 
-          unless namespace && can?(current_user, :create_projects, namespace)
+          unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
             not_found!('Target Namespace')
           end
-
-          attrs[:namespace] = namespace
         end
 
-        @forked_project =
-          ::Projects::ForkService.new(user_project,
-                                      current_user,
-                                      attrs).execute
+        forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
 
-        if @forked_project.errors.any?
-          conflict!(@forked_project.errors.messages)
+        if forked_project.errors.any?
+          conflict!(forked_project.errors.messages)
         else
-          present @forked_project, with: Entities::Project,
-                                   user_can_admin_project: can?(current_user, :admin_project, @forked_project)
+          present forked_project, with: Entities::Project,
+                                  user_can_admin_project: can?(current_user, :admin_project, forked_project)
         end
       end
 
-      # Update an existing project
-      #
-      # Parameters:
-      #   id (required) - the id of a project
-      #   name (optional) - name of a project
-      #   path (optional) - path of a project
-      #   description (optional) - short project description
-      #   issues_enabled (optional)
-      #   merge_requests_enabled (optional)
-      #   builds_enabled (optional)
-      #   wiki_enabled (optional)
-      #   snippets_enabled (optional)
-      #   container_registry_enabled (optional)
-      #   shared_runners_enabled (optional)
-      #   public (optional) - if true same as setting visibility_level = 20
-      #   visibility_level (optional) - visibility level of a project
-      #   public_builds (optional)
-      #   lfs_enabled (optional)
-      # Example Request
-      #   PUT /projects/:id
+      desc 'Update an existing project' do
+        success Entities::Project
+      end
+      params do
+        optional :name, type: String, desc: 'The name of the project'
+        optional :default_branch, type: String, desc: 'The default branch of the project'
+        optional :path, type: String, desc: 'The path of the repository'
+        use :optional_params
+        at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
+                        :wiki_enabled, :builds_enabled, :snippets_enabled,
+                        :shared_runners_enabled, :container_registry_enabled,
+                        :lfs_enabled, :public, :visibility_level, :public_builds,
+                        :request_access_enabled, :only_allow_merge_if_build_succeeds,
+                        :only_allow_merge_if_all_discussions_are_resolved, :path,
+                        :default_branch
+      end
       put ':id' do
-        attrs = attributes_for_keys [:builds_enabled,
-                                     :container_registry_enabled,
-                                     :default_branch,
-                                     :description,
-                                     :issues_enabled,
-                                     :lfs_enabled,
-                                     :merge_requests_enabled,
-                                     :name,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :path,
-                                     :public,
-                                     :public_builds,
-                                     :request_access_enabled,
-                                     :shared_runners_enabled,
-                                     :snippets_enabled,
-                                     :visibility_level,
-                                     :wiki_enabled,
-                                     :only_allow_merge_if_all_discussions_are_resolved]
-        attrs = map_public_to_visibility_level(attrs)
         authorize_admin_project
+        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
         authorize! :rename_project, user_project if attrs[:name].present?
-        if attrs[:visibility_level].present?
-          authorize! :change_visibility_level, user_project
-        end
+        authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
 
-        ::Projects::UpdateService.new(user_project,
-                                      current_user, attrs).execute
+        ::Projects::UpdateService.new(user_project, current_user, attrs).execute
 
         if user_project.errors.any?
           render_validation_error!(user_project)
@@ -297,12 +299,9 @@ module API
         end
       end
 
-      # Archive project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   PUT /projects/:id/archive
+      desc 'Archive a project' do
+        success Entities::Project
+      end
       post ':id/archive' do
         authorize!(:archive_project, user_project)
 
@@ -311,12 +310,9 @@ module API
         present user_project, with: Entities::Project
       end
 
-      # Unarchive project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   PUT /projects/:id/unarchive
+      desc 'Unarchive a project' do
+        success Entities::Project
+      end
       post ':id/unarchive' do
         authorize!(:archive_project, user_project)
 
@@ -325,12 +321,9 @@ module API
         present user_project, with: Entities::Project
       end
 
-      # Star project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   POST /projects/:id/star
+      desc 'Star a project' do
+        success Entities::Project
+      end
       post ':id/star' do
         if current_user.starred?(user_project)
           not_modified!
@@ -342,12 +335,9 @@ module API
         end
       end
 
-      # Unstar project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   DELETE /projects/:id/star
+      desc 'Unstar a project' do
+        success Entities::Project
+      end
       delete ':id/star' do
         if current_user.starred?(user_project)
           current_user.toggle_star(user_project)
@@ -359,67 +349,51 @@ module API
         end
       end
 
-      # Remove project
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   DELETE /projects/:id
+      desc 'Remove a project'
       delete ":id" do
         authorize! :remove_project, user_project
         ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
       end
 
-      # Mark this project as forked from another
-      #
-      # Parameters:
-      #   id: (required) - The ID of the project being marked as a fork
-      #   forked_from_id: (required) - The ID of the project it was forked from
-      # Example Request:
-      #   POST /projects/:id/fork/:forked_from_id
+      desc 'Mark this project as forked from another'
+      params do
+        requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
+      end
       post ":id/fork/:forked_from_id" do
         authenticated_as_admin!
-        forked_from_project = find_project(params[:forked_from_id])
-        unless forked_from_project.nil?
-          if user_project.forked_from_project.nil?
-            user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
-          else
-            render_api_error!("Project already forked", 409)
-          end
+
+        forked_from_project = find_project!(params[:forked_from_id])
+        not_found!("Source Project") unless forked_from_project
+
+        if user_project.forked_from_project.nil?
+          user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
         else
-          not_found!("Source Project")
+          render_api_error!("Project already forked", 409)
         end
       end
 
-      # Remove a forked_from relationship
-      #
-      # Parameters:
-      #   id: (required) - The ID of the project being marked as a fork
-      # Example Request:
-      #  DELETE /projects/:id/fork
+      desc 'Remove a forked_from relationship'
       delete ":id/fork" do
         authorize! :remove_fork_project, user_project
+
         if user_project.forked?
           user_project.forked_project_link.destroy
+        else
+          not_modified!
         end
       end
 
-      # Share project with group
-      #
-      # Parameters:
-      #   id (required)           - The ID of a project
-      #   group_id (required)     - The ID of a group
-      #   group_access (required) - Level of permissions for sharing
-      #   expires_at (optional)   - Share expiration date
-      #
-      # Example Request:
-      #   POST /projects/:id/share
+      desc 'Share the project with a group' do
+        success Entities::ProjectGroupLink
+      end
+      params do
+        requires :group_id, type: Integer, desc: 'The ID of a group'
+        requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
+        optional :expires_at, type: Date, desc: 'Share expiration date'
+      end
       post ":id/share" do
         authorize! :admin_project, user_project
-        required_attributes! [:group_id, :group_access]
-        attrs = attributes_for_keys [:group_id, :group_access, :expires_at]
-
-        group = Group.find_by_id(attrs[:group_id])
+        group = Group.find_by_id(params[:group_id])
 
         unless group && can?(current_user, :read_group, group)
           not_found!('Group')
@@ -429,7 +403,7 @@ module API
           return render_api_error!("The project sharing with group is disabled", 400)
         end
 
-        link = user_project.project_group_links.new(attrs)
+        link = user_project.project_group_links.new(declared_params(include_missing: false))
 
         if link.save
           present link, with: Entities::ProjectGroupLink
@@ -451,40 +425,26 @@ module API
         no_content!
       end
 
-      # Upload a file
-      #
-      # Parameters:
-      #   id: (required) - The ID of the project
-      #   file: (required) - The file to be uploaded
+      desc 'Upload a file'
+      params do
+        requires :file, type: File, desc: 'The file to be uploaded'
+      end
       post ":id/uploads" do
         ::Projects::UploadService.new(user_project, params[:file]).execute
       end
 
-      # search for projects current_user has access to
-      #
-      # Parameters:
-      #   query (required) - A string contained in the project name
-      #   per_page (optional) - number of projects to return per page
-      #   page (optional) - the page to retrieve
-      # Example Request:
-      #   GET /projects/search/:query
-      get "/search/:query" do
-        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
-        projects = search_service.objects('projects', params[:page])
-        projects = projects.reorder(project_order_by => project_sort)
-
-        present paginate(projects), with: Entities::Project
+      desc 'Get the users list of a project' do
+        success Entities::UserBasic
+      end
+      params do
+        optional :search, type: String, desc: 'Return list of users matching the search criteria'
+        use :pagination
       end
-
-      # Get a users list
-      #
-      # Example Request:
-      #  GET /users
       get ':id/users' do
-        @users = User.where(id: user_project.team.users.map(&:id))
-        @users = @users.search(params[:search]) if params[:search].present?
-        @users = paginate @users
-        present @users, with: Entities::UserBasic
+        users = user_project.team.users
+        users = users.search(params[:search]) if params[:search].present?
+
+        present paginate(users), with: Entities::UserBasic
       end
     end
   end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 4d23499aa39ac4b0a4cf83433f4f6e4b73d17664..bc427705777fc38ca3856ddfe99dbec23f2736a8 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -65,7 +65,7 @@ module API
         detail 'Added in GitLab 8.13'
       end
       post ':id/services/:service_slug/trigger' do
-        project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id])
+        project = find_project(params[:id])
 
         # This is not accurate, but done to prevent leakage of the project names
         not_found!('Service') unless project
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 569598fbd2cfb4c6821c40b43c87fd59c903cab1..bb4de39def1e78c0441b6781047609b88a7994e8 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -13,7 +13,7 @@ module API
         optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
       end
       post ":id/(ref/:ref/)trigger/builds" do
-        project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id])
+        project = find_project(params[:id])
         trigger = Ci::Trigger.find_by_token(params[:token].to_s)
         not_found! unless project && trigger
         unauthorized! unless trigger.project == project
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 90f904b8a1239f3c35569780146cbe79356b1bd7..f623b1dfe9f158af7876072db14416c3bde6566b 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -30,7 +30,7 @@ module API
       end
       get ':id/variables/:key' do
         key = params[:key]
-        variable = user_project.variables.find_by(key: key.to_s)
+        variable = user_project.variables.find_by(key: key)
 
         return not_found!('Variable') unless variable
 
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 0dfffaf0bc6d6d6ba5cc95995a027c72d8c4e67a..96c201005417bac32fd1d156c0281dd563d1c6af 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -14,7 +14,7 @@ module Backup
       s[:gitlab_version]     = Gitlab::VERSION
       s[:tar_version]        = tar_version
       s[:skipped]            = ENV["SKIP"]
-      tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar"
+      tar_file = s[:backup_created_at].strftime('%s_%Y_%m_%d') + '_gitlab_backup.tar'
 
       Dir.chdir(Gitlab.config.backup.path) do
         File.open("#{Gitlab.config.backup.path}/backup_information.yml",
@@ -83,10 +83,14 @@ module Backup
 
         Dir.chdir(Gitlab.config.backup.path) do
           file_list = Dir.glob('*_gitlab_backup.tar')
-          file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
-          file_list.sort.each do |timestamp|
-            if Time.at(timestamp) < (Time.now - keep_time)
-              if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar))
+          file_list.map! do |path_string|
+            if path_string =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/
+              { timestamp: $1.to_i, path: path_string }
+            end
+          end
+          file_list.sort.each do |file|
+            if Time.at(file[:timestamp]) < (Time.now - keep_time)
+              if Kernel.system(*%W(rm #{file[:path]}))
                 removed += 1
               end
             end
@@ -103,7 +107,7 @@ module Backup
       Dir.chdir(Gitlab.config.backup.path)
 
       # check for existing backups in the backup dir
-      file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
+      file_list = Dir.glob("*_gitlab_backup.tar")
       puts "no backups found" if file_list.count == 0
 
       if file_list.count > 1 && ENV["BACKUP"].nil?
@@ -112,7 +116,7 @@ module Backup
         exit 1
       end
 
-      tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
+      tar_file = ENV["BACKUP"].nil? ? file_list.first : file_list.grep(ENV['BACKUP']).first
 
       unless File.exist?(tar_file)
         puts "The specified backup doesn't exist!"
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index a4eda6fdf7685e6092189fbd3a8d928c9ad4173f..8e7084f2543649e9bf7a6e737f068afbeeb23510 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -35,9 +35,11 @@ module Banzai
           headers[id] += 1
 
           if header_content = node.children.first
+            # namespace detection will be automatically handled via javascript (see issue #22781)
+            namespace = "user-content-"
             href = "#{id}#{uniq}"
             push_toc(href, text)
-            header_content.add_previous_sibling(anchor_tag(href))
+            header_content.add_previous_sibling(anchor_tag("#{namespace}#{href}", href))
           end
         end
 
@@ -48,8 +50,8 @@ module Banzai
 
       private
 
-      def anchor_tag(href)
-        %Q{<a id="#{href}" class="anchor" href="##{href}" aria-hidden="true"></a>}
+      def anchor_tag(id, href)
+        %Q{<a id="#{id}" class="anchor" href="##{href}" aria-hidden="true"></a>}
       end
 
       def push_toc(href, text)
diff --git a/lib/email_template_interceptor.rb b/lib/email_template_interceptor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb04a7824b82ef9a6efa180871e0b1d8ebc50aaa
--- /dev/null
+++ b/lib/email_template_interceptor.rb
@@ -0,0 +1,13 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class EmailTemplateInterceptor
+  include Gitlab::CurrentSettings
+
+  def self.delivering_email(message)
+    # Remove HTML part if HTML emails are disabled.
+    unless current_application_settings.html_emails_enabled
+      message.part.delete_if do |part|
+        part.content_type.try(:start_with?, 'text/html')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb
index e59d69b72b90af005f7d7348d1b8f7e93c4178b6..25da8474e95a0f7c43c507b3f18be238ef849c63 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/chat_commands/base_command.rb
@@ -40,9 +40,7 @@ module Gitlab
       private
 
       def find_by_iid(iid)
-        resource = collection.find_by(iid: iid)
-
-        readable?(resource) ? resource : nil
+        collection.find_by(iid: iid)
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 0ec358debc7d4df00f5d1733b95e64f990f4a93c..b0d3fdbc48ae321aa3981ee0d568ec0401ed2ae0 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -4,6 +4,7 @@ module Gitlab
       COMMANDS = [
         Gitlab::ChatCommands::IssueShow,
         Gitlab::ChatCommands::IssueCreate,
+        Gitlab::ChatCommands::IssueSearch,
         Gitlab::ChatCommands::Deploy,
       ].freeze
 
diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/chat_commands/issue_command.rb
index f1bc36239d5250951e08167634789e8bb4f8d456..84de3e44c70c8c3ea28f26ddc7f7748783db6304 100644
--- a/lib/gitlab/chat_commands/issue_command.rb
+++ b/lib/gitlab/chat_commands/issue_command.rb
@@ -6,11 +6,7 @@ module Gitlab
       end
 
       def collection
-        project.issues
-      end
-
-      def readable?(issue)
-        self.class.can?(current_user, :read_issue, issue)
+        IssuesFinder.new(current_user, project_id: project.id).execute
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51bf80c800b73f4fe09292859b8f68e91b16ac29
--- /dev/null
+++ b/lib/gitlab/chat_commands/issue_search.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module ChatCommands
+    class IssueSearch < IssueCommand
+      def self.match(text)
+        /\Aissue\s+search\s+(?<query>.*)/.match(text)
+      end
+
+      def self.help_message
+        "issue search <your query>"
+      end
+
+      def execute(match)
+        collection.search(match[:query]).limit(QUERY_LIMIT)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb
index f5bceb038e5aeb8dbf2821abe6a2e05fa9d9e143..2a45d49cf6b30824f5672ccfcebb1e9172ced818 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/chat_commands/issue_show.rb
@@ -2,7 +2,7 @@ module Gitlab
   module ChatCommands
     class IssueShow < IssueCommand
       def self.match(text)
-        /\Aissue\s+show\s+(?<iid>\d+)/.match(text)
+        /\Aissue\s+show\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text)
       end
 
       def self.help_message
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 2d5c92324257a230be7fbd4612b9299fea7721bc..55b8f888d534cb631c53015ad50a17880f9e44d0 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -35,13 +35,6 @@ module Gitlab
       order
     end
 
-    def self.serialized_transaction
-      opts = {}
-      opts[:isolation] = :serializable unless Rails.env.test? && connection.transaction_open?
-
-      connection.transaction(opts) { yield }
-    end
-
     def self.random
       Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()"
     end
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index fe7adb7bed671fe942d346a922c84a8ae50a376a..56530448f3603475b68161bf684d40a8df20f9ea 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -20,7 +20,7 @@ module Gitlab
         # Extracted method to highlight in the same iteration to the diff_collection.
         def decorate_diff!(diff)
           diff_file = super
-          cache_highlight!(diff_file) if cacheable?
+          cache_highlight!(diff_file) if cacheable?(diff_file)
           diff_file
         end
 
@@ -60,8 +60,8 @@ module Gitlab
           Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
         end
 
-        def cacheable?
-          @merge_request_diff.present?
+        def cacheable?(diff_file)
+          @merge_request_diff.present? && diff_file.blob && diff_file.blob.text?
         end
 
         def cache_key
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index bcbf6455998b2121e7a5718b3db411bdeb1f842b..db07b7c5fcc5756dd160e44f7b3cee02c41a96ab 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -46,7 +46,7 @@ module Gitlab
     def download_access_check
       if user
         user_download_access_check
-      elsif deploy_key.nil? && !Guest.can?(:download_code, project)
+      elsif deploy_key.nil? && !guest_can_downlod_code?
         raise UnauthorizedError, ERROR_MESSAGES[:download]
       end
     end
@@ -59,6 +59,10 @@ module Gitlab
       end
     end
 
+    def guest_can_downlod_code?
+      Guest.can?(:download_code, project)
+    end
+
     def user_download_access_check
       unless user_can_download_code? || build_can_download_code?
         raise UnauthorizedError, ERROR_MESSAGES[:download]
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index f71d3575909c68567203edd33958e3da97b5dea9..2c06c4ff1ef0cfb7eaea2c1efcbeebb2f46009ec 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,5 +1,13 @@
 module Gitlab
   class GitAccessWiki < GitAccess
+    def guest_can_downlod_code?
+      Guest.can?(:download_wiki_code, project)
+    end
+
+    def user_can_download_code?
+      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
+    end
+
     def change_access_check(change)
       if user_access.can_do_action?(:create_wiki)
         build_status_object(true)
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
index 4750675ae9ddf75afb954de6da314c393c40f18a..0a8d05b5fe1d327d672ee7e1d7d9531aa1e9a687 100644
--- a/lib/gitlab/github_import/branch_formatter.rb
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -8,7 +8,7 @@ module Gitlab
       end
 
       def valid?
-        repo.present?
+        sha.present? && ref.present?
       end
 
       private
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index a8b4dc2a83fe9d179db747eae3397061578398e9..96ed20af9189982598973cccf44fa054250bae97 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -39,7 +39,7 @@ module Gitlab
         log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
         gl_user
       rescue ActiveRecord::RecordInvalid => e
-        log.info "(#{provider}) Error saving user: #{gl_user.errors.full_messages}"
+        log.info "(#{provider}) Error saving user #{auth_hash.uid} (#{auth_hash.email}): #{gl_user.errors.full_messages}"
         return self, e.record.errors
       end
 
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 2690938fe820c6cc5b442c3209b3bc497a254b3b..47d8599e298dcad989bcfb502c2529b4d38201d5 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -50,7 +50,7 @@ module Gitlab
     end
 
     def issues
-      issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation)
+      issues = IssuesFinder.new(current_user).execute.where(project_id: project_ids_relation)
 
       if query =~ /#(\d+)\z/
         issues = issues.where(iid: $1)
diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake
new file mode 100644
index 0000000000000000000000000000000000000000..dd2d58614814adbd411814ef173121ebad901b34
--- /dev/null
+++ b/lib/tasks/gitlab/helpers.rake
@@ -0,0 +1,8 @@
+require 'tasks/gitlab/task_helpers'
+
+# Prevent StateMachine warnings from outputting during a cron task
+StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
+
+namespace :gitlab do
+  include Gitlab::TaskHelpers
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 58761a129d42a41fb078d75990413bdee08e1505..5a09cd7ce41decaaac0e1945afdf0c81acc69f15 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -5,42 +5,23 @@ namespace :gitlab do
       warn_user_is_not_gitlab
 
       default_version = Gitlab::Shell.version_required
-      default_version_tag = 'v' + default_version
-      args.with_defaults(tag: default_version_tag, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
+      default_version_tag = "v#{default_version}"
+      args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
 
-      user = Gitlab.config.gitlab.user
-      home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
       gitlab_url = Gitlab.config.gitlab.url
       # gitlab-shell requires a / at the end of the url
       gitlab_url += '/' unless gitlab_url.end_with?('/')
       target_dir = Gitlab.config.gitlab_shell.path
 
-      # Clone if needed
-      if File.directory?(target_dir)
-        Dir.chdir(target_dir) do
-          system(*%W(Gitlab.config.git.bin_path} fetch --tags --quiet))
-          system(*%W(Gitlab.config.git.bin_path} checkout --quiet #{default_version_tag}))
-        end
-      else
-        system(*%W(#{Gitlab.config.git.bin_path} clone -- #{args.repo} #{target_dir}))
-      end
+      checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir)
 
       # Make sure we're on the right tag
       Dir.chdir(target_dir) do
-        # First try to checkout without fetching
-        # to avoid stalling tests if the Internet is down.
-        reseted = reset_to_commit(args)
-
-        unless reseted
-          system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
-          reset_to_commit(args)
-        end
-
         config = {
-          user: user,
+          user: Gitlab.config.gitlab.user,
           gitlab_url: gitlab_url,
           http_settings: {self_signed_cert: false}.stringify_keys,
-          auth_file: File.join(home_dir, ".ssh", "authorized_keys"),
+          auth_file: File.join(user_home, ".ssh", "authorized_keys"),
           redis: {
             bin: %x{which redis-cli}.chomp,
             namespace: "resque:gitlab"
@@ -74,7 +55,7 @@ namespace :gitlab do
       # be an issue since it is more than likely that there are no "normal"
       # user accounts on a gitlab server). The alternative is for the admin to
       # install a ruby (1.9.3+) in the global path.
-      File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f|
+      File.open(File.join(user_home, ".ssh", "environment"), "w+") do |f|
         f.puts "PATH=#{ENV['PATH']}"
       end
 
@@ -142,15 +123,4 @@ namespace :gitlab do
     puts "Quitting...".color(:red)
     exit 1
   end
-
-  def reset_to_commit(args)
-    tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- #{args.tag}))
-
-    unless status.zero?
-      tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- origin/#{args.tag}))
-    end
-
-    tag = tag.strip
-    system(*%W(#{Gitlab.config.git.bin_path} reset --hard #{tag}))
-  end
 end
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
deleted file mode 100644
index 74be413423aa9ece819d105ff0d4b09d153ac63a..0000000000000000000000000000000000000000
--- a/lib/tasks/gitlab/task_helpers.rake
+++ /dev/null
@@ -1,140 +0,0 @@
-module Gitlab
-  class TaskAbortedByUserError < StandardError; end
-end
-
-require 'rainbow/ext/string'
-
-# Prevent StateMachine warnings from outputting during a cron task
-StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
-
-namespace :gitlab do
-
-  # Ask if the user wants to continue
-  #
-  # Returns "yes" the user chose to continue
-  # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue
-  def ask_to_continue
-    answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no})
-    raise Gitlab::TaskAbortedByUserError unless answer == "yes"
-  end
-
-  # Check which OS is running
-  #
-  # It will primarily use lsb_relase to determine the OS.
-  # It has fallbacks to Debian, SuSE, OS X and systems running systemd.
-  def os_name
-    os_name = run_command(%W(lsb_release -irs))
-    os_name ||= if File.readable?('/etc/system-release')
-                  File.read('/etc/system-release')
-                end
-    os_name ||= if File.readable?('/etc/debian_version')
-                  debian_version = File.read('/etc/debian_version')
-                  "Debian #{debian_version}"
-                end
-    os_name ||= if File.readable?('/etc/SuSE-release')
-                  File.read('/etc/SuSE-release')
-                end
-    os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion))
-                  "Mac OS X #{os_x_version}"
-                end
-    os_name ||= if File.readable?('/etc/os-release')
-                  File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
-                end
-    os_name.try(:squish!)
-  end
-
-  # Prompt the user to input something
-  #
-  # message - the message to display before input
-  # choices - array of strings of acceptable answers or nil for any answer
-  #
-  # Returns the user's answer
-  def prompt(message, choices = nil)
-    begin
-      print(message)
-      answer = STDIN.gets.chomp
-    end while choices.present? && !choices.include?(answer)
-    answer
-  end
-
-  # Runs the given command and matches the output against the given pattern
-  #
-  # Returns nil if nothing matched
-  # Returns the MatchData if the pattern matched
-  #
-  # see also #run_command
-  # see also String#match
-  def run_and_match(command, regexp)
-    run_command(command).try(:match, regexp)
-  end
-
-  # Runs the given command
-  #
-  # Returns nil if the command was not found
-  # Returns the output of the command otherwise
-  #
-  # see also #run_and_match
-  def run_command(command)
-    output, _ = Gitlab::Popen.popen(command)
-    output
-  rescue Errno::ENOENT
-    '' # if the command does not exist, return an empty string
-  end
-
-  def uid_for(user_name)
-    run_command(%W(id -u #{user_name})).chomp.to_i
-  end
-
-  def gid_for(group_name)
-    begin
-      Etc.getgrnam(group_name).gid
-    rescue ArgumentError # no group
-      "group #{group_name} doesn't exist"
-    end
-  end
-
-  def warn_user_is_not_gitlab
-    unless @warned_user_not_gitlab
-      gitlab_user = Gitlab.config.gitlab.user
-      current_user = run_command(%W(whoami)).chomp
-      unless current_user == gitlab_user
-        puts " Warning ".color(:black).background(:yellow)
-        puts "  You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
-        puts "  Things may work\/fail for the wrong reasons."
-        puts "  For correct results you should run this as user #{gitlab_user.color(:magenta)}."
-        puts ""
-      end
-      @warned_user_not_gitlab = true
-    end
-  end
-
-  # Tries to configure git itself
-  #
-  # Returns true if all subcommands were successfull (according to their exit code)
-  # Returns false if any or all subcommands failed.
-  def auto_fix_git_config(options)
-    if !@warned_user_not_gitlab
-      command_success = options.map do |name, value|
-        system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
-      end
-
-      command_success.all?
-    else
-      false
-    end
-  end
-
-  def all_repos
-    Gitlab.config.repositories.storages.each do |name, path|
-      IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
-        find.each_line do |path|
-          yield path.chomp
-        end
-      end
-    end
-  end
-
-  def repository_storage_paths_args
-    Gitlab.config.repositories.storages.values
-  end
-end
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e128738b5f8cb2ca92a9a6f0ffcc8e4037bfe8fb
--- /dev/null
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -0,0 +1,190 @@
+require 'rainbow/ext/string'
+
+module Gitlab
+  TaskFailedError = Class.new(StandardError)
+  TaskAbortedByUserError = Class.new(StandardError)
+
+  module TaskHelpers
+    # Ask if the user wants to continue
+    #
+    # Returns "yes" the user chose to continue
+    # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue
+    def ask_to_continue
+      answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no})
+      raise Gitlab::TaskAbortedByUserError unless answer == "yes"
+    end
+
+    # Check which OS is running
+    #
+    # It will primarily use lsb_relase to determine the OS.
+    # It has fallbacks to Debian, SuSE, OS X and systems running systemd.
+    def os_name
+      os_name = run_command(%W(lsb_release -irs))
+      os_name ||= if File.readable?('/etc/system-release')
+        File.read('/etc/system-release')
+      end
+      os_name ||= if File.readable?('/etc/debian_version')
+        debian_version = File.read('/etc/debian_version')
+        "Debian #{debian_version}"
+      end
+      os_name ||= if File.readable?('/etc/SuSE-release')
+        File.read('/etc/SuSE-release')
+      end
+      os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion))
+        "Mac OS X #{os_x_version}"
+      end
+      os_name ||= if File.readable?('/etc/os-release')
+        File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
+      end
+      os_name.try(:squish!)
+    end
+
+    # Prompt the user to input something
+    #
+    # message - the message to display before input
+    # choices - array of strings of acceptable answers or nil for any answer
+    #
+    # Returns the user's answer
+    def prompt(message, choices = nil)
+      begin
+        print(message)
+        answer = STDIN.gets.chomp
+      end while choices.present? && !choices.include?(answer)
+      answer
+    end
+
+    # Runs the given command and matches the output against the given pattern
+    #
+    # Returns nil if nothing matched
+    # Returns the MatchData if the pattern matched
+    #
+    # see also #run_command
+    # see also String#match
+    def run_and_match(command, regexp)
+      run_command(command).try(:match, regexp)
+    end
+
+    # Runs the given command
+    #
+    # Returns '' if the command was not found
+    # Returns the output of the command otherwise
+    #
+    # see also #run_and_match
+    def run_command(command)
+      output, _ = Gitlab::Popen.popen(command)
+      output
+    rescue Errno::ENOENT
+      '' # if the command does not exist, return an empty string
+    end
+
+    # Runs the given command and raises a Gitlab::TaskFailedError exception if
+    # the command does not exit with 0
+    #
+    # Returns the output of the command otherwise
+    def run_command!(command)
+      output, status = Gitlab::Popen.popen(command)
+
+      raise Gitlab::TaskFailedError unless status.zero?
+
+      output
+    end
+
+    def uid_for(user_name)
+      run_command(%W(id -u #{user_name})).chomp.to_i
+    end
+
+    def gid_for(group_name)
+      begin
+        Etc.getgrnam(group_name).gid
+      rescue ArgumentError # no group
+        "group #{group_name} doesn't exist"
+      end
+    end
+
+    def warn_user_is_not_gitlab
+      unless @warned_user_not_gitlab
+        gitlab_user = Gitlab.config.gitlab.user
+        current_user = run_command(%W(whoami)).chomp
+        unless current_user == gitlab_user
+          puts " Warning ".color(:black).background(:yellow)
+          puts "  You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
+          puts "  Things may work\/fail for the wrong reasons."
+          puts "  For correct results you should run this as user #{gitlab_user.color(:magenta)}."
+          puts ""
+        end
+        @warned_user_not_gitlab = true
+      end
+    end
+
+    # Tries to configure git itself
+    #
+    # Returns true if all subcommands were successfull (according to their exit code)
+    # Returns false if any or all subcommands failed.
+    def auto_fix_git_config(options)
+      if !@warned_user_not_gitlab
+        command_success = options.map do |name, value|
+          system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
+        end
+
+        command_success.all?
+      else
+        false
+      end
+    end
+
+    def all_repos
+      Gitlab.config.repositories.storages.each do |name, path|
+        IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
+          find.each_line do |path|
+            yield path.chomp
+          end
+        end
+      end
+    end
+
+    def repository_storage_paths_args
+      Gitlab.config.repositories.storages.values
+    end
+
+    def user_home
+      Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
+    end
+
+    def checkout_or_clone_tag(tag:, repo:, target_dir:)
+      if Dir.exist?(target_dir)
+        checkout_tag(tag, target_dir)
+      else
+        clone_repo(repo, target_dir)
+      end
+
+      reset_to_tag(tag, target_dir)
+    end
+
+    def clone_repo(repo, target_dir)
+      run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
+    end
+
+    def checkout_tag(tag, target_dir)
+      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet])
+      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}])
+    end
+
+    def reset_to_tag(tag_wanted, target_dir)
+      tag =
+      begin
+        # First try to checkout without fetching
+        # to avoid stalling tests if the Internet is down.
+        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
+      rescue Gitlab::TaskFailedError
+        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
+        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
+      end
+
+      if tag
+        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])
+      else
+        raise Gitlab::TaskFailedError
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
new file mode 100644
index 0000000000000000000000000000000000000000..baea94bf8ca0456fb9e26b31cf9eed11ffb41858
--- /dev/null
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -0,0 +1,23 @@
+namespace :gitlab do
+  namespace :workhorse do
+    desc "GitLab | Install or upgrade gitlab-workhorse"
+    task :install, [:dir] => :environment do |t, args|
+      warn_user_is_not_gitlab
+      unless args.dir.present?
+        abort %(Please specify the directory where you want to install gitlab-workhorse:\n  rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]")
+      end
+
+      tag = "v#{Gitlab::Workhorse.version}"
+      repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git'
+
+      checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir)
+
+      _, status = Gitlab::Popen.popen(%w[which gmake])
+      command = status.zero? ? 'gmake' : 'make'
+
+      Dir.chdir(args.dir) do
+        run_command!([command])
+      end
+    end
+  end
+end
diff --git a/package.json b/package.json
index 350e4cd80c9a740c01a6917a9d29524153c0ee0f..961989f80127298b1f7c61424c1eb193cdb12172 100644
--- a/package.json
+++ b/package.json
@@ -6,13 +6,11 @@
     "eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html"
   },
   "devDependencies": {
-    "eslint": "^3.1.1",
-    "eslint-config-airbnb": "^12.0.0",
+    "eslint": "^3.10.1",
+    "eslint-config-airbnb-base": "^10.0.1",
     "eslint-plugin-filenames": "^1.1.0",
-    "eslint-plugin-import": "^1.16.0",
-    "eslint-plugin-jasmine": "^1.8.1",
-    "eslint-plugin-jsx-a11y": "^2.2.3",
-    "eslint-plugin-react": "^6.4.1",
+    "eslint-plugin-import": "^2.2.0",
+    "eslint-plugin-jasmine": "^2.1.0",
     "istanbul": "^0.4.5"
   }
 }
diff --git a/public/404.html b/public/404.html
index 92b7f4da0b9b577e722f093589b2b033e66b66d7..11b29d09a82981401004e1d4da06b65bfc28cbb7 100644
--- a/public/404.html
+++ b/public/404.html
@@ -42,6 +42,8 @@
 
     img {
       max-width: 40vw;
+      display: block;
+      margin: 40px auto;
     }
 
     .container {
@@ -51,8 +53,9 @@
 </head>
 
 <body>
+  <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+       alt="GitLab Logo" />
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     404
   </h1>
   <div class="container">
diff --git a/public/422.html b/public/422.html
index f625f8a33b7558d12f8c41430db97646a60808a7..9bd7cb4b7c82f7c4aac87ac639a1670cd30d4982 100644
--- a/public/422.html
+++ b/public/422.html
@@ -42,6 +42,8 @@
 
     img {
       max-width: 40vw;
+      display: block;
+      margin: 40px auto;
     }
 
     .container {
@@ -51,8 +53,9 @@
 </head>
 
 <body>
+  <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+       alt="GitLab Logo" />
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     422
   </h1>
   <div class="container">
diff --git a/public/500.html b/public/500.html
index d76c66ba92a1aadc0a417d1bda7ac922a8011e71..f92e8839f8d9601e53373a810a5f4281c1ab3598 100644
--- a/public/500.html
+++ b/public/500.html
@@ -42,6 +42,8 @@
 
     img {
       max-width: 40vw;
+      display: block;
+      margin: 40px auto;
     }
 
     .container {
@@ -51,8 +53,9 @@
 </head>
 
 <body>
+  <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+       alt="GitLab Logo" />
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     500
   </h1>
   <div class="container">
diff --git a/public/502.html b/public/502.html
index 1a3c7efc7696256e850b77ac87dd27b2a8cbb15f..c2be4f130a95d603aa480053a378385e7ec5fc5a 100644
--- a/public/502.html
+++ b/public/502.html
@@ -42,6 +42,8 @@
 
     img {
       max-width: 40vw;
+      display: block;
+      margin: 40px auto;
     }
 
     .container {
@@ -51,8 +53,9 @@
 </head>
 
 <body>
+  <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+       alt="GitLab Logo" />
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     502
   </h1>
   <div class="container">
diff --git a/public/503.html b/public/503.html
index c1c4e3ffdb8e85e299a9e7699adc1f40004ac7c7..8850ffce362bc91f7053aee28d3a0c358a907848 100644
--- a/public/503.html
+++ b/public/503.html
@@ -42,6 +42,8 @@
 
     img {
       max-width: 40vw;
+      display: block;
+      margin: 40px auto;
     }
 
     .container {
@@ -51,8 +53,9 @@
 </head>
 
 <body>
+  <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+       alt="GitLab Logo" />
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     503
   </h1>
   <div class="container">
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index d9a86346c8138466b3592fc10a349e4ea69962a1..ea2fd90a9b013d02983d24cc7135794709a10b87 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -4,7 +4,7 @@ describe AutocompleteController do
   let!(:project) { create(:project) }
   let!(:user) { create(:user) }
 
-  context 'users and members' do
+  context 'GET users' do
     let!(:user2) { create(:user) }
     let!(:non_member) { create(:user) }
 
@@ -144,6 +144,15 @@ describe AutocompleteController do
         it { expect(body).to be_kind_of(Array) }
         it { expect(body.size).to eq 0 }
       end
+
+      describe 'GET #users with todo filter' do
+        it 'gives an array of users' do
+          get :users, todo_filter: true
+
+          expect(response.status).to eq 200
+          expect(body).to be_kind_of(Array)
+        end
+      end
     end
 
     context 'author of issuable included' do
@@ -180,7 +189,7 @@ describe AutocompleteController do
     end
   end
 
-  context 'projects' do
+  context 'GET projects' do
     let(:authorized_project) { create(:project) }
     let(:authorized_search_project) { create(:project, name: 'rugged') }
 
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 6fc6ea95e13803527d66c13bb769c34d2d597b4e..d3489324a9cff4176d4135b2b77a70d11a0b1418 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -8,35 +8,33 @@ describe HelpController do
   end
 
   describe 'GET #index' do
-    context 'when url prefixed without /help/' do
-      it 'has correct url prefix' do
-        stub_readme("[API](api/README.md)")
+    context 'with absolute url' do
+      it 'keeps the URL absolute' do
+        stub_readme("[API](/api/README.md)")
+
         get :index
-        expect(assigns[:help_index]).to eq '[API](/help/api/README.md)'
+
+        expect(assigns[:help_index]).to eq '[API](/api/README.md)'
       end
     end
 
-    context 'when url prefixed with help/' do
-      it 'will be an absolute path' do
-        stub_readme("[API](help/api/README.md)")
+    context 'with relative url' do
+      it 'prefixes it with /help/' do
+        stub_readme("[API](api/README.md)")
+
         get :index
+
         expect(assigns[:help_index]).to eq '[API](/help/api/README.md)'
       end
     end
 
-    context 'when url prefixed with help' do
-      it 'will be an absolute path' do
-        stub_readme("[API](helpful_hints/README.md)")
-        get :index
-        expect(assigns[:help_index]).to eq '[API](/help/helpful_hints/README.md)'
-      end
-    end
+    context 'when url is an external link' do
+      it 'does not change it' do
+        stub_readme("[external](https://some.external.link)")
 
-    context 'when url prefixed with /help/' do
-      it 'will not be changed' do
-        stub_readme("[API](/help/api/README.md)")
         get :index
-        expect(assigns[:help_index]).to eq '[API](/help/api/README.md)'
+
+        expect(assigns[:help_index]).to eq '[external](https://some.external.link)'
       end
     end
   end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 52d13fb6f9e11527c38ff69e12ea873eaf3084f2..3efef757ae2f6a241d17ef15ac4672bd20832cac 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -36,4 +36,53 @@ describe Projects::BlobController do
       end
     end
   end
+
+  describe 'PUT update' do
+    let(:default_params) do
+      {
+        namespace_id: project.namespace.to_param,
+        project_id: project.to_param,
+        id: 'master/CHANGELOG',
+        target_branch: 'master',
+        content: 'Added changes',
+        commit_message: 'Update CHANGELOG'
+      }
+    end
+
+    def blob_after_edit_path
+      namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')
+    end
+
+    it 'redirects to blob' do
+      put :update, default_params
+
+      expect(response).to redirect_to(blob_after_edit_path)
+    end
+
+    context '?from_merge_request_iid' do
+      let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+      let(:mr_params) { default_params.merge(from_merge_request_iid: merge_request.iid) }
+
+      it 'redirects to MR diff' do
+        put :update, mr_params
+
+        after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+        file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}"
+        expect(response).to redirect_to(after_edit_path + file_anchor)
+      end
+
+      context "when user doesn't have access" do
+        before do
+          other_project = create(:empty_project)
+          merge_request.update!(source_project: other_project, target_project: other_project)
+        end
+
+        it "it redirect to blob" do
+          put :update, mr_params
+
+          expect(response).to redirect_to(blob_after_edit_path)
+        end
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index f7cf006efd66a6f09b87a1ddc544d6941d4d2c93..b88586b8678352b0016991ade107d9636dd4fe54 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -94,6 +94,24 @@ describe Projects::BranchesController do
           branch_name: branch,
           issue_iid: issue.iid
       end
+
+      context 'without issue feature access' do
+        before do
+          project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+          project.team.truncate
+        end
+
+        it "doesn't post a system note" do
+          expect(SystemNoteService).not_to receive(:new_issue_branch)
+
+          post :create,
+            namespace_id: project.namespace.to_param,
+            project_id: project.to_param,
+            branch_name: branch,
+            issue_iid: issue.iid
+        end
+      end
     end
   end
 
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 7c5f33c63b8f731bbb327434cde4f8fdd4b34da4..6d30d085056de1631e006dbdd8720c23c1358a53 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -31,7 +31,7 @@ describe Projects::MilestonesController do
 
       # Check system note left for milestone removal
       last_note = project.issues.find(issue.id).notes[-1].note
-      expect(last_note).to eq('Milestone removed')
+      expect(last_note).to eq('removed milestone')
     end
   end
 end
diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb
index 936320a37090d756d789f88c8bce9de589bddce4..193a3f6b5a3a4411921c8e8acdd59f1d88bf9962 100644
--- a/spec/controllers/projects/todo_controller_spec.rb
+++ b/spec/controllers/projects/todo_controller_spec.rb
@@ -4,7 +4,7 @@ describe Projects::TodosController do
   include ApiHelpers
 
   let(:user)          { create(:user) }
-  let(:project)       { create(:project) }
+  let(:project)       { create(:empty_project) }
   let(:issue)         { create(:issue, project: project) }
   let(:merge_request) { create(:merge_request, source_project: project) }
 
@@ -42,7 +42,7 @@ describe Projects::TodosController do
         end
       end
 
-      context 'when not authorized' do
+      context 'when not authorized for project' do
         it 'does not create todo for issue that user has no access to' do
           sign_in(user)
           expect do
@@ -60,6 +60,19 @@ describe Projects::TodosController do
           expect(response).to have_http_status(302)
         end
       end
+
+      context 'when not authorized for issue' do
+        before do
+          project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+          project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+          sign_in(user)
+        end
+
+        it "doesn't create todo" do
+          expect{ go }.not_to change { user.todos.count }
+          expect(response).to have_http_status(404)
+        end
+      end
     end
   end
 
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 2d762fdaa04899539137e6ab0e952345970b804c..d76fe9f580fe95e1c211ace9254f11e73779b145 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -3,6 +3,28 @@ require 'spec_helper'
 describe SnippetsController do
   let(:user) { create(:user) }
 
+  describe 'GET #new' do
+    context 'when signed in' do
+      before do
+        sign_in(user)
+      end
+
+      it 'responds with status 200' do
+        get :new
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    context 'when not signed in' do
+      it 'redirects to the sign in page' do
+        get :new
+
+        expect(response).to redirect_to(new_user_session_path)
+      end
+    end
+  end
+
   describe 'GET #show' do
     context 'when the personal snippet is private' do
       let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index eb20bd7dd583012a58ab0fcf0b5f82764db52ac8..c443af09075536330e401acce0ebfe9368fdad55 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -38,6 +38,10 @@ FactoryGirl.define do
       status 'canceled'
     end
 
+    trait :skipped do
+      status 'skipped'
+    end
+
     trait :running do
       status 'running'
     end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index ac2a1ba5dffb66bb1f1bffb1d359e5e4377272c8..1735791f6441bcbde223610b1ddb41979c36fc99 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -7,26 +7,30 @@ FactoryGirl.define do
     project factory: :empty_project
 
     factory :ci_pipeline_without_jobs do
-      after(:build) do |commit|
-        allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) }
+      after(:build) do |pipeline|
+        allow(pipeline).to receive(:ci_yaml_file) { YAML.dump({}) }
       end
     end
 
     factory :ci_pipeline_with_one_job do
-      after(:build) do |commit|
-        allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) }
-      end
-    end
-
-    factory :ci_pipeline_with_two_job do
-      after(:build) do |commit|
-        allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) }
+      after(:build) do |pipeline|
+        allow(pipeline).to receive(:ci_yaml_file) do
+          YAML.dump({ rspec: { script: "ls" } })
+        end
       end
     end
 
     factory :ci_pipeline do
-      after(:build) do |commit|
-        allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
+      transient { config nil }
+
+      after(:build) do |pipeline, evaluator|
+        allow(pipeline).to receive(:ci_yaml_file) do
+          if evaluator.config
+            YAML.dump(evaluator.config)
+          else
+            File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+          end
+        end
       end
     end
   end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 995f2080f1008e05497b639ec0267972bc90c8f7..756b341ecbad33af139ae0cd5a5789d353de7907 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -19,6 +19,10 @@ FactoryGirl.define do
       status 'canceled'
     end
 
+    trait :skipped do
+      status 'skipped'
+    end
+
     trait :running do
       status 'running'
     end
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..562ace925985b430d3144ed31b2e48919aa2a460
--- /dev/null
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe 'Admin browse spam logs' do
+  let!(:spam_log) { create(:spam_log) }
+
+  before do
+    login_as :admin
+  end
+
+  scenario 'Browse spam logs' do
+    visit admin_spam_logs_path
+
+    expect(page).to have_content('Spam Logs')
+    expect(page).to have_content(spam_log.source_ip)
+    expect(page).to have_content(spam_log.noteable_type)
+    expect(page).to have_content('N')
+    expect(page).to have_content(spam_log.title)
+    expect(page).to have_content("#{spam_log.description[0...97]}...")
+    expect(page).to have_link('Remove user')
+    expect(page).to have_link('Block user')
+  end
+end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index f160052a8448624e6e6c134c6162e04c59634905..c16aafa14704db7306232cd7ac31b30297bcc0bb 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -304,8 +304,8 @@ describe 'Issue Boards', feature: true, js: true do
 
       page.within('.subscription') do
         click_button 'Subscribe'
-
-        expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.")
+        wait_for_ajax
+        expect(page).to have_content("Unsubscribe")
       end
     end
   end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index e2101b333e23170003718cf079eec9f36bcc8c7d..4319d6db0d25301fdfe0e6e7fe2711c2abac60ae 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -10,4 +10,37 @@ describe 'Help Pages', feature: true do
       expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
     end
   end
+
+  describe 'Get the main help page' do
+    shared_examples_for 'help page' do |prefix: ''|
+      it 'prefixes links correctly' do
+        expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"]))
+      end
+    end
+
+    context 'without a trailing slash' do
+      before do
+        visit help_path
+      end
+
+      it_behaves_like 'help page'
+    end
+
+    context 'with a trailing slash' do
+      before do
+        visit help_path + '/'
+      end
+
+      it_behaves_like 'help page'
+    end
+
+    context 'with a relative installation' do
+      before do
+        stub_config_setting(relative_url_root: '/gitlab')
+        visit help_path
+      end
+
+      it_behaves_like 'help page', prefix: '/gitlab'
+    end
+  end
 end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c421da97d76bdc1a3698253f374b45002d386ee7
--- /dev/null
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+feature 'GFM autocomplete', feature: true, js: true do
+  include WaitForAjax
+  let(:user)    { create(:user) }
+  let(:project) { create(:project) }
+  let(:issue)   { create(:issue, project: project) }
+
+  before do
+    project.team << [user, :master]
+    login_as(user)
+    visit namespace_project_issue_path(project.namespace, project, issue)
+
+    wait_for_ajax
+  end
+
+  it 'opens autocomplete menu when field starts with text' do
+    page.within '.timeline-content-form' do
+      find('#note_note').native.send_keys('')
+      find('#note_note').native.send_keys('@')
+    end
+
+    expect(page).to have_selector('.atwho-container')
+  end
+
+  it 'opens autocomplete menu when field is prefixed with non-text character' do
+    page.within '.timeline-content-form' do
+      find('#note_note').native.send_keys('')
+      find('#note_note').native.send_keys('@')
+    end
+
+    expect(page).to have_selector('.atwho-container')
+  end
+
+  it 'doesnt open autocomplete menu character is prefixed with text' do
+    page.within '.timeline-content-form' do
+      find('#note_note').native.send_keys('testing')
+      find('#note_note').native.send_keys('@')
+    end
+
+    expect(page).not_to have_selector('.atwho-view')
+  end
+end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 055210399a74f6bcff282822c20f780a3013e4bc..c9bec05a9da7b6cbbc53ad5229d27d332a873946 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -27,7 +27,7 @@ feature 'issue move to another project' do
     let!(:mr) { create(:merge_request, source_project: old_project) }
     let(:new_project) { create(:project) }
     let(:new_project_search) { create(:project) }
-    let(:text) { 'Text with !1' }
+    let(:text) { "Text with #{mr.to_reference}" }
     let(:cross_reference) { old_project.to_reference }
 
     background do
@@ -43,8 +43,8 @@ feature 'issue move to another project' do
 
       expect(current_url).to include project_path(new_project)
 
-      expect(page).to have_content("Text with #{cross_reference}!1")
-      expect(page).to have_content("Moved from #{cross_reference}#1")
+      expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
+      expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
       expect(page).to have_content(issue.title)
     end
 
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index ab901e746172e398c32096c3dd54816e623868da..a4d3053d10cd74144fd7bb011e5adc9eae672f0c 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -20,12 +20,12 @@ feature 'Start new branch from an issue', feature: true do
     context "when there is a referenced merge request" do
       let!(:note) do
         create(:note, :on_issue, :system, project: project, noteable: issue,
-                                          note: "Mentioned in !#{referenced_mr.iid}")
+                                          note: "mentioned in #{referenced_mr.to_reference}")
       end
 
       let(:referenced_mr) do
         create(:merge_request, :simple, source_project: project, target_project: project,
-                                        description: "Fixes ##{issue.iid}", author: user)
+                                        description: "Fixes #{issue.to_reference}", author: user)
       end
 
       before do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index de8fdda388dc7a109cd6e5c6eebcceec0c472934..41ff31d2b99b95fc7f5fa0c2a60a4b6e4a799c1c 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -13,8 +13,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
 
   it 'creates todo when clicking button' do
     page.within '.issuable-sidebar' do
-      click_button 'Add Todo'
-      expect(page).to have_content 'Mark Done'
+      click_button 'Add todo'
+      expect(page).to have_content 'Mark done'
     end
 
     page.within '.header-content .todos-pending-count' do
@@ -30,8 +30,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
 
   it 'marks a todo as done' do
     page.within '.issuable-sidebar' do
-      click_button 'Add Todo'
-      click_button 'Mark Done'
+      click_button 'Add todo'
+      click_button 'Mark done'
     end
 
     expect(page).to have_selector('.todos-pending-count', visible: false)
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index eab64bd4b54f48edfc7d651f3da6e80ab39dcdf1..d5e3d8e7eff19358675e61bbeeb169057a9ee869 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -201,7 +201,7 @@ feature 'Diff notes resolve', feature: true, js: true do
           expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
         end
 
-        expect(page).not_to have_content('Last updated')
+        expect(page).to have_content('Last updated')
 
         page.within '.line-resolve-all-container' do
           expect(page).to have_content('0/1 discussion resolved')
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index 8eceaad2457cdecfc0aeecf748963fc5063f3db8..9ad225e3a1b0f06edf6fc3932cd1c45a657fd74a 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -44,7 +44,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
         expect(page).to have_content "The source branch will not be removed."
 
         visit_merge_request(merge_request) # Needed to refresh the page
-        expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i
+        expect(page).to have_content /enabled an automatic merge when the build for \h{8} succeeds/i
       end
     end
   end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 5d7247e2a626bc3808abd2317c04394279ff6223..9fffbb43e87e493f6912de64b00dfa5ef57d2d70 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -141,7 +141,7 @@ describe 'Comments', feature: true do
     let(:project2) { create(:project, :private) }
     let(:issue) { create(:issue, project: project2) }
     let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') }
-    let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") }
+    let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") }
 
     it 'shows the system note' do
       login_as :admin
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index d14a1158b676a27ce9558b793e07590f3d57cab1..a6b841c021060f2ae54a4b274e3287ef9339ef36 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -73,7 +73,7 @@ describe 'Profile > Preferences', feature: true do
         expect(page.current_path).to eq starred_dashboard_projects_path
       end
 
-      click_link 'Your Projects'
+      click_link 'Your projects'
 
       expect(page).not_to have_content("You don't have starred projects yet")
       expect(page.current_path).to eq dashboard_projects_path
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a820d07ab3b58e34f7b37403cbb1e28b283421a7
--- /dev/null
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+feature 'Editing file blob', feature: true, js: true do
+  include WaitForAjax
+
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
+  given(:project) { merge_request.target_project }
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  def edit_and_commit
+    wait_for_ajax
+    first('.file-actions').click_link 'Edit'
+    execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
+    click_button 'Commit Changes'
+  end
+
+  context 'from MR diff' do
+    before do
+      visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      edit_and_commit
+    end
+
+    scenario 'returns me to the mr' do
+      expect(page).to have_content(merge_request.title)
+    end
+  end
+
+  context 'from blob file path' do
+    before do
+      visit namespace_project_blob_path(project.namespace, project, '/feature/files/ruby/feature.rb')
+      edit_and_commit
+    end
+
+    scenario 'updates content' do
+      expect(page).to have_content 'successfully committed'
+      expect(page).to have_content 'NextFeature'
+    end
+  end
+end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3350a3aeefcc404013729c254b559c2bd7f1f82e
--- /dev/null
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -0,0 +1,154 @@
+require 'spec_helper'
+
+describe "Pipelines", feature: true, js: true do
+  include GitlabRoutingHelper
+
+  let(:project) { create(:empty_project) }
+  let(:user) { create(:user) }
+
+  before do
+    login_as(user)
+    project.team << [user, :developer]
+  end
+
+  describe 'GET /:project/pipelines/:id' do
+    let(:project) { create(:project) }
+    let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
+
+    before do
+      @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
+      @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+      @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+      @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
+      @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
+    end
+
+    before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+    it 'shows the pipeline graph' do
+      expect(page).to have_selector('.pipeline-visualization')
+      expect(page).to have_content('Build')
+      expect(page).to have_content('Test')
+      expect(page).to have_content('Deploy')
+      expect(page).to have_content('Retry failed')
+      expect(page).to have_content('Cancel running')
+    end
+
+    it 'shows Pipeline tab pane as active' do
+      expect(page).to have_css('#js-tab-pipeline.active')
+    end
+
+    context 'page tabs' do
+      it 'shows Pipeline and Builds tabs with link' do
+        expect(page).to have_link('Pipeline')
+        expect(page).to have_link('Builds')
+      end
+
+      it 'shows counter in Builds tab' do
+        expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
+      end
+
+      it 'shows Pipeline tab as active' do
+        expect(page).to have_css('.js-pipeline-tab-link.active')
+      end
+    end
+
+    context 'retrying builds' do
+      it { expect(page).not_to have_content('retried') }
+
+      context 'when retrying' do
+        before { click_on 'Retry failed' }
+
+        it { expect(page).not_to have_content('Retry failed') }
+      end
+    end
+
+    context 'canceling builds' do
+      it { expect(page).not_to have_selector('.ci-canceled') }
+
+      context 'when canceling' do
+        before { click_on 'Cancel running' }
+
+        it { expect(page).not_to have_content('Cancel running') }
+      end
+    end
+  end
+
+  describe 'GET /:project/pipelines/:id/builds' do
+    let(:project) { create(:project) }
+    let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
+
+    before do
+      @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
+      @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+      @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+      @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
+      @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
+    end
+
+    before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)}
+
+    it 'shows a list of builds' do
+      expect(page).to have_content('Test')
+      expect(page).to have_content(@success.id)
+      expect(page).to have_content('Deploy')
+      expect(page).to have_content(@failed.id)
+      expect(page).to have_content(@running.id)
+      expect(page).to have_content(@external.id)
+      expect(page).to have_content('Retry failed')
+      expect(page).to have_content('Cancel running')
+      expect(page).to have_link('Play')
+    end
+
+    it 'shows Builds tab pane as active' do
+      expect(page).to have_css('#js-tab-builds.active')
+    end
+
+    context 'page tabs' do
+      it 'shows Pipeline and Builds tabs with link' do
+        expect(page).to have_link('Pipeline')
+        expect(page).to have_link('Builds')
+      end
+
+      it 'shows counter in Builds tab' do
+        expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
+      end
+
+      it 'shows Builds tab as active' do
+        expect(page).to have_css('li.js-builds-tab-link.active')
+      end
+    end
+
+    context 'retrying builds' do
+      it { expect(page).not_to have_content('retried') }
+
+      context 'when retrying' do
+        before { click_on 'Retry failed' }
+
+        it { expect(page).not_to have_content('Retry failed') }
+        it { expect(page).to have_selector('.retried') }
+      end
+    end
+
+    context 'canceling builds' do
+      it { expect(page).not_to have_selector('.ci-canceled') }
+
+      context 'when canceling' do
+        before { click_on 'Cancel running' }
+
+        it { expect(page).not_to have_content('Cancel running') }
+        it { expect(page).to have_selector('.ci-canceled') }
+      end
+    end
+
+    context 'playing manual build' do
+      before do
+        within '.pipeline-holder' do
+          click_link('Play')
+        end
+      end
+
+      it { expect(@manual.reload).to be_pending }
+    end
+  end
+end
diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
similarity index 73%
rename from spec/features/projects/pipelines_spec.rb
rename to spec/features/projects/pipelines/pipelines_spec.rb
index 002c6f6b3596723a86cb8a01c031121e0ad5b97c..f3731698a1832936ec7570c8d57e4e64820ae49b 100644
--- a/spec/features/projects/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -90,13 +90,20 @@ describe "Pipelines" do
           visit namespace_project_pipelines_path(project.namespace, project)
         end
 
-        it 'is not cancelable' do
-          expect(page).not_to have_link('Cancel')
+        it 'is cancelable' do
+          expect(page).to have_link('Cancel')
         end
 
         it 'has pipeline running' do
           expect(page).to have_selector('.ci-running')
         end
+
+        context 'when canceling' do
+          before { click_link('Cancel') }
+
+          it { expect(page).not_to have_link('Cancel') }
+          it { expect(page).to have_selector('.ci-canceled') }
+        end
       end
 
       context 'when failed' do
@@ -145,65 +152,6 @@ describe "Pipelines" do
     end
   end
 
-  describe 'GET /:project/pipelines/:id' do
-    let(:project) { create(:project) }
-    let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
-
-    before do
-      @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
-      @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
-      @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
-      @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
-      @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
-    end
-
-    before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
-
-    it 'shows a list of builds' do
-      expect(page).to have_content('Test')
-      expect(page).to have_content(@success.id)
-      expect(page).to have_content('Deploy')
-      expect(page).to have_content(@failed.id)
-      expect(page).to have_content(@running.id)
-      expect(page).to have_content(@external.id)
-      expect(page).to have_content('Retry failed')
-      expect(page).to have_content('Cancel running')
-      expect(page).to have_link('Play')
-    end
-
-    context 'retrying builds' do
-      it { expect(page).not_to have_content('retried') }
-
-      context 'when retrying' do
-        before { click_on 'Retry failed' }
-
-        it { expect(page).not_to have_content('Retry failed') }
-        it { expect(page).to have_selector('.retried') }
-      end
-    end
-
-    context 'canceling builds' do
-      it { expect(page).not_to have_selector('.ci-canceled') }
-
-      context 'when canceling' do
-        before { click_on 'Cancel running' }
-
-        it { expect(page).not_to have_content('Cancel running') }
-        it { expect(page).to have_selector('.ci-canceled') }
-      end
-    end
-
-    context 'playing manual build' do
-      before do
-        within '.pipeline-holder' do
-          click_link('Play')
-        end
-      end
-
-      it { expect(@manual.reload).to be_pending }
-    end
-  end
-
   describe 'POST /:project/pipelines' do
     let(:project) { create(:project) }
 
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 7afd83b7250d6ef289466e16cd1d45fc49b5ee18..fff8b9f344756dd59c9a9152210b754666d9da88 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -20,7 +20,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
         click_button 'Create page'
 
         expect(page).to have_content('Home')
-        expect(page).to have_content("last edited by #{user.name}")
+        expect(page).to have_content("Last edited by #{user.name}")
         expect(page).to have_content('My awesome wiki!')
       end
     end
@@ -41,7 +41,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
           click_button 'Create page'
 
           expect(page).to have_content('Foo')
-          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content("Last edited by #{user.name}")
           expect(page).to have_content('My awesome wiki!')
         end
 
@@ -55,7 +55,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
           click_button 'Create page'
 
           expect(page).to have_content('Spaces in the name')
-          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content("Last edited by #{user.name}")
           expect(page).to have_content('My awesome wiki!')
         end
 
@@ -69,7 +69,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
           click_button 'Create page'
 
           expect(page).to have_content('Hyphens in the name')
-          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content("Last edited by #{user.name}")
           expect(page).to have_content('My awesome wiki!')
         end
       end
@@ -85,7 +85,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
         click_button 'Create page'
 
         expect(page).to have_content('Home')
-        expect(page).to have_content("last edited by #{user.name}")
+        expect(page).to have_content("Last edited by #{user.name}")
         expect(page).to have_content('My awesome wiki!')
       end
     end
@@ -105,7 +105,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
         click_button 'Create page'
 
         expect(page).to have_content('Foo')
-        expect(page).to have_content("last edited by #{user.name}")
+        expect(page).to have_content("Last edited by #{user.name}")
         expect(page).to have_content('My awesome wiki!')
       end
     end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index ef82d2375dd9bd22a74eee27f80671b0fc6d40d9..f842d14fa962e66d145f09e3dd73fa4273df3394 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -22,7 +22,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
       click_button 'Save changes'
 
       expect(page).to have_content('Home')
-      expect(page).to have_content("last edited by #{user.name}")
+      expect(page).to have_content("Last edited by #{user.name}")
       expect(page).to have_content('My awesome wiki!')
     end
   end
@@ -37,7 +37,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do
       click_button 'Save changes'
 
       expect(page).to have_content('Home')
-      expect(page).to have_content("last edited by #{user.name}")
+      expect(page).to have_content("Last edited by #{user.name}")
       expect(page).to have_content('My awesome wiki!')
     end
   end
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index 35fcef7a71279bff017d14c21586f5985ed6cc0a..87cce32d6c63f3acddb91ad07b050a673267ce1c 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -3,25 +3,12 @@ require 'rails_helper'
 describe 'Internal Group access', feature: true do
   include AccessMatchers
 
-  let(:group) { create(:group, :internal) }
+  let(:group)   { create(:group, :internal) }
   let(:project) { create(:project, :internal, group: group) }
-
-  let(:owner)     { create(:user) }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  let(:project_guest) { create(:user) }
-
-  before do
-    group.add_owner(owner)
-    group.add_master(master)
-    group.add_developer(developer)
-    group.add_reporter(reporter)
-    group.add_guest(guest)
-
-    project.team << [project_guest, :guest]
+  let(:project_guest) do
+    create(:user) do |user|
+      project.add_guest(user)
+    end
   end
 
   describe "Group should be internal" do
@@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do
   describe 'GET /groups/:path' do
     subject { group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/issues' do
     subject { issues_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/merge_requests' do
     subject { merge_requests_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/group_members' do
     subject { group_group_members_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/edit' do
     subject { edit_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_denied_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :visitor }
-    it { is_expected.to be_denied_for :external }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_denied_for(:master).of(group) }
+    it { is_expected.to be_denied_for(:developer).of(group) }
+    it { is_expected.to be_denied_for(:reporter).of(group) }
+    it { is_expected.to be_denied_for(:guest).of(group) }
+    it { is_expected.to be_denied_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:visitor) }
+    it { is_expected.to be_denied_for(:external) }
   end
 end
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 75a9334262837113db853097940476439323116a..1d6b3e77c2213ddf0b5d6ec28548395fd0f3fe21 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -3,25 +3,12 @@ require 'rails_helper'
 describe 'Private Group access', feature: true do
   include AccessMatchers
 
-  let(:group) { create(:group, :private) }
+  let(:group)   { create(:group, :private) }
   let(:project) { create(:project, :private, group: group) }
-
-  let(:owner)     { create(:user) }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  let(:project_guest) { create(:user) }
-
-  before do
-    group.add_owner(owner)
-    group.add_master(master)
-    group.add_developer(developer)
-    group.add_reporter(reporter)
-    group.add_guest(guest)
-
-    project.team << [project_guest, :guest]
+  let(:project_guest) do
+    create(:user) do |user|
+      project.add_guest(user)
+    end
   end
 
   describe "Group should be private" do
@@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do
   describe 'GET /groups/:path' do
     subject { group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/issues' do
     subject { issues_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/merge_requests' do
     subject { merge_requests_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/group_members' do
     subject { group_group_members_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe 'GET /groups/:path/edit' do
     subject { edit_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_denied_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :visitor }
-    it { is_expected.to be_denied_for :external }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_denied_for(:master).of(group) }
+    it { is_expected.to be_denied_for(:developer).of(group) }
+    it { is_expected.to be_denied_for(:reporter).of(group) }
+    it { is_expected.to be_denied_for(:guest).of(group) }
+    it { is_expected.to be_denied_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:visitor) }
+    it { is_expected.to be_denied_for(:external) }
   end
 end
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index 6c5ee93970b27f56d39a80d29ae91c6408fe60ce..d7d761772692b5f95ef0d2a57db2a341eca3cf33 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -3,25 +3,12 @@ require 'rails_helper'
 describe 'Public Group access', feature: true do
   include AccessMatchers
 
-  let(:group) { create(:group, :public) }
+  let(:group)   { create(:group, :public) }
   let(:project) { create(:project, :public, group: group) }
-
-  let(:owner)     { create(:user) }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  let(:project_guest) { create(:user) }
-
-  before do
-    group.add_owner(owner)
-    group.add_master(master)
-    group.add_developer(developer)
-    group.add_reporter(reporter)
-    group.add_guest(guest)
-    
-    project.team << [project_guest, :guest]
+  let(:project_guest) do
+    create(:user) do |user|
+      project.add_guest(user)
+    end
   end
 
   describe "Group should be public" do
@@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do
   describe 'GET /groups/:path' do
     subject { group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe 'GET /groups/:path/issues' do
     subject { issues_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe 'GET /groups/:path/merge_requests' do
     subject { merge_requests_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe 'GET /groups/:path/group_members' do
     subject { group_group_members_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for project_guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_allowed_for(:master).of(group) }
+    it { is_expected.to be_allowed_for(:developer).of(group) }
+    it { is_expected.to be_allowed_for(:reporter).of(group) }
+    it { is_expected.to be_allowed_for(:guest).of(group) }
+    it { is_expected.to be_allowed_for(project_guest) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe 'GET /groups/:path/edit' do
     subject { edit_group_path(group) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_denied_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for project_guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :visitor }
-    it { is_expected.to be_denied_for :external }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(group) }
+    it { is_expected.to be_denied_for(:master).of(group) }
+    it { is_expected.to be_denied_for(:developer).of(group) }
+    it { is_expected.to be_denied_for(:reporter).of(group) }
+    it { is_expected.to be_denied_for(:guest).of(group) }
+    it { is_expected.to be_denied_for(project_guest) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:visitor) }
+    it { is_expected.to be_denied_for(:external) }
   end
 end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index b6acc509342af993f534b124a8faf6f065a42b93..1897c8119d28b16c09880455dd81cfbc5b0af7c1 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -5,19 +5,6 @@ describe "Internal Project Access", feature: true  do
 
   let(:project) { create(:project, :internal) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
-
   describe "Project should be internal" do
     describe '#internal?' do
       subject { project.internal? }
@@ -28,213 +15,213 @@ describe "Internal Project Access", feature: true  do
   describe "GET /:project_path" do
     subject { namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/tree/master" do
     subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/commits/master" do
     subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/commit/:sha" do
     subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/compare" do
     subject { namespace_project_compare_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/project_members" do
     subject { namespace_project_project_members_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :visitor }
-    it { is_expected.to be_denied_for :external }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:visitor) }
+    it { is_expected.to be_denied_for(:external) }
   end
 
   describe "GET /:project_path/blob" do
     let(:commit) { project.repository.commit }
     subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/edit" do
     subject { edit_namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/deploy_keys" do
     subject { namespace_project_deploy_keys_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/issues" do
     subject { namespace_project_issues_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
     subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/new" do
     subject { new_namespace_project_snippet_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/merge_requests" do
     subject { namespace_project_merge_requests_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/merge_requests/new" do
     subject { new_namespace_project_merge_request_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/branches" do
@@ -245,15 +232,15 @@ describe "Internal Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:branches).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/tags" do
@@ -264,58 +251,58 @@ describe "Internal Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:tags).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/hooks" do
     subject { namespace_project_hooks_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/pipelines" do
     subject { namespace_project_pipelines_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/builds" do
@@ -324,29 +311,29 @@ describe "Internal Project Access", feature: true  do
     context "when allowed for public and internal" do
       before { project.update(public_builds: true) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "when disallowed for public and internal" do
       before { project.update(public_builds: false) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_denied_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_denied_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
@@ -358,73 +345,73 @@ describe "Internal Project Access", feature: true  do
     context "when allowed for public and internal" do
       before { project.update(public_builds: true) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "when disallowed for public and internal" do
       before { project.update(public_builds: false) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_denied_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_denied_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
   describe "GET /:project_path/environments" do
     subject { namespace_project_environments_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
     subject { namespace_project_environment_path(project.namespace, project, environment) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/container_registry" do
@@ -435,14 +422,14 @@ describe "Internal Project Access", feature: true  do
 
     subject { namespace_project_container_registry_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 79417c769a87069f6bbbf163cddd0aead9458baf..290ddb4c6ddab0fa312d1998830f0809224536ff 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -5,19 +5,6 @@ describe "Private Project Access", feature: true  do
 
   let(:project) { create(:project, :private) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
-
   describe "Project should be private" do
     describe '#private?' do
       subject { project.private? }
@@ -28,185 +15,185 @@ describe "Private Project Access", feature: true  do
   describe "GET /:project_path" do
     subject { namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/tree/master" do
     subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/commits/master" do
     subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/commit/:sha" do
     subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/compare" do
     subject { namespace_project_compare_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/project_members" do
     subject { namespace_project_project_members_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/blob" do
     let(:commit) { project.repository.commit }
     subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/edit" do
     subject { edit_namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/deploy_keys" do
     subject { namespace_project_deploy_keys_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/issues" do
     subject { namespace_project_issues_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
     subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/merge_requests" do
     subject { namespace_project_merge_requests_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for  guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/branches" do
@@ -217,15 +204,15 @@ describe "Private Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:branches).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/tags" do
@@ -236,72 +223,72 @@ describe "Private Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:tags).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/hooks" do
     subject { namespace_project_hooks_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/pipelines" do
     subject { namespace_project_pipelines_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/builds" do
     subject { namespace_project_builds_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/builds/:id" do
@@ -309,58 +296,58 @@ describe "Private Project Access", feature: true  do
     let(:build) { create(:ci_build, pipeline: pipeline) }
     subject { namespace_project_build_path(project.namespace, project, build.id) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments" do
     subject { namespace_project_environments_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
     subject { namespace_project_environment_path(project.namespace, project, environment) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/container_registry" do
@@ -371,14 +358,14 @@ describe "Private Project Access", feature: true  do
 
     subject { namespace_project_container_registry_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 985663e7c989a4c9d76bd9bdab53c99ebb2819a2..bed9e92fcb6071a60630a07550bf9200506f062a 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -5,19 +5,6 @@ describe "Public Project Access", feature: true  do
 
   let(:project) { create(:project, :public) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
-
   describe "Project should be public" do
     describe '#public?' do
       subject { project.public? }
@@ -28,114 +15,114 @@ describe "Public Project Access", feature: true  do
   describe "GET /:project_path" do
     subject { namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/tree/master" do
     subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/commits/master" do
     subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/commit/:sha" do
     subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/compare" do
     subject { namespace_project_compare_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/project_members" do
     subject { namespace_project_project_members_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :visitor }
-    it { is_expected.to be_allowed_for :external }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:visitor) }
+    it { is_expected.to be_allowed_for(:external) }
   end
 
   describe "GET /:project_path/pipelines" do
     subject { namespace_project_pipelines_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/pipelines/:id" do
     let(:pipeline) { create(:ci_pipeline, project: project) }
     subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/builds" do
@@ -144,29 +131,29 @@ describe "Public Project Access", feature: true  do
     context "when allowed for public" do
       before { project.update(public_builds: true) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_allowed_for :external }
-      it { is_expected.to be_allowed_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_allowed_for(:external) }
+      it { is_expected.to be_allowed_for(:visitor) }
     end
 
     context "when disallowed for public" do
       before { project.update(public_builds: false) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_denied_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_denied_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
@@ -178,73 +165,73 @@ describe "Public Project Access", feature: true  do
     context "when allowed for public" do
       before { project.update(public_builds: true) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_allowed_for :external }
-      it { is_expected.to be_allowed_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_allowed_for(:external) }
+      it { is_expected.to be_allowed_for(:visitor) }
     end
 
     context "when disallowed for public" do
       before { project.update(public_builds: false) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_denied_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_denied_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
   describe "GET /:project_path/environments" do
     subject { namespace_project_environments_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/:id" do
     let(:environment) { create(:environment, project: project) }
     subject { namespace_project_environment_path(project.namespace, project, environment) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/blob" do
@@ -252,127 +239,127 @@ describe "Public Project Access", feature: true  do
 
     subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/edit" do
     subject { edit_namespace_project_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/deploy_keys" do
     subject { namespace_project_deploy_keys_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/issues" do
     subject { namespace_project_issues_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/issues/:id/edit" do
     let(:issue) { create(:issue, project: project) }
     subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/new" do
     subject { new_namespace_project_snippet_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/merge_requests" do
     subject { namespace_project_merge_requests_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/merge_requests/new" do
     subject { new_namespace_project_merge_request_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/branches" do
@@ -383,15 +370,15 @@ describe "Public Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:branches).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/tags" do
@@ -402,29 +389,29 @@ describe "Public Project Access", feature: true  do
       allow_any_instance_of(Project).to receive(:tags).and_return([])
     end
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/hooks" do
     subject { namespace_project_hooks_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_denied_for developer }
-    it { is_expected.to be_denied_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_denied_for(:developer).of(project) }
+    it { is_expected.to be_denied_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/container_registry" do
@@ -435,14 +422,14 @@ describe "Public Project Access", feature: true  do
 
     subject { namespace_project_container_registry_index_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 end
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 49deacc5c7437180c0fae8a73315b0c038010b01..2659b3ee3ec3aee20c5ce0e5b5032dcd2fe83221 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -5,76 +5,64 @@ describe "Internal Project Snippets Access", feature: true  do
 
   let(:project) { create(:empty_project, :internal) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-  let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
-  let(:private_snippet)  { create(:project_snippet, :private, project: project, author: owner) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
+  let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
+  let(:private_snippet)  { create(:project_snippet, :private,  project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/new" do
     subject { new_namespace_project_snippet_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/:id" do
     context "for an internal snippet" do
       subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "for a private snippet" do
       subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
@@ -82,29 +70,29 @@ describe "Internal Project Snippets Access", feature: true  do
     context "for an internal snippet" do
       subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "for a private snippet" do
       subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 end
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index a1bfc076d99527533163a61fd5d0811ff981a3a6..6eb9f163bd5b25d782c79a3c13f20c2f14ebfaab 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -5,73 +5,61 @@ describe "Private Project Snippets Access", feature: true  do
 
   let(:project) { create(:empty_project, :private) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-  let(:private_snippet)  { create(:project_snippet, :private, project: project, author: owner) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
+  let(:private_snippet)  { create(:project_snippet, :private, project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/new" do
     subject { new_namespace_project_snippet_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/:id for a private snippet" do
     subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/:id/raw for a private snippet" do
     subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index 30bcd87ef049622a8a23a073a2b796323fb20448..f3329d0bc9609bb83f0e6f4c8769a584a2ed5ca2 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -5,91 +5,79 @@ describe "Public Project Snippets Access", feature: true  do
 
   let(:project) { create(:empty_project, :public) }
 
-  let(:owner)     { project.owner }
-  let(:master)    { create(:user) }
-  let(:developer) { create(:user) }
-  let(:reporter)  { create(:user) }
-  let(:guest)     { create(:user) }
-  let(:public_snippet)   { create(:project_snippet, :public, project: project, author: owner) }
-  let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
-  let(:private_snippet)  { create(:project_snippet, :private, project: project, author: owner) }
-
-  before do
-    project.team << [master, :master]
-    project.team << [developer, :developer]
-    project.team << [reporter, :reporter]
-    project.team << [guest, :guest]
-  end
+  let(:public_snippet)   { create(:project_snippet, :public,   project: project, author: project.owner) }
+  let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
+  let(:private_snippet)  { create(:project_snippet, :private,  project: project, author: project.owner) }
 
   describe "GET /:project_path/snippets" do
     subject { namespace_project_snippets_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_allowed_for guest }
-    it { is_expected.to be_allowed_for :user }
-    it { is_expected.to be_allowed_for :external }
-    it { is_expected.to be_allowed_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_allowed_for(:guest).of(project) }
+    it { is_expected.to be_allowed_for(:user) }
+    it { is_expected.to be_allowed_for(:external) }
+    it { is_expected.to be_allowed_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/new" do
     subject { new_namespace_project_snippet_path(project.namespace, project) }
 
-    it { is_expected.to be_allowed_for :admin }
-    it { is_expected.to be_allowed_for owner }
-    it { is_expected.to be_allowed_for master }
-    it { is_expected.to be_allowed_for developer }
-    it { is_expected.to be_allowed_for reporter }
-    it { is_expected.to be_denied_for guest }
-    it { is_expected.to be_denied_for :user }
-    it { is_expected.to be_denied_for :external }
-    it { is_expected.to be_denied_for :visitor }
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
   end
 
   describe "GET /:project_path/snippets/:id" do
     context "for a public snippet" do
       subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_allowed_for :external }
-      it { is_expected.to be_allowed_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_allowed_for(:external) }
+      it { is_expected.to be_allowed_for(:visitor) }
     end
 
     context "for an internal snippet" do
       subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "for a private snippet" do
       subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 
@@ -97,43 +85,43 @@ describe "Public Project Snippets Access", feature: true  do
     context "for a public snippet" do
       subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_allowed_for :external }
-      it { is_expected.to be_allowed_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_allowed_for(:external) }
+      it { is_expected.to be_allowed_for(:visitor) }
     end
 
     context "for an internal snippet" do
       subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_allowed_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_allowed_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
 
     context "for a private snippet" do
       subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
 
-      it { is_expected.to be_allowed_for :admin }
-      it { is_expected.to be_allowed_for owner }
-      it { is_expected.to be_allowed_for master }
-      it { is_expected.to be_allowed_for developer }
-      it { is_expected.to be_allowed_for reporter }
-      it { is_expected.to be_allowed_for guest }
-      it { is_expected.to be_denied_for :user }
-      it { is_expected.to be_denied_for :external }
-      it { is_expected.to be_denied_for :visitor }
+      it { is_expected.to be_allowed_for(:admin) }
+      it { is_expected.to be_allowed_for(:owner).of(project) }
+      it { is_expected.to be_allowed_for(:master).of(project) }
+      it { is_expected.to be_allowed_for(:developer).of(project) }
+      it { is_expected.to be_allowed_for(:reporter).of(project) }
+      it { is_expected.to be_allowed_for(:guest).of(project) }
+      it { is_expected.to be_denied_for(:user) }
+      it { is_expected.to be_denied_for(:external) }
+      it { is_expected.to be_denied_for(:visitor) }
     end
   end
 end
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb95e7828dbf9cb9306b14e1970624be27f8b405
--- /dev/null
+++ b/spec/features/snippets/create_snippet_spec.rb
@@ -0,0 +1,20 @@
+require 'rails_helper'
+
+feature 'Create Snippet', feature: true do
+  before do
+    login_as :user
+    visit new_snippet_path
+  end
+
+  scenario 'Authenticated user creates a snippet' do
+    fill_in 'personal_snippet_title', with: 'My Snippet Title'
+    page.within('.file-editor') do
+      find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!'
+    end
+
+    click_button 'Create snippet'
+
+    expect(page).to have_content('My Snippet Title')
+    expect(page).to have_content('Hello World!')
+  end
+end
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index d7880d5778f27d33b94f47606374e6055a242924..ff30ffd78206caf844bdc698cebeb496cfd802d7 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -29,6 +29,31 @@ describe 'Project variables', js: true do
     end
   end
 
+  it 'reveals and hides new variable' do
+    fill_in('variable_key', with: 'key')
+    fill_in('variable_value', with: 'key value')
+    click_button('Add new variable')
+
+    page.within('.variables-table') do
+      expect(page).to have_content('key')
+      expect(page).to have_content('******')
+    end
+
+    click_button('Reveal Values')
+
+    page.within('.variables-table') do
+      expect(page).to have_content('key')
+      expect(page).to have_content('key value')
+    end
+
+    click_button('Hide Values')
+
+    page.within('.variables-table') do
+      expect(page).to have_content('key')
+      expect(page).to have_content('******')
+    end
+  end
+
   it 'deletes variable' do
     page.within('.variables-table') do
       find('.btn-variable-delete').click
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 9085cc8debf8904cb9dbcef22d5a27afc517b1be..1724cdba830a8f8a56b4fc7ed9746835821da91d 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -17,7 +17,7 @@ describe LabelsFinder do
     let!(:project_label_4) { create(:label, project: project_4, title: 'Label 4') }
     let!(:project_label_5) { create(:label, project: project_5, title: 'Label 5') }
 
-    let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1') }
+    let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1 (group)') }
     let!(:group_label_2) { create(:group_label, group: group_1, title: 'Group Label 2') }
     let!(:group_label_3) { create(:group_label, group: group_2, title: 'Group Label 3') }
 
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 62cc10f579a534b43beec52598cee59d5e43d3e7..a4f08dc4af0b0eb1e5d76a9131a8afe8e2fd72a4 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -114,4 +114,25 @@ describe IssuablesHelper do
       end
     end
   end
+
+  describe '#issuable_filter_present?' do
+    it 'returns true when any key is present' do
+      allow(helper).to receive(:params).and_return(
+        ActionController::Parameters.new(milestone_title: 'Velit consectetur asperiores natus delectus.',
+                                         project_id: 'gitlabhq',
+                                         scope: 'all')
+      )
+
+      expect(helper.issuable_filter_present?).to be_truthy
+    end
+
+    it 'returns false when no key is present' do
+      allow(helper).to receive(:params).and_return(
+        ActionController::Parameters.new(project_id: 'gitlabhq',
+                                         scope: 'all')
+      )
+
+      expect(helper.issuable_filter_present?).to be_falsey
+    end
+  end
 end
diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js
deleted file mode 100644
index 7e38abc608e403246436f3cf9be0d0bea194ceed..0000000000000000000000000000000000000000
--- a/spec/javascripts/application_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, padded-blocks, max-len */
-
-/*= require lib/utils/common_utils */
-
-(function() {
-  describe('Application', function() {
-    return describe('disable buttons', function() {
-      fixture.preload('application.html');
-      beforeEach(function() {
-        return fixture.load('application.html');
-      });
-      it('should prevent default action for disabled buttons', function() {
-        var $button, isClicked;
-        gl.utils.preventDisabledButtons();
-        isClicked = false;
-        $button = $('#test-button');
-        expect($button).toExist();
-        $button.click(function() {
-          return isClicked = true;
-        });
-        $button.trigger('click');
-        return expect(isClicked).toBe(false);
-      });
-
-      it('should be on the same page if a disabled link clicked', function() {
-        var locationBeforeLinkClick, $link;
-        locationBeforeLinkClick = window.location.href;
-        gl.utils.preventDisabledButtons();
-        $link = $('#test-link');
-        expect($link).toExist();
-        $link.click();
-        return expect(window.location.href).toBe(locationBeforeLinkClick);
-      });
-    });
-  });
-
-}).call(this);
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..9aa3c50611de88d0ec983495d2c5eb15350b7927
--- /dev/null
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
@@ -0,0 +1,55 @@
+//= require lib/utils/bootstrap_linked_tabs
+
+(() => {
+  describe('Linked Tabs', () => {
+    fixture.preload('linked_tabs');
+
+    beforeEach(() => {
+      fixture.load('linked_tabs');
+    });
+
+    describe('when is initialized', () => {
+      it('should activate the tab correspondent to the given action', () => {
+        const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+          action: 'tab1',
+          defaultAction: 'tab1',
+          parentEl: '.linked-tabs',
+        });
+
+        expect(document.querySelector('#tab1').classList).toContain('active');
+      });
+
+      it('should active the default tab action when the action is show', () => {
+        const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+          action: 'show',
+          defaultAction: 'tab1',
+          parentEl: '.linked-tabs',
+        });
+
+        expect(document.querySelector('#tab1').classList).toContain('active');
+      });
+    });
+
+    describe('on click', () => {
+      it('should change the url according to the clicked tab', () => {
+        const historySpy = spyOn(history, 'replaceState').and.callFake(() => {});
+
+        const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line
+          action: 'show',
+          defaultAction: 'tab1',
+          parentEl: '.linked-tabs',
+        });
+
+        const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a');
+        const newState = secondTab.getAttribute('href') + linkedTabs.currentLocation.search + linkedTabs.currentLocation.hash;
+
+        secondTab.click();
+
+        expect(historySpy).toHaveBeenCalledWith({
+          turbolinks: true,
+          url: newState,
+        }, document.title, newState);
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6
index 4208e076e96953d50b0f7297fdacd1c533d3e4e1..3983cad4c137122cdaff5bd93b4aed2cbcfccbf5 100644
--- a/spec/javascripts/build_spec.js.es6
+++ b/spec/javascripts/build_spec.js.es6
@@ -8,184 +8,177 @@
 //= require jquery.nicescroll
 //= require turbolinks
 
-(() => {
-  describe('Build', () => {
-    fixture.preload('build.html');
+describe('Build', () => {
+  const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
+  // see spec/factories/ci/builds.rb
+  const BUILD_TRACE = 'BUILD TRACE';
+  // see lib/ci/ansi2html.rb
+  const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({
+    offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0,
+  }));
+
+  fixture.preload('builds/build-with-artifacts.html.raw');
+
+  beforeEach(() => {
+    fixture.load('builds/build-with-artifacts.html.raw');
+    spyOn($, 'ajax');
+  });
+
+  describe('constructor', () => {
+    beforeEach(() => {
+      jasmine.clock().install();
+    });
 
-    beforeEach(function () {
-      fixture.load('build.html');
-      spyOn($, 'ajax');
+    afterEach(() => {
+      jasmine.clock().uninstall();
     });
 
-    describe('constructor', () => {
+    describe('setup', () => {
       beforeEach(function () {
-        jasmine.clock().install();
+        this.build = new Build();
       });
 
-      afterEach(() => {
-        jasmine.clock().uninstall();
+      it('copies build options', function () {
+        expect(this.build.pageUrl).toBe(BUILD_URL);
+        expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
+        expect(this.build.buildStatus).toBe('success');
+        expect(this.build.buildStage).toBe('test');
+        expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE);
       });
 
-      describe('setup', function () {
-        const removeDate = new Date();
-        removeDate.setUTCFullYear(removeDate.getUTCFullYear() + 1);
-        // give the test three days to run
-        removeDate.setTime(removeDate.getTime() + (3 * 24 * 60 * 60 * 1000));
+      it('only shows the jobs matching the current stage', () => {
+        expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
+        expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
+        expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
+      });
 
-        beforeEach(function () {
-          const removeDateElement = document.querySelector('.js-artifacts-remove');
-          removeDateElement.innerText = removeDate.toString();
+      it('selects the current stage in the build dropdown menu', () => {
+        expect($('.stage-selection').text()).toBe('test');
+      });
 
-          this.build = new Build();
-        });
+      it('updates the jobs when the build dropdown changes', () => {
+        $('.stage-item:contains("build")').click();
 
-        it('copies build options', function () {
-          expect(this.build.pageUrl).toBe('http://example.com/root/test-build/builds/2');
-          expect(this.build.buildUrl).toBe('http://example.com/root/test-build/builds/2.json');
-          expect(this.build.buildStatus).toBe('passed');
-          expect(this.build.buildStage).toBe('test');
-          expect(this.build.state).toBe('buildstate');
-        });
+        expect($('.stage-selection').text()).toBe('build');
+        expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
+        expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
+        expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
+      });
 
-        it('only shows the jobs matching the current stage', function () {
-          expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
-          expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
-          expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
-        });
+      it('displays the remove date correctly', () => {
+        const removeDateElement = document.querySelector('.js-artifacts-remove');
+        expect(removeDateElement.innerText.trim()).toBe('1 year');
+      });
+    });
 
-        it('selects the current stage in the build dropdown menu', function () {
-          expect($('.stage-selection').text()).toBe('test');
-        });
+    describe('initial build trace', () => {
+      beforeEach(() => {
+        new Build();
+      });
 
-        it('updates the jobs when the build dropdown changes', function () {
-          $('.stage-item:contains("build")').click();
+      it('displays the initial build trace', () => {
+        expect($.ajax.calls.count()).toBe(1);
+        const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0);
+        expect(url).toBe(`${BUILD_URL}.json`);
+        expect(dataType).toBe('json');
+        expect(success).toEqual(jasmine.any(Function));
 
-          expect($('.stage-selection').text()).toBe('build');
-          expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
-          expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
-          expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
-        });
+        success.call(context, { trace_html: '<span>Example</span>', status: 'running' });
 
-        it('displays the remove date correctly', function () {
-          const removeDateElement = document.querySelector('.js-artifacts-remove');
-          expect(removeDateElement.innerText.trim()).toBe('1 year');
-        });
+        expect($('#build-trace .js-build-output').text()).toMatch(/Example/);
       });
 
-      describe('initial build trace', function () {
-        beforeEach(function () {
-          new Build();
-        });
+      it('removes the spinner', () => {
+        const [{ success, context }] = $.ajax.calls.argsFor(0);
+        success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
 
-        it('displays the initial build trace', function () {
-          expect($.ajax.calls.count()).toBe(1);
-          const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0);
-          expect(url).toBe('http://example.com/root/test-build/builds/2.json');
-          expect(dataType).toBe('json');
-          expect(success).toEqual(jasmine.any(Function));
+        expect($('.js-build-refresh').length).toBe(0);
+      });
+    });
 
-          success.call(context, { trace_html: '<span>Example</span>', status: 'running' });
+    describe('running build', () => {
+      beforeEach(function () {
+        $('.js-build-options').data('buildStatus', 'running');
+        this.build = new Build();
+        spyOn(this.build, 'location').and.returnValue(BUILD_URL);
+      });
 
-          expect($('#build-trace .js-build-output').text()).toMatch(/Example/);
+      it('updates the build trace on an interval', function () {
+        jasmine.clock().tick(4001);
+
+        expect($.ajax.calls.count()).toBe(2);
+        let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
+        expect(url).toBe(
+          `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}`,
+        );
+        expect(dataType).toBe('json');
+        expect(success).toEqual(jasmine.any(Function));
+
+        success.call(context, {
+          html: '<span>Update<span>',
+          status: 'running',
+          state: 'newstate',
+          append: true,
         });
 
-        it('removes the spinner', function () {
-          const [{ success, context }] = $.ajax.calls.argsFor(0);
-          success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
+        expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+        expect(this.build.state).toBe('newstate');
 
-          expect($('.js-build-refresh').length).toBe(0);
+        jasmine.clock().tick(4001);
+
+        expect($.ajax.calls.count()).toBe(3);
+        [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
+        expect(url).toBe(`${BUILD_URL}/trace.json?state=newstate`);
+        expect(dataType).toBe('json');
+        expect(success).toEqual(jasmine.any(Function));
+
+        success.call(context, {
+          html: '<span>More</span>',
+          status: 'running',
+          state: 'finalstate',
+          append: true,
         });
+
+        expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
+        expect(this.build.state).toBe('finalstate');
       });
 
-      describe('running build', function () {
-        beforeEach(function () {
-          $('.js-build-options').data('buildStatus', 'running');
-          this.build = new Build();
-          spyOn(this.build, 'location')
-            .and.returnValue('http://example.com/root/test-build/builds/2');
+      it('replaces the entire build trace', () => {
+        jasmine.clock().tick(4001);
+        let [{ success, context }] = $.ajax.calls.argsFor(1);
+        success.call(context, {
+          html: '<span>Update</span>',
+          status: 'running',
+          append: true,
         });
 
-        it('updates the build trace on an interval', function () {
-          jasmine.clock().tick(4001);
-
-          expect($.ajax.calls.count()).toBe(2);
-          let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
-          expect(url).toBe(
-            'http://example.com/root/test-build/builds/2/trace.json?state=buildstate'
-          );
-          expect(dataType).toBe('json');
-          expect(success).toEqual(jasmine.any(Function));
-
-          success.call(context, {
-            html: '<span>Update<span>',
-            status: 'running',
-            state: 'newstate',
-            append: true,
-          });
-
-          expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-          expect(this.build.state).toBe('newstate');
-
-          jasmine.clock().tick(4001);
-
-          expect($.ajax.calls.count()).toBe(3);
-          [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
-          expect(url).toBe(
-            'http://example.com/root/test-build/builds/2/trace.json?state=newstate'
-          );
-          expect(dataType).toBe('json');
-          expect(success).toEqual(jasmine.any(Function));
-
-          success.call(context, {
-            html: '<span>More</span>',
-            status: 'running',
-            state: 'finalstate',
-            append: true,
-          });
-
-          expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
-          expect(this.build.state).toBe('finalstate');
-        });
+        expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
 
-        it('replaces the entire build trace', function () {
-          jasmine.clock().tick(4001);
-          let [{ success, context }] = $.ajax.calls.argsFor(1);
-          success.call(context, {
-            html: '<span>Update</span>',
-            status: 'running',
-            append: true,
-          });
-
-          expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-
-          jasmine.clock().tick(4001);
-          [{ success, context }] = $.ajax.calls.argsFor(2);
-          success.call(context, {
-            html: '<span>Different</span>',
-            status: 'running',
-            append: false,
-          });
-
-          expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
-          expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+        jasmine.clock().tick(4001);
+        [{ success, context }] = $.ajax.calls.argsFor(2);
+        success.call(context, {
+          html: '<span>Different</span>',
+          status: 'running',
+          append: false,
         });
 
-        it('reloads the page when the build is done', function () {
-          spyOn(Turbolinks, 'visit');
+        expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
+        expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+      });
 
-          jasmine.clock().tick(4001);
-          const [{ success, context }] = $.ajax.calls.argsFor(1);
-          success.call(context, {
-            html: '<span>Final</span>',
-            status: 'passed',
-            append: true,
-          });
+      it('reloads the page when the build is done', () => {
+        spyOn(Turbolinks, 'visit');
 
-          expect(Turbolinks.visit).toHaveBeenCalledWith(
-            'http://example.com/root/test-build/builds/2'
-          );
+        jasmine.clock().tick(4001);
+        const [{ success, context }] = $.ajax.calls.argsFor(1);
+        success.call(context, {
+          html: '<span>Final</span>',
+          status: 'passed',
+          append: true,
         });
+
+        expect(Turbolinks.visit).toHaveBeenCalledWith(BUILD_URL);
       });
     });
   });
-})();
+});
diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6
index c9ac7a73fd0ae8da175ed97b6fda0a03deb21541..76e81233e89178663689f9a8f4ba527832471da4 100644
--- a/spec/javascripts/environments/environment_actions_spec.js.es6
+++ b/spec/javascripts/environments/environment_actions_spec.js.es6
@@ -28,10 +28,10 @@ describe('Actions Component', () => {
     });
 
     expect(
-      component.$el.querySelectorAll('.dropdown-menu li').length
+      component.$el.querySelectorAll('.dropdown-menu li').length,
     ).toEqual(actionsMock.length);
     expect(
-      component.$el.querySelector('.dropdown-menu li a').getAttribute('href')
+      component.$el.querySelector('.dropdown-menu li a').getAttribute('href'),
     ).toEqual(actionsMock[0].play_path);
   });
 });
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6
index 3c15e3b771959857f55c0fdbb9c5bbcc4872e838..5d7c6b2411d050b242adc09060ab87fdb2400292 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js.es6
@@ -1,4 +1,5 @@
 //= require vue
+//= require timeago
 //= require environments/components/environment_item
 
 describe('Environment item', () => {
@@ -109,6 +110,8 @@ describe('Environment item', () => {
             name: 'deploy',
             build_path: '/root/ci-folders/builds/1279',
             retry_path: '/root/ci-folders/builds/1279/retry',
+            created_at: '2016-11-29T18:11:58.430Z',
+            updated_at: '2016-11-29T18:11:58.430Z',
           },
           manual_actions: [
             {
@@ -141,18 +144,29 @@ describe('Environment item', () => {
     describe('With deployment', () => {
       it('should render deployment internal id', () => {
         expect(
-          component.$el.querySelector('.deployment-column span').textContent
+          component.$el.querySelector('.deployment-column span').textContent,
         ).toContain(environment.last_deployment.iid);
 
         expect(
-          component.$el.querySelector('.deployment-column span').textContent
+          component.$el.querySelector('.deployment-column span').textContent,
         ).toContain('#');
       });
 
+      it('should render last deployment date', () => {
+        const timeagoInstance = new timeago(); // eslint-disable-line
+        const formatedDate = timeagoInstance.format(
+          environment.last_deployment.deployable.created_at,
+        );
+
+        expect(
+          component.$el.querySelector('.environment-created-date-timeago').textContent,
+        ).toContain(formatedDate);
+      });
+
       describe('With user information', () => {
         it('should render user avatar with link to profile', () => {
           expect(
-            component.$el.querySelector('.js-deploy-user-container').getAttribute('href')
+            component.$el.querySelector('.js-deploy-user-container').getAttribute('href'),
           ).toEqual(environment.last_deployment.user.web_url);
         });
       });
@@ -160,13 +174,13 @@ describe('Environment item', () => {
       describe('With build url', () => {
         it('Should link to build url provided', () => {
           expect(
-            component.$el.querySelector('.build-link').getAttribute('href')
+            component.$el.querySelector('.build-link').getAttribute('href'),
           ).toEqual(environment.last_deployment.deployable.build_path);
         });
 
         it('Should render deployable name and id', () => {
           expect(
-            component.$el.querySelector('.build-link').getAttribute('href')
+            component.$el.querySelector('.build-link').getAttribute('href'),
           ).toEqual(environment.last_deployment.deployable.build_path);
         });
       });
@@ -174,7 +188,7 @@ describe('Environment item', () => {
       describe('With commit information', () => {
         it('should render commit component', () => {
           expect(
-            component.$el.querySelector('.js-commit-component')
+            component.$el.querySelector('.js-commit-component'),
           ).toBeDefined();
         });
       });
@@ -183,7 +197,7 @@ describe('Environment item', () => {
     describe('With manual actions', () => {
       it('Should render actions component', () => {
         expect(
-          component.$el.querySelector('.js-manual-actions-container')
+          component.$el.querySelector('.js-manual-actions-container'),
         ).toBeDefined();
       });
     });
@@ -191,7 +205,7 @@ describe('Environment item', () => {
     describe('With external URL', () => {
       it('should render external url component', () => {
         expect(
-          component.$el.querySelector('.js-external-url-container')
+          component.$el.querySelector('.js-external-url-container'),
         ).toBeDefined();
       });
     });
@@ -199,7 +213,7 @@ describe('Environment item', () => {
     describe('With stop action', () => {
       it('Should render stop action component', () => {
         expect(
-          component.$el.querySelector('.js-stop-component-container')
+          component.$el.querySelector('.js-stop-component-container'),
         ).toBeDefined();
       });
     });
@@ -207,7 +221,7 @@ describe('Environment item', () => {
     describe('With retry action', () => {
       it('Should render rollback component', () => {
         expect(
-          component.$el.querySelector('.js-rollback-component-container')
+          component.$el.querySelector('.js-rollback-component-container'),
         ).toBeDefined();
       });
     });
diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6
index 9b0b3cb1c652862fbe4d1add2399120eadf0bbc5..17c00acf63e3959e9e3bfa86ce81dabcb362c131 100644
--- a/spec/javascripts/environments/environments_store_spec.js.es6
+++ b/spec/javascripts/environments/environments_store_spec.js.es6
@@ -5,11 +5,11 @@
 //= require ./mock_data
 
 (() => {
-  beforeEach(() => {
-    gl.environmentsList.EnvironmentsStore.create();
-  });
-
   describe('Store', () => {
+    beforeEach(() => {
+      gl.environmentsList.EnvironmentsStore.create();
+    });
+
     it('should start with a blank state', () => {
       expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0);
       expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0);
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
deleted file mode 100644
index c56e6c7789bebf1af135df7b15398e620e0849c8..0000000000000000000000000000000000000000
--- a/spec/javascripts/extensions/array_spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable space-before-function-paren, no-var, padded-blocks */
-
-/*= require extensions/array */
-
-(function() {
-  describe('Array extensions', function() {
-    describe('first', function() {
-      return it('returns the first item', function() {
-        var arr;
-        arr = [0, 1, 2, 3, 4, 5];
-        return expect(arr.first()).toBe(0);
-      });
-    });
-    return describe('last', function() {
-      return it('returns the last item', function() {
-        var arr;
-        arr = [0, 1, 2, 3, 4, 5];
-        return expect(arr.last()).toBe(5);
-      });
-    });
-  });
-
-}).call(this);
diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..2ec759c8e80f3e869cf56d24ac85b3278dffd364
--- /dev/null
+++ b/spec/javascripts/extensions/array_spec.js.es6
@@ -0,0 +1,46 @@
+/* eslint-disable space-before-function-paren, no-var, padded-blocks */
+
+/*= require extensions/array */
+
+(function() {
+  describe('Array extensions', function() {
+    describe('first', function() {
+      return it('returns the first item', function() {
+        var arr;
+        arr = [0, 1, 2, 3, 4, 5];
+        return expect(arr.first()).toBe(0);
+      });
+    });
+    describe('last', function() {
+      return it('returns the last item', function() {
+        var arr;
+        arr = [0, 1, 2, 3, 4, 5];
+        return expect(arr.last()).toBe(5);
+      });
+    });
+
+    describe('find', function () {
+      beforeEach(() => {
+        this.arr = [0, 1, 2, 3, 4, 5];
+      });
+
+      it('returns the item that first passes the predicate function', () => {
+        expect(this.arr.find(item => item === 2)).toBe(2);
+      });
+
+      it('returns undefined if no items pass the predicate function', () => {
+        expect(this.arr.find(item => item === 6)).not.toBeDefined();
+      });
+
+      it('error when called on undefined or null', () => {
+        expect(Array.prototype.find.bind(undefined, item => item === 1)).toThrow();
+        expect(Array.prototype.find.bind(null, item => item === 1)).toThrow();
+      });
+
+      it('error when predicate is not a function', () => {
+        expect(Array.prototype.find.bind(this.arr, 1)).toThrow();
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/fixtures/application.html.haml b/spec/javascripts/fixtures/application.html.haml
deleted file mode 100644
index 3fc6114407d6197bf74c959228d4d0ca96a55e9c..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/application.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%a#test-link.btn.disabled{:href => "/foo"} Test link
-%button#test-button.btn.disabled Test Button
diff --git a/spec/javascripts/fixtures/build.html.haml b/spec/javascripts/fixtures/build.html.haml
deleted file mode 100644
index 06b49516e5cd2cc33f1460a604e498574837be9e..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/build.html.haml
+++ /dev/null
@@ -1,62 +0,0 @@
-.build-page
-  .prepend-top-default
-    .autoscroll-container
-      %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
-    #js-build-scroll.scroll-controls
-      %a.btn{href: '#build-trace'}
-        %i.fa.fa-angle-up
-      %a.btn{href: '#down-build-trace'}
-        %i.fa.fa-angle-down
-    %pre.build-trace#build-trace
-      %code.bash.js-build-output
-      %i.fa.fa-refresh.fa-spin.js-build-refresh
-
-%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
-  .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
-    Build
-    %strong #1
-    %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
-      %i.fa.fa-angle-double-right
-  .blocks-container
-    .dropdown.build-dropdown
-      .title Stage
-      %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
-        %span.stage-selection More
-        %i.fa.fa-caret-down
-      %ul.dropdown-menu
-        %li
-          %a.stage-item build
-        %li
-          %a.stage-item test
-        %li
-          %a.stage-item deploy
-  .builds-container
-    .build-job{data: {stage: 'build'}}
-      %a{href: 'http://example.com/root/test-build/builds/1'}
-        %i.fa.fa-check
-        %i.fa.fa-check-circle-o
-        %span
-          Setup
-    .build-job{data: {stage: 'test'}}
-      %a{href: 'http://example.com/root/test-build/builds/2'}
-        %i.fa.fa-check
-        %i.fa.fa-check-circle-o
-        %span
-          Tests
-    .build-job{data: {stage: 'deploy'}}
-      %a{href: 'http://example.com/root/test-build/builds/3'}
-        %i.fa.fa-check
-        %i.fa.fa-check-circle-o
-        %span
-          Deploy
-
-.js-build-options{ data: { page_url: 'http://example.com/root/test-build/builds/2',
-  build_url: 'http://example.com/root/test-build/builds/2.json',
-  build_status: 'passed',
-  build_stage: 'test',
-  log_state: 'buildstate' }}
-
-%p.build-detail-row
-  The artifacts will be removed in
-  %span.js-artifacts-remove
-    2016-12-19 09:02:12 UTC
diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..978e25a1c32f46cc3130c76da93696ef674d665a
--- /dev/null
+++ b/spec/javascripts/fixtures/builds.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do
+  include JavaScriptFixturesHelpers
+
+  let(:admin) { create(:admin) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+  let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
+  let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
+  let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
+
+  render_views
+
+  before(:all) do
+    clean_frontend_fixtures('builds/')
+  end
+
+  before(:each) do
+    sign_in(admin)
+  end
+
+  it 'builds/build-with-artifacts.html.raw' do |example|
+    get :show,
+      namespace_id: project.namespace.to_param,
+      project_id: project.to_param,
+      id: build_with_artifacts.to_param
+
+    expect(response).to be_success
+    store_frontend_fixture(response, example.description)
+  end
+end
diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml
deleted file mode 100644
index cc1f8f15c2188e7705bd56b0b7d1693d7c801a31..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/comments.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-.flash-container.timeline-content
-.timeline-icon.hidden-xs.hidden-sm
-  %a.author_link
-    %img
-.timeline-content.timeline-content-form
-  %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form
-    .md-area
-      .md-header
-      .md-write-holder
-        .zen-backdrop.div-dropzone-wrapper
-          .div-dropzone-wrapper
-            .div-dropzone.dz-clickable
-              %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area
-    .note-form-actions.clearfix
-      %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' }
-      %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen
-        Reopen issue
-      %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close
-        Close issue
-      %a.btn.btn-cancel.js-note-discard
-        Discard draft
\ No newline at end of file
diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml
deleted file mode 100644
index 42ab4aa68b1c922017c10d323a05f03431164453..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/issuable.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%form.js-main-target-form
-  %textarea#note_note
diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml
deleted file mode 100644
index 0aecc7334fdcb986f34110893d75e2360a7ac904..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/issue_note.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%ul
-  %li.note
-    .js-task-list-container
-      .note-text
-        %ul.task-list
-          %li.task-list-item
-            %input.task-list-item-checkbox{type: 'checkbox'}
-            Task List Item
-      .note-edit-form
-        %form
-          %textarea.js-task-list-field
-            \- [ ] Task List Item
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index d95eb851421364cdef3d120cf4d4be3b2f4694c4..06f708f9e151a53a4bee1803762e8739d4da6a93 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -4,7 +4,8 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
   include JavaScriptFixturesHelpers
 
   let(:admin) { create(:admin) }
-  let(:project) { create(:project_empty_repo) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
 
   render_views
 
@@ -25,8 +26,13 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
   end
 
   it 'issues/issue-with-task-list.html.raw' do |example|
+    issue = create(:issue, project: project, description: '- [ ] Task List Item')
+    render_issue(example.description, issue)
+  end
+
+  it 'issues/issue_with_comment.html.raw' do |example|
     issue = create(:issue, project: project)
-    issue.update(description: '- [ ] Task List Item')
+    create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
     render_issue(example.description, issue)
   end
 
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..93c0cf97ff0979b055b9bdee4e8d716984fda08b
--- /dev/null
+++ b/spec/javascripts/fixtures/linked_tabs.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-tabs.linked-tabs
+  %li
+    %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
+      Tab 1
+  %li
+    %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
+      Tab 2
+
+.tab-content
+  #tab1.tab-pane
+    Tab 1 Content
+  #tab2.tab-pane
+    Tab 2 Content
diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml
deleted file mode 100644
index d48b77cf0ce2d4ba764fd2dd05e8e6e6557460e7..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/right_sidebar.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%div
-  %div.page-gutter.page-with-sidebar
-
-  %aside.right-sidebar
-    %div.block.issuable-sidebar-header
-      %a.gutter-toggle.pull-right.js-sidebar-toggle
-        %i.fa.fa-angle-double-left
-      %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: "1", issuable_type: "issue", url: "/todos" }}
-        %span.js-issuable-todo-text
-          Add Todo
-        %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden
-
-    %form.issuable-context-form
-      %div.block.labels
-        %div.sidebar-collapsed-icon
-          %i.fa.fa-tags
-          %span 1
diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml
deleted file mode 100644
index cb906a7feaa1e688e45a4d45cdd230804058fef1..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/zen_mode.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-.md-area
-  .zen-backdrop
-    %textarea#note_note.js-gfm-input.markdown-area
-    %a.js-zen-enter(tabindex="-1" href="#")
-      %i.fa.fa-expand
-      Edit in fullscreen
-    %a.js-zen-leave(tabindex="-1" href="#")
-      %i.fa.fa-compress
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index beef46122abfc71d61f37b93f7981b9c432ba7c8..14af6644de1a0ecb0492c0ed474f914c59a9af60 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -74,7 +74,7 @@
       it('submits an ajax request on tasklist:changed', function() {
         spyOn(jQuery, 'ajax').and.callFake(function(req) {
           expect(req.type).toBe('PATCH');
-          expect(req.url).toBe('https://fixture.invalid/namespace3/project3/issues/1.json');
+          expect(req.url).toBe(gl.TEST_HOST + '/frontend-fixtures/issues-project/issues/1.json'); // eslint-disable-line prefer-template
           expect(req.data.issue.description).not.toBe(null);
         });
 
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 51f2ae8bcbdcad815ca5563ba4c7cf08f7fd804d..2db182d702b6a56962703f0f93efd546e0921f90 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -6,17 +6,21 @@
 
 (function() {
   window.gon || (window.gon = {});
-
-  window.disableButtonIfEmptyField = function() {
-    return null;
-  };
+  window.gl = window.gl || {};
+  gl.utils = gl.utils || {};
 
   describe('Notes', function() {
-    describe('task lists', function() {
-      fixture.preload('issue_note.html');
+    var commentsTemplate = 'issues/issue_with_comment.raw';
+    fixture.preload(commentsTemplate);
 
+    beforeEach(function () {
+      fixture.load(commentsTemplate);
+      gl.utils.disableButtonIfEmptyField = _.noop;
+      window.project_uploads_path = 'http://test.host/uploads';
+    });
+
+    describe('task lists', function() {
       beforeEach(function() {
-        fixture.load('issue_note.html');
         $('form').on('submit', function(e) {
           e.preventDefault();
         });
@@ -41,12 +45,9 @@
     });
 
     describe('comments', function() {
-      var commentsTemplate = 'comments.html';
       var textarea = '.js-note-text';
-      fixture.preload(commentsTemplate);
 
       beforeEach(function() {
-        fixture.load(commentsTemplate);
         this.notes = new Notes();
 
         this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 83ebbd63f3a2cede20d99a21b431d5df36cbf1e9..0a9bc546144c3dacbc4a3472aa0b2ab3693711a2 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -34,9 +34,10 @@
   };
 
   describe('RightSidebar', function() {
-    fixture.preload('right_sidebar.html');
+    var fixtureName = 'issues/open-issue.html.raw';
+    fixture.preload(fixtureName);
     beforeEach(function() {
-      fixture.load('right_sidebar.html');
+      fixture.load(fixtureName);
       this.sidebar = new Sidebar;
       $aside = $('.right-sidebar');
       $page = $('.page-with-sidebar');
@@ -44,15 +45,12 @@
       $toggle = $aside.find('.js-sidebar-toggle');
       return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
     });
-    it('should expand the sidebar when arrow is clicked', function() {
+    it('should expand/collapse the sidebar when arrow is clicked', function() {
+      assertSidebarState('expanded');
       $toggle.click();
-      return assertSidebarState('expanded');
-    });
-    it('should collapse the sidebar when arrow is clicked', function() {
+      assertSidebarState('collapsed');
       $toggle.click();
       assertSidebarState('expanded');
-      $toggle.click();
-      return assertSidebarState('collapsed');
     });
     it('should float over the page and when sidebar icons clicked', function() {
       $labelsIcon.click();
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 7d36d79b687682ee450e76c1fab0721fc24b3693..e37816b0a8cfc62b8b1bad30d6a54e7a71efbe80 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -4,9 +4,11 @@
 
 (function() {
   describe('ShortcutsIssuable', function() {
-    fixture.preload('issuable.html');
+    var fixtureName = 'issues/open-issue.html.raw';
+    fixture.preload(fixtureName);
     beforeEach(function() {
-      fixture.load('issuable.html');
+      fixture.load(fixtureName);
+      document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
       return this.shortcut = new ShortcutsIssuable();
     });
     return describe('#replyWithSelectedText', function() {
diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6
index 651d1f0f97509c8a9b19628252630ec4fa63e052..ed6166a25a83b40324e62c81596cc2b39594ce81 100644
--- a/spec/javascripts/smart_interval_spec.js.es6
+++ b/spec/javascripts/smart_interval_spec.js.es6
@@ -37,7 +37,7 @@
           const intervalConfig = this.smartInterval.cfg;
           const iterationCount = 4;
           const maxIntervalAfterIterations = intervalConfig.startingInterval *
-            Math.pow(intervalConfig.incrementByFactorOf, (iterationCount - 1)); // 40
+            (intervalConfig.incrementByFactorOf ** (iterationCount - 1)); // 40
           const currentInterval = interval.getCurrentInterval();
 
           // Provide some flexibility for performance of testing environment
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index 8a64de4dd438925f844a40793e1e83d5110bb729..831dfada95224abb52880d14d150a352e50ff915 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -41,3 +41,8 @@
 
 
 }).call(this);
+
+// defined in ActionDispatch::TestRequest
+// see https://github.com/rails/rails/blob/v4.2.7.1/actionpack/lib/action_dispatch/testing/test_request.rb#L7
+window.gl = window.gl || {};
+gl.TEST_HOST = 'http://test.host';
diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6
index 0e3b82967c119c94f100e204bde6a1eb5bc7c5ca..d170517dd9b6e88d696eb81839d97f0a0fdbfa54 100644
--- a/spec/javascripts/vue_common_components/commit_spec.js.es6
+++ b/spec/javascripts/vue_common_components/commit_spec.js.es6
@@ -10,7 +10,7 @@ describe('Commit component', () => {
       el: document.querySelector('.test-commit-container'),
       propsData: {
         tag: false,
-        ref: {
+        commit_ref: {
           name: 'master',
           ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
         },
@@ -34,7 +34,7 @@ describe('Commit component', () => {
 
       props = {
         tag: true,
-        ref: {
+        commit_ref: {
           name: 'master',
           ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
         },
@@ -59,11 +59,11 @@ describe('Commit component', () => {
     });
 
     it('should render a link to the ref url', () => {
-      expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.ref.ref_url);
+      expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commit_ref.ref_url);
     });
 
     it('should render the ref name', () => {
-      expect(component.$el.querySelector('.branch-name').textContent).toContain(props.ref.name);
+      expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commit_ref.name);
     });
 
     it('should render the commit short sha with a link to the commit url', () => {
@@ -74,26 +74,26 @@ describe('Commit component', () => {
     describe('Given commit title and author props', () => {
       it('Should render a link to the author profile', () => {
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href')
+          component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'),
         ).toEqual(props.author.web_url);
       });
 
       it('Should render the author avatar with title and alt attributes', () => {
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title')
+          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'),
         ).toContain(props.author.username);
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt')
+          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'),
         ).toContain(`${props.author.username}'s avatar`);
       });
     });
 
     it('should render the commit title', () => {
       expect(
-        component.$el.querySelector('a.commit-row-message').getAttribute('href')
+        component.$el.querySelector('a.commit-row-message').getAttribute('href'),
       ).toEqual(props.commit_url);
       expect(
-        component.$el.querySelector('a.commit-row-message').textContent
+        component.$el.querySelector('a.commit-row-message').textContent,
       ).toContain(props.title);
     });
   });
@@ -103,7 +103,7 @@ describe('Commit component', () => {
       fixture.set('<div class="test-commit-container"></div>');
       props = {
         tag: false,
-        ref: {
+        commit_ref: {
           name: 'master',
           ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
         },
@@ -119,7 +119,7 @@ describe('Commit component', () => {
       });
 
       expect(
-        component.$el.querySelector('.commit-title span').textContent
+        component.$el.querySelector('.commit-title span').textContent,
       ).toContain('Cant find HEAD commit for this branch');
     });
   });
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index a18e8aee9b1e90d357f81d5e1cb360b412ef2fd0..b9acaaa5a0da306c6f57c6746b34e39dc64c1268 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -6,9 +6,10 @@
   var enterZen, escapeKeydown, exitZen;
 
   describe('ZenMode', function() {
-    fixture.preload('zen_mode.html');
+    var fixtureName = 'issues/open-issue.html.raw';
+    fixture.preload(fixtureName);
     beforeEach(function() {
-      fixture.load('zen_mode.html');
+      fixture.load(fixtureName);
       spyOn(Dropzone, 'forElement').and.callFake(function() {
         return {
           enable: function() {
@@ -60,11 +61,11 @@
   });
 
   enterZen = function() {
-    return $('a.js-zen-enter').click();
+    return $('.js-zen-enter').click();
   };
 
   exitZen = function() { // Ohmmmmmmm
-    return $('a.js-zen-leave').click();
+    return $('.js-zen-leave').click();
   };
 
   escapeKeydown = function() {
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 356dd01a03a81014ecaac3a92792f215c58bddfe..70b31f3a880496b17378009f280f84a907998b3b 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -22,7 +22,7 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
       html = header(i, "Header #{i}")
       doc = filter(html)
 
-      expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
+      expect(doc.css("h#{i} a").first.attr('id')).to eq "user-content-header-#{i}"
     end
   end
 
@@ -32,7 +32,12 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
       expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
     end
 
-    it 'links to the id' do
+    it 'has a namespaced id' do
+      doc = filter(header(1, 'Header'))
+      expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-header'
+    end
+
+    it 'links to the non-namespaced id' do
       doc = filter(header(1, 'Header'))
       expect(doc.css('h1 a').first.attr('href')).to eq '#header'
     end
@@ -40,29 +45,29 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do
     describe 'generated IDs' do
       it 'translates spaces to dashes' do
         doc = filter(header(1, 'This header has spaces in it'))
-        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
+        expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-has-spaces-in-it'
       end
 
       it 'squeezes multiple spaces and dashes' do
         doc = filter(header(1, 'This---header     is poorly-formatted'))
-        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
+        expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-poorly-formatted'
       end
 
       it 'removes punctuation' do
         doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
-        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
+        expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-filled-with-punctuation'
       end
 
       it 'appends a unique number to duplicates' do
         doc = filter(header(1, 'One') + header(2, 'One'))
 
-        expect(doc.css('h1 a').first.attr('id')).to eq 'one'
-        expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
+        expect(doc.css('h1 a').first.attr('href')).to eq '#one'
+        expect(doc.css('h2 a').first.attr('href')).to eq '#one-1'
       end
 
       it 'supports Unicode' do
         doc = filter(header(1, '한글'))
-        expect(doc.css('h1 a').first.attr('id')).to eq '한글'
+        expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-한글'
         expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
       end
     end
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..24c06a967fa4ed8f30fe13fca196e283eaced5f7
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::IssueSearch, service: true do
+  describe '#execute' do
+    let!(:issue) { create(:issue, title: 'find me') }
+    let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
+    let(:project) { issue.project }
+    let(:user) { issue.author }
+    let(:regex_match) { described_class.match("issue search find") }
+
+    subject do
+      described_class.new(project, user).execute(regex_match)
+    end
+
+    context 'when the user has no access' do
+      it 'only returns the open issues' do
+        expect(subject).not_to include(confidential)
+      end
+    end
+
+    context 'the user has access' do
+      before do
+        project.team << [user, :master]
+      end
+
+      it 'returns all results' do
+        expect(subject).to include(confidential, issue)
+      end
+    end
+
+    context 'without hits on the query' do
+      it 'returns an empty collection' do
+        expect(subject).to be_empty
+      end
+    end
+  end
+
+  describe 'self.match' do
+    let(:query) { "my search keywords" }
+    it 'matches the query' do
+      match = described_class.match("issue search #{query}")
+
+      expect(match[:query]).to eq(query)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
index 331a4604e9b5f89f3b2bfb96409fe1a710866f49..2eab73e49e5deb8224795d1472e5b3f3661c56b1 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
@@ -19,6 +19,14 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
       it 'returns the issue' do
         expect(subject.iid).to be issue.iid
       end
+
+      context 'when its reference is given' do
+        let(:regex_match) { described_class.match("issue show #{issue.to_reference}") }
+
+        it 'shows the issue' do
+          expect(subject.iid).to be issue.iid
+        end
+      end
     end
 
     context 'the issue does not exist' do
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 9aeaa6b3ee851f37f25a826719c6757df3fe3be7..6062e7af4f5712c1bff87d1ad4c45b8af2349201 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -321,6 +321,6 @@ describe Gitlab::CycleAnalytics::Events do
     context.update(milestone: milestone)
     mr = create_merge_request_closing_issue(context)
 
-    ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha)
+    ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash)
   end
 end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2a680f03476acbeeacee02ef9f5c63db323cfd28
--- /dev/null
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::FileCollection::MergeRequestDiff do
+  let(:merge_request) { create :merge_request }
+
+  it 'does not hightlight binary files' do
+    allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => false))
+
+    expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
+
+    described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files
+  end
+
+  it 'does not hightlight file if blob is not accessable' do
+    allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil)
+
+    expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
+
+    described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files
+  end
+end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 576aa5c366fd32e3a920ad5cb50d35d2adc87f93..578db51631e194d183da6596974c952bca162543 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -26,4 +26,29 @@ describe Gitlab::GitAccessWiki, lib: true do
   def changes
     ['6f6d7e7ed 570e7b2ab refs/heads/master']
   end
+
+  describe '#download_access_check' do
+    subject { access.check('git-upload-pack', '_any') }
+
+    before do
+      project.team << [user, :developer]
+    end
+
+    context 'when wiki feature is enabled' do
+      it 'give access to download wiki code' do
+        project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
+
+        expect(subject.allowed?).to be_truthy
+      end
+    end
+
+    context 'when wiki feature is disabled' do
+      it 'does not give access to download wiki code' do
+        project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+
+        expect(subject.allowed?).to be_falsey
+        expect(subject.message).to match(/You are not allowed to download code/)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
index e5300dbba1ee4951100965e66334d277dea00a35..462caa5b5fe8a6bd4c23f1c12b46508fa308f8a7 100644
--- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -49,14 +49,20 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do
   end
 
   describe '#valid?' do
-    it 'returns true when raw repo is present' do
+    it 'returns true when raw sha and ref are present' do
       branch = described_class.new(project, double(raw))
 
       expect(branch.valid?).to eq true
     end
 
-    it 'returns false when raw repo is blank' do
-      branch = described_class.new(project, double(raw.merge(repo: nil)))
+    it 'returns false when raw sha is blank' do
+      branch = described_class.new(project, double(raw.merge(sha: nil)))
+
+      expect(branch.valid?).to eq false
+    end
+
+    it 'returns false when raw ref is blank' do
+      branch = described_class.new(project, double(raw.merge(ref: nil)))
 
       expect(branch.valid?).to eq false
     end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index a0fdad87eee4d3c7915e5b4eb95928ae6e823e06..3cd9863ec6ae4a3092ffa2298d7e6ab0391f39b3 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -65,6 +65,14 @@ describe Gitlab::ProjectSearchResults, lib: true do
     end
   end
 
+  it 'does not list issues on private projects' do
+    issue = create(:issue, project: project)
+
+    results = described_class.new(user, project, issue.title)
+
+    expect(results.objects('issues')).not_to include issue
+  end
+
   describe 'confidential issues' do
     let(:query) { 'issue' }
     let(:author) { create(:user) }
@@ -72,6 +80,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
     let(:non_member) { create(:user) }
     let(:member) { create(:user) }
     let(:admin) { create(:admin) }
+    let(:project) { create(:empty_project, :internal) }
     let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
     let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
     let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index dfbefad6367ceb7c52fe85a5a49f01618fa8bfa5..f23e3522625f13ea8c6353ba0e607e8a42286a51 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -12,35 +12,48 @@ describe Gitlab::SearchResults do
   let!(:milestone) { create(:milestone, project: project, title: 'foo') }
   let(:results) { described_class.new(user, Project.all, 'foo') }
 
-  describe '#projects_count' do
-    it 'returns the total amount of projects' do
-      expect(results.projects_count).to eq(1)
+  context 'as a user with access' do
+    before do
+      project.team << [user, :developer]
     end
-  end
 
-  describe '#issues_count' do
-    it 'returns the total amount of issues' do
-      expect(results.issues_count).to eq(1)
+    describe '#projects_count' do
+      it 'returns the total amount of projects' do
+        expect(results.projects_count).to eq(1)
+      end
     end
-  end
 
-  describe '#merge_requests_count' do
-    it 'returns the total amount of merge requests' do
-      expect(results.merge_requests_count).to eq(1)
+    describe '#issues_count' do
+      it 'returns the total amount of issues' do
+        expect(results.issues_count).to eq(1)
+      end
+    end
+
+    describe '#merge_requests_count' do
+      it 'returns the total amount of merge requests' do
+        expect(results.merge_requests_count).to eq(1)
+      end
     end
-  end
 
-  describe '#milestones_count' do
-    it 'returns the total amount of milestones' do
-      expect(results.milestones_count).to eq(1)
+    describe '#milestones_count' do
+      it 'returns the total amount of milestones' do
+        expect(results.milestones_count).to eq(1)
+      end
     end
   end
 
+  it 'does not list issues on private projects' do
+    private_project = create(:empty_project, :private)
+    issue = create(:issue, project: private_project, title: 'foo')
+
+    expect(results.objects('issues')).not_to include issue
+  end
+
   describe 'confidential issues' do
-    let(:project_1) { create(:empty_project) }
-    let(:project_2) { create(:empty_project) }
-    let(:project_3) { create(:empty_project) }
-    let(:project_4) { create(:empty_project) }
+    let(:project_1) { create(:empty_project, :internal) }
+    let(:project_2) { create(:empty_project, :internal) }
+    let(:project_3) { create(:empty_project, :internal) }
+    let(:project_4) { create(:empty_project, :internal) }
     let(:query) { 'issue' }
     let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
     let(:author) { create(:user) }
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 932a5dc48625df45e111b05d794e940ba43da390..b692142713f98c098a3317ae797a8a2dcc220e18 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -50,7 +50,7 @@ describe Notify do
 
           context 'when enabled email_author_in_body' do
             before do
-              allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
+              stub_application_setting(email_author_in_body: true)
             end
 
             it 'contains a link to note author' do
@@ -229,7 +229,7 @@ describe Notify do
 
           context 'when enabled email_author_in_body' do
             before do
-              allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
+              stub_application_setting(email_author_in_body: true)
             end
 
             it 'contains a link to note author' do
@@ -607,7 +607,7 @@ describe Notify do
 
         context 'when enabled email_author_in_body' do
           before do
-            allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
+            stub_application_setting(email_author_in_body: true)
           end
 
           it 'contains a link to note author' do
@@ -686,6 +686,79 @@ describe Notify do
         end
       end
     end
+
+    context 'items that are noteable, emails for a note on a diff' do
+      let(:note_author) { create(:user, name: 'author_name') }
+
+      before :each do
+        allow(Note).to receive(:find).with(note.id).and_return(note)
+      end
+
+      shared_examples 'a note email on a diff' do  |model|
+        let(:note) { create(model, project: project, author: note_author) }
+
+        it "includes diffs with character-level highlighting" do
+          is_expected.to have_body_text /<span class=\"p\">}<\/span><\/span>/
+        end
+
+        it 'contains a link to the diff file' do
+          is_expected.to have_body_text /#{note.diff_file.file_path}/
+        end
+
+        it_behaves_like 'it should have Gmail Actions links'
+
+        it 'is sent as the author' do
+          sender = subject.header[:from].addrs[0]
+          expect(sender.display_name).to eq(note_author.name)
+          expect(sender.address).to eq(gitlab_sender)
+        end
+
+        it 'is sent to the given recipient' do
+          is_expected.to deliver_to recipient.notification_email
+        end
+
+        it 'contains the message from the note' do
+          is_expected.to have_body_text /#{note.note}/
+        end
+
+        it 'does not contain note author' do
+          is_expected.not_to have_body_text /wrote\:/
+        end
+
+        context 'when enabled email_author_in_body' do
+          before do
+            stub_application_setting(email_author_in_body: true)
+          end
+
+          it 'contains a link to note author' do
+            is_expected.to have_body_text note.author_name
+            is_expected.to have_body_text /wrote\:/
+          end
+        end
+      end
+
+      describe 'on a commit' do
+        let(:commit) { project.commit }
+        let(:note) { create(:diff_note_on_commit) }
+
+        subject { Notify.note_commit_email(recipient.id, note.id) }
+
+        it_behaves_like 'a note email on a diff', :diff_note_on_commit
+        it_behaves_like 'it should show Gmail Actions View Commit link'
+        it_behaves_like 'a user cannot unsubscribe through footer link'
+      end
+
+      describe 'on a merge request' do
+        let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+        let(:note) { create(:diff_note_on_merge_request) }
+
+        subject { Notify.note_merge_request_email(recipient.id, note.id) }
+
+        it_behaves_like 'a note email on a diff', :diff_note_on_merge_request
+        it_behaves_like 'it should show Gmail Actions View Merge request link'
+        it_behaves_like 'an unsubscribeable thread'
+      end
+    end
   end
 
   context 'for a group' do
@@ -1099,4 +1172,38 @@ describe Notify do
       is_expected.to have_body_text /#{diff_path}/
     end
   end
+
+  describe 'HTML emails setting' do
+    let(:project) { create(:project) }
+    let(:user) { create(:user) }
+    let(:multipart_mail) { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+
+    context 'when disabled' do
+      it 'only sends the text template' do
+        stub_application_setting(html_emails_enabled: false)
+
+        EmailTemplateInterceptor.delivering_email(multipart_mail)
+
+        expect(multipart_mail).to have_part_with('text/plain')
+        expect(multipart_mail).not_to have_part_with('text/html')
+      end
+    end
+
+    context 'when enabled' do
+      it 'sends a multipart message' do
+        stub_application_setting(html_emails_enabled: true)
+
+        EmailTemplateInterceptor.delivering_email(multipart_mail)
+
+        expect(multipart_mail).to have_part_with('text/plain')
+        expect(multipart_mail).to have_part_with('text/html')
+      end
+    end
+
+    matcher :have_part_with do |expected|
+      match do |actual|
+        actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) }
+      end
+    end
+  end
 end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..52428547a9f451a671ea4e0f6220c681fc6c8926
--- /dev/null
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -0,0 +1,194 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb')
+
+describe MigrateProcessCommitWorkerJobs do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+  let(:commit) { project.commit.raw.raw_commit }
+
+  describe 'Project' do
+    describe 'find_including_path' do
+      it 'returns Project instances' do
+        expect(described_class::Project.find_including_path(project.id)).
+          to be_an_instance_of(described_class::Project)
+      end
+
+      it 'selects the full path for every Project' do
+        migration_project = described_class::Project.
+          find_including_path(project.id)
+
+        expect(migration_project[:path_with_namespace]).
+          to eq(project.path_with_namespace)
+      end
+    end
+
+    describe '#repository_storage_path' do
+      it 'returns the storage path for the repository' do
+        migration_project = described_class::Project.
+          find_including_path(project.id)
+
+        expect(File.directory?(migration_project.repository_storage_path)).
+          to eq(true)
+      end
+    end
+
+    describe '#repository_path' do
+      it 'returns the path to the repository' do
+        migration_project = described_class::Project.
+          find_including_path(project.id)
+
+        expect(File.directory?(migration_project.repository_path)).to eq(true)
+      end
+    end
+
+    describe '#repository' do
+      it 'returns a Rugged::Repository' do
+        migration_project = described_class::Project.
+          find_including_path(project.id)
+
+        expect(migration_project.repository).
+          to be_an_instance_of(Rugged::Repository)
+      end
+    end
+  end
+
+  describe '#up', :redis do
+    let(:migration) { described_class.new }
+
+    def job_count
+      Sidekiq.redis { |r| r.llen('queue:process_commit') }
+    end
+
+    before do
+      Sidekiq.redis do |redis|
+        job = JSON.dump(args: [project.id, user.id, commit.oid])
+        redis.lpush('queue:process_commit', job)
+      end
+    end
+
+    it 'skips jobs using a project that no longer exists' do
+      allow(described_class::Project).to receive(:find_including_path).
+        with(project.id).
+        and_return(nil)
+
+      migration.up
+
+      expect(job_count).to eq(0)
+    end
+
+    it 'skips jobs using commits that no longer exist' do
+      allow_any_instance_of(Rugged::Repository).to receive(:lookup).
+        with(commit.oid).
+        and_raise(Rugged::OdbError)
+
+      migration.up
+
+      expect(job_count).to eq(0)
+    end
+
+    it 'inserts migrated jobs back into the queue' do
+      migration.up
+
+      expect(job_count).to eq(1)
+    end
+
+    context 'a migrated job' do
+      let(:job) do
+        migration.up
+
+        JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+      end
+
+      let(:commit_hash) do
+        job['args'][2]
+      end
+
+      it 'includes the project ID' do
+        expect(job['args'][0]).to eq(project.id)
+      end
+
+      it 'includes the user ID' do
+        expect(job['args'][1]).to eq(user.id)
+      end
+
+      it 'includes the commit ID' do
+        expect(commit_hash['id']).to eq(commit.oid)
+      end
+
+      it 'includes the commit message' do
+        expect(commit_hash['message']).to eq(commit.message)
+      end
+
+      it 'includes the parent IDs' do
+        expect(commit_hash['parent_ids']).to eq(commit.parent_ids)
+      end
+
+      it 'includes the author date' do
+        expect(commit_hash['authored_date']).to eq(commit.author[:time].to_s)
+      end
+
+      it 'includes the author name' do
+        expect(commit_hash['author_name']).to eq(commit.author[:name])
+      end
+
+      it 'includes the author Email' do
+        expect(commit_hash['author_email']).to eq(commit.author[:email])
+      end
+
+      it 'includes the commit date' do
+        expect(commit_hash['committed_date']).to eq(commit.committer[:time].to_s)
+      end
+
+      it 'includes the committer name' do
+        expect(commit_hash['committer_name']).to eq(commit.committer[:name])
+      end
+
+      it 'includes the committer Email' do
+        expect(commit_hash['committer_email']).to eq(commit.committer[:email])
+      end
+    end
+  end
+
+  describe '#down', :redis do
+    let(:migration) { described_class.new }
+
+    def job_count
+      Sidekiq.redis { |r| r.llen('queue:process_commit') }
+    end
+
+    before do
+      Sidekiq.redis do |redis|
+        job = JSON.dump(args: [project.id, user.id, commit.oid])
+        redis.lpush('queue:process_commit', job)
+
+        migration.up
+      end
+    end
+
+    it 'pushes migrated jobs back into the queue' do
+      migration.down
+
+      expect(job_count).to eq(1)
+    end
+
+    context 'a migrated job' do
+      let(:job) do
+        migration.down
+
+        JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+      end
+
+      it 'includes the project ID' do
+        expect(job['args'][0]).to eq(project.id)
+      end
+
+      it 'includes the user ID' do
+        expect(job['args'][1]).to eq(user.id)
+      end
+
+      it 'includes the commit SHA' do
+        expect(job['args'][2]).to eq(commit.oid)
+      end
+    end
+  end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index ef07f2275b1fea3286fe300b09b9afbd6774bde5..d4970e38f7ca1ce6ddff1ae5d5dc17d22935734a 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -730,8 +730,8 @@ describe Ci::Build, models: true do
         pipeline2 = create(:ci_pipeline, project: project)
         @build2 = create(:ci_build, pipeline: pipeline2)
 
-        commits = [double(id: pipeline.sha), double(id: pipeline2.sha)]
-        allow(@merge_request).to receive(:commits).and_return(commits)
+        allow(@merge_request).to receive(:commits_sha).
+          and_return([pipeline.sha, pipeline2.sha])
         allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
       end
 
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index ea022e03608af1918e408decd46ffbde51e89b4f..0d2b4920835a98a1303b684201fc5db622c7a8d6 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Ci::Pipeline, models: true do
+  include EmailHelpers
+
   let(:project) { FactoryGirl.create :empty_project }
   let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project }
 
@@ -402,6 +404,160 @@ describe Ci::Pipeline, models: true do
     end
   end
 
+  describe '#cancelable?' do
+    %i[created running pending].each do |status0|
+      context "when there is a build #{status0}" do
+        before do
+          create(:ci_build, status0, pipeline: pipeline)
+        end
+
+        it 'is cancelable' do
+          expect(pipeline.cancelable?).to be_truthy
+        end
+      end
+
+      context "when there is an external job #{status0}" do
+        before do
+          create(:generic_commit_status, status0, pipeline: pipeline)
+        end
+
+        it 'is cancelable' do
+          expect(pipeline.cancelable?).to be_truthy
+        end
+      end
+
+      %i[success failed canceled].each do |status1|
+        context "when there are generic_commit_status jobs for #{status0} and #{status1}" do
+          before do
+            create(:generic_commit_status, status0, pipeline: pipeline)
+            create(:generic_commit_status, status1, pipeline: pipeline)
+          end
+
+          it 'is cancelable' do
+            expect(pipeline.cancelable?).to be_truthy
+          end
+        end
+
+        context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do
+          before do
+            create(:generic_commit_status, status0, pipeline: pipeline)
+            create(:ci_build, status1, pipeline: pipeline)
+          end
+
+          it 'is cancelable' do
+            expect(pipeline.cancelable?).to be_truthy
+          end
+        end
+
+        context "when there are ci_build jobs for #{status0} and #{status1}" do
+          before do
+            create(:ci_build, status0, pipeline: pipeline)
+            create(:ci_build, status1, pipeline: pipeline)
+          end
+
+          it 'is cancelable' do
+            expect(pipeline.cancelable?).to be_truthy
+          end
+        end
+      end
+    end
+
+    %i[success failed canceled].each do |status|
+      context "when there is a build #{status}" do
+        before do
+          create(:ci_build, status, pipeline: pipeline)
+        end
+
+        it 'is not cancelable' do
+          expect(pipeline.cancelable?).to be_falsey
+        end
+      end
+
+      context "when there is an external job #{status}" do
+        before do
+          create(:generic_commit_status, status, pipeline: pipeline)
+        end
+
+        it 'is not cancelable' do
+          expect(pipeline.cancelable?).to be_falsey
+        end
+      end
+    end
+  end
+
+  describe '#cancel_running' do
+    let(:latest_status) { pipeline.statuses.pluck(:status) }
+
+    context 'when there is a running external job and created build' do
+      before do
+        create(:ci_build, :running, pipeline: pipeline)
+        create(:generic_commit_status, :running, pipeline: pipeline)
+
+        pipeline.cancel_running
+      end
+
+      it 'cancels both jobs' do
+        expect(latest_status).to contain_exactly('canceled', 'canceled')
+      end
+    end
+
+    context 'when builds are in different stages' do
+      before do
+        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)
+
+        pipeline.cancel_running
+      end
+
+      it 'cancels both jobs' do
+        expect(latest_status).to contain_exactly('canceled', 'canceled')
+      end
+    end
+  end
+
+  describe '#retry_failed' do
+    let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
+
+    context 'when there is a failed build and failed external status' do
+      before do
+        create(:ci_build, :failed, name: 'build', pipeline: pipeline)
+        create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)
+
+        pipeline.retry_failed(create(:user))
+      end
+
+      it 'retries only build' do
+        expect(latest_status).to contain_exactly('pending', 'failed')
+      end
+    end
+
+    context 'when builds are in different stages' do
+      before do
+        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
+
+        pipeline.retry_failed(create(:user))
+      end
+
+      it 'retries both builds' do
+        expect(latest_status).to contain_exactly('pending', 'pending')
+      end
+    end
+
+    context 'when there are canceled and failed' do
+      before do
+        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
+
+        pipeline.retry_failed(create(:user))
+      end
+
+      it 'retries both builds' do
+        expect(latest_status).to contain_exactly('pending', 'pending')
+      end
+    end
+  end
+
   describe '#execute_hooks' do
     let!(:build_a) { create_build('a', 0) }
     let!(:build_b) { create_build('b', 1) }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e3bb3482d67b2f881a0bc310166344a0c895ab67..7194c20d3bf7f85a9003bbf8dcaab000b5a59a09 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -302,4 +302,21 @@ eos
       expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
     end
   end
+
+  describe '.from_hash' do
+    let(:new_commit) { described_class.from_hash(commit.to_hash, project) }
+
+    it 'returns a Commit' do
+      expect(new_commit).to be_an_instance_of(described_class)
+    end
+
+    it 'wraps a Gitlab::Git::Commit' do
+      expect(new_commit.raw).to be_an_instance_of(Gitlab::Git::Commit)
+    end
+
+    it 'stores the correct commit fields' do
+      expect(new_commit.id).to eq(commit.id)
+      expect(new_commit.message).to eq(commit.message)
+    end
+  end
 end
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 87bffbdc54e599d6fdbb694640e71358b277aa6c..9defb17dc926a6c1010ea7078e2d61351bc84ba9 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -123,4 +123,100 @@ describe HasStatus do
       it_behaves_like 'build status summary'
     end
   end
+
+  context 'for scope with one status' do
+    shared_examples 'having a job' do |status|
+      %i[ci_build generic_commit_status].each do |type|
+        context "when it's #{status} #{type} job" do
+          let!(:job) { create(type, status) }
+
+          describe ".#{status}" do
+            it 'contains the job' do
+              expect(CommitStatus.public_send(status).all).
+                to contain_exactly(job)
+            end
+          end
+
+          describe '.relevant' do
+            if status == :created
+              it 'contains nothing' do
+                expect(CommitStatus.relevant.all).to be_empty
+              end
+            else
+              it 'contains the job' do
+                expect(CommitStatus.relevant.all).to contain_exactly(job)
+              end
+            end
+          end
+        end
+      end
+    end
+
+    %i[created running pending success
+       failed canceled skipped].each do |status|
+      it_behaves_like 'having a job', status
+    end
+  end
+
+  context 'for scope with more statuses' do
+    shared_examples 'containing the job' do |status|
+      %i[ci_build generic_commit_status].each do |type|
+        context "when it's #{status} #{type} job" do
+          let!(:job) { create(type, status) }
+
+          it 'contains the job' do
+            is_expected.to contain_exactly(job)
+          end
+        end
+      end
+    end
+
+    shared_examples 'not containing the job' do |status|
+      %i[ci_build generic_commit_status].each do |type|
+        context "when it's #{status} #{type} job" do
+          let!(:job) { create(type, status) }
+
+          it 'contains nothing' do
+            is_expected.to be_empty
+          end
+        end
+      end
+    end
+
+    describe '.running_or_pending' do
+      subject { CommitStatus.running_or_pending }
+
+      %i[running pending].each do |status|
+        it_behaves_like 'containing the job', status
+      end
+
+      %i[created failed success].each do |status|
+        it_behaves_like 'not containing the job', status
+      end
+    end
+
+    describe '.finished' do
+      subject { CommitStatus.finished }
+
+      %i[success failed canceled].each do |status|
+        it_behaves_like 'containing the job', status
+      end
+
+      %i[created running pending].each do |status|
+        it_behaves_like 'not containing the job', status
+      end
+    end
+
+    describe '.cancelable' do
+      subject { CommitStatus.cancelable }
+
+      %i[running pending created].each do |status|
+        it_behaves_like 'containing the job', status
+      end
+
+      %i[failed success skipped canceled].each do |status|
+        it_behaves_like 'not containing the job', status
+      end
+    end
+  end
 end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 7691d690db0fc79dbfcb653a2641c905dcce8564..7771785ead3e9182f2145765029a23f18068c455 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   context 'with deployment' do
     generate_cycle_analytics_spec(
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index f649b44d3670b4016be3c900ad859c3271bd10f9..5ed3d37f2fbfb18601d2c271d88e1612a1f38d4e 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :issue,
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 2cdefbeef21bcd2d4441e38d48f5ee3ee34d8ebe..baf3e3241a128cfac97440ef60cf3ac27b84a029 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :plan,
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 1f5e5cab92d0698f105b721f1f796b4d2af54623..21b9c6e71507a7e93fcd63c192bcd2dc346ea938 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :production,
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 0ed080a42b1d7ff094b25259f66075d1361fff14..158621d59a43ef0edd38fca5e6230ea12cd554a5 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :review,
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index af1c4477ddb58bdd9f7ba2dfccdc3961362ccc19..dad653964b74cd59b16aae8b5c8426ace1f2b34f 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :staging,
diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/models/cycle_analytics/summary_spec.rb
index 9d67bc82cbaf5703727c76d165181782432beadc..725bc68b25f457f1b9b9116d5d5b6e329756d393 100644
--- a/spec/models/cycle_analytics/summary_spec.rb
+++ b/spec/models/cycle_analytics/summary_spec.rb
@@ -4,7 +4,7 @@ describe CycleAnalytics::Summary, models: true do
   let(:project) { create(:project) }
   let(:from) { Time.now }
   let(:user) { create(:user, :admin) }
-  subject { described_class.new(project, from: from) }
+  subject { described_class.new(project, user, from: from) }
 
   describe "#new_issues" do
     it "finds the number of issues created after the 'from date'" do
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 02ddfeed9c1695cc5208435fbeadebca907ed824..2313724e8f38274aba2301bbb98b95d2dc46ab5c 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
-  subject { CycleAnalytics.new(project, from: from_date) }
+  subject { CycleAnalytics.new(project, user, from: from_date) }
 
   generate_cycle_analytics_spec(
     phase: :test,
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 0142706d140039b54b98fa3e3e1e713713bfac3e..2a67c60b978b3bac1d7964f2d0d8997b5329a886 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -590,4 +590,23 @@ describe Discussion, model: true do
       end
     end
   end
+
+  describe "#truncated_diff_lines" do
+    let(:truncated_lines) { subject.truncated_diff_lines }
+
+    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(truncated_lines.count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES
+      end
+    end
+
+    context "when some diff lines are meta" do
+      it "returns no meta lines"  do
+        expect(subject.diff_lines).to include(be_meta)
+        expect(truncated_lines).not_to include(be_meta)
+      end
+    end
+  end
 end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index b684053cd02d2c2a277cefb0166a97441ef1180e..f8660da031dba4e2c91abd100e6fa1ec99ac50ed 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -260,6 +260,24 @@ describe Event, models: true do
     end
   end
 
+  describe '#authored_by?' do
+    let(:event) { build(:event) }
+
+    it 'returns true when the event author and user are the same' do
+      expect(event.authored_by?(event.author)).to eq(true)
+    end
+
+    it 'returns false when passing nil as an argument' do
+      expect(event.authored_by?(nil)).to eq(false)
+    end
+
+    it 'returns false when the given user is not the author of the event' do
+      user = double(:user, id: -1)
+
+      expect(event.authored_by?(user)).to eq(false)
+    end
+  end
+
   def create_event(project, user, attrs = {})
     data = {
       before: Gitlab::Git::BLANK_SHA,
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index e500742404112e27d35e719e7a18911fa0c88d9c..eb876d105da77b1e97b4e6ca72fb54e5a51be3be 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -77,24 +77,13 @@ describe MergeRequestDiff, models: true do
   end
 
   describe '#commits_sha' do
-    shared_examples 'returning all commits SHA' do
-      it 'returns all commits SHA' do
-        commits_sha = subject.commits_sha
+    it 'returns all commits SHA using serialized commits' do
+      subject.st_commits = [
+        { id: 'sha1' },
+        { id: 'sha2' }
+      ]
 
-        expect(commits_sha).to eq(subject.commits.map(&:sha))
-      end
-    end
-
-    context 'when commits were loaded' do
-      before do
-        subject.commits
-      end
-
-      it_behaves_like 'returning all commits SHA'
-    end
-
-    context 'when commits were not loaded' do
-      it_behaves_like 'returning all commits SHA'
+      expect(subject.commits_sha).to eq(['sha1', 'sha2'])
     end
   end
 
@@ -113,4 +102,15 @@ describe MergeRequestDiff, models: true do
       expect(diffs.size).to eq(3)
     end
   end
+
+  describe '#commits_count' do
+    it 'returns number of commits using serialized commits' do
+      subject.st_commits = [
+        { id: 'sha1' },
+        { id: 'sha2' }
+      ]
+
+      expect(subject.commits_count).to eq 2
+    end
+  end
 end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 58ccd056328319bef1aa53f6c61670ef6e4cdf11..ec22ef934652f46d6b39bad0512a0f9a0a0120aa 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -557,20 +557,17 @@ describe MergeRequest, models: true do
   end
 
   describe '#commits_sha' do
-    let(:commit0) { double('commit0', sha: 'sha1') }
-    let(:commit1) { double('commit1', sha: 'sha2') }
-    let(:commit2) { double('commit2', sha: 'sha3') }
-
     before do
-      allow(subject.merge_request_diff).to receive(:commits).and_return([commit0, commit1, commit2])
+      allow(subject.merge_request_diff).to receive(:commits_sha).
+        and_return(['sha1'])
     end
 
-    it 'returns sha of commits' do
-      expect(subject.commits_sha).to contain_exactly('sha1', 'sha2', 'sha3')
+    it 'delegates to merge request diff' do
+      expect(subject.commits_sha).to eq ['sha1']
     end
   end
 
-  describe '#pipeline' do
+  describe '#head_pipeline' do
     describe 'when the source project exists' do
       it 'returns the latest pipeline' do
         pipeline = double(:ci_pipeline, ref: 'master')
@@ -581,7 +578,7 @@ describe MergeRequest, models: true do
           with('master', '123abc').
           and_return(pipeline)
 
-        expect(subject.pipeline).to eq(pipeline)
+        expect(subject.head_pipeline).to eq(pipeline)
       end
     end
 
@@ -589,7 +586,7 @@ describe MergeRequest, models: true do
       it 'returns nil' do
         allow(subject).to receive(:source_project).and_return(nil)
 
-        expect(subject.pipeline).to be_nil
+        expect(subject.head_pipeline).to be_nil
       end
     end
   end
@@ -857,7 +854,7 @@ describe MergeRequest, models: true do
       context 'and a failed pipeline is associated' do
         before do
           pipeline.update(status: 'failed')
-          allow(subject).to receive(:pipeline) { pipeline }
+          allow(subject).to receive(:head_pipeline) { pipeline }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_falsey }
@@ -866,7 +863,7 @@ describe MergeRequest, models: true do
       context 'and a successful pipeline is associated' do
         before do
           pipeline.update(status: 'success')
-          allow(subject).to receive(:pipeline) { pipeline }
+          allow(subject).to receive(:head_pipeline) { pipeline }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_truthy }
@@ -875,7 +872,7 @@ describe MergeRequest, models: true do
       context 'and a skipped pipeline is associated' do
         before do
           pipeline.update(status: 'skipped')
-          allow(subject).to receive(:pipeline) { pipeline }
+          allow(subject).to receive(:head_pipeline) { pipeline }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_truthy }
@@ -883,7 +880,7 @@ describe MergeRequest, models: true do
 
       context 'when no pipeline is associated' do
         before do
-          allow(subject).to receive(:pipeline) { nil }
+          allow(subject).to receive(:head_pipeline) { nil }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_truthy }
@@ -896,7 +893,7 @@ describe MergeRequest, models: true do
       context 'and a failed pipeline is associated' do
         before do
           pipeline.statuses << create(:commit_status, status: 'failed', project: project)
-          allow(subject).to receive(:pipeline) { pipeline }
+          allow(subject).to receive(:head_pipeline) { pipeline }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_truthy }
@@ -904,7 +901,7 @@ describe MergeRequest, models: true do
 
       context 'when no pipeline is associated' do
         before do
-          allow(subject).to receive(:pipeline) { nil }
+          allow(subject).to receive(:head_pipeline) { nil }
         end
 
         it { expect(subject.mergeable_ci_state?).to be_truthy }
@@ -1440,4 +1437,26 @@ describe MergeRequest, models: true do
       end
     end
   end
+
+  describe '#has_commits?' do
+    before do
+      allow(subject.merge_request_diff).to receive(:commits_count).
+        and_return(2)
+    end
+
+    it 'returns true when merge request diff has commits' do
+      expect(subject.has_commits?).to be_truthy
+    end
+  end
+
+  describe '#has_no_commits?' do
+    before do
+      allow(subject.merge_request_diff).to receive(:commits_count).
+        and_return(0)
+    end
+
+    it 'returns true when merge request diff has 0 commits' do
+      expect(subject.has_no_commits?).to be_truthy
+    end
+  end
 end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index e6b6e7c06344d7c1de2dcf9f2b60d9aafff1bb5d..17a15b12dcb09421deb0684d2bf80a93a499ae46 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -223,7 +223,7 @@ describe Note, models: true do
     let(:note) do
       create :note,
         noteable: ext_issue, project: ext_proj,
-        note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}",
+        note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
         system: true
     end
 
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index f5da967cd14c261766e5b025cc3b8974e957410d..862e3a72a737d8fba70b1858be8f39a6099ef187 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -68,7 +68,7 @@ describe JiraService, models: true do
     end
   end
 
-  describe "Execute" do
+  describe '#close_issue' do
     let(:custom_base_url) { 'http://custom_url' }
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
@@ -101,12 +101,10 @@ describe JiraService, models: true do
       @jira_service.save
 
       project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123'
-      @project_url       = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject'
       @transitions_url   = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
       @comment_url       = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
       @remote_link_url   = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/remotelink'
 
-      WebMock.stub_request(:get, @project_url)
       WebMock.stub_request(:get, project_issues_url)
       WebMock.stub_request(:post, @transitions_url)
       WebMock.stub_request(:post, @comment_url)
@@ -114,7 +112,7 @@ describe JiraService, models: true do
     end
 
     it "calls JIRA API" do
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       expect(WebMock).to have_requested(:post, @comment_url).with(
         body: /Issue solved with/
@@ -124,7 +122,7 @@ describe JiraService, models: true do
     # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
     # for more information
     it "creates Remote Link reference in JIRA for comment" do
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       # Creates comment
       expect(WebMock).to have_requested(:post, @comment_url)
@@ -146,7 +144,7 @@ describe JiraService, models: true do
     it "does not send comment or remote links to issues already closed" do
       allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true)
 
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       expect(WebMock).not_to have_requested(:post, @comment_url)
       expect(WebMock).not_to have_requested(:post, @remote_link_url)
@@ -155,7 +153,7 @@ describe JiraService, models: true do
     it "references the GitLab commit/merge request" do
       stub_config_setting(base_url: custom_base_url)
 
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       expect(WebMock).to have_requested(:post, @comment_url).with(
         body: /#{custom_base_url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
@@ -170,7 +168,7 @@ describe JiraService, models: true do
         { script_name: '/gitlab' }
       end
 
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       expect(WebMock).to have_requested(:post, @comment_url).with(
         body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
@@ -178,19 +176,33 @@ describe JiraService, models: true do
     end
 
     it "calls the api with jira_issue_transition_id" do
-      @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
 
       expect(WebMock).to have_requested(:post, @transitions_url).with(
         body: /custom-id/
       ).once
     end
+  end
 
-    context "when testing" do
-      it "tries to get jira project" do
-        @jira_service.execute(nil)
+  describe '#test_settings' do
+    let(:jira_service) do
+      described_class.new(
+        url: 'http://jira.example.com',
+        username: 'gitlab_jira_username',
+        password: 'gitlab_jira_password',
+        project_key: 'GitLabProject'
+      )
+    end
+    let(:project_url) { 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' }
 
-        expect(WebMock).to have_requested(:get, @project_url)
-      end
+    before do
+      WebMock.stub_request(:get, project_url)
+    end
+
+    it 'tries to get JIRA project' do
+      jira_service.test_settings
+
+      expect(WebMock).to have_requested(:get, project_url)
     end
   end
 
diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb
index 4f56bceda44c9f2887a9e91dcf63f1a0cdaa0f41..7c8824485f5046eacb26ca45215fc4e5a1d11f7e 100644
--- a/spec/models/project_services/pipeline_email_service_spec.rb
+++ b/spec/models/project_services/pipeline_email_service_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe PipelinesEmailService do
+  include EmailHelpers
+
   let(:pipeline) do
     create(:ci_pipeline, project: project, sha: project.commit('master').sha)
   end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index da38254d1bce2b48083fb11e0c8007edfa01736d..8abcce42ce05d6e2ce6538c25ca4b0f2905a94f1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -361,10 +361,15 @@ describe Project, models: true do
   describe '#get_issue' do
     let(:project) { create(:empty_project) }
     let!(:issue)  { create(:issue, project: project) }
+    let(:user)    { create(:user) }
+
+    before do
+      project.team << [user, :developer]
+    end
 
     context 'with default issues tracker' do
       it 'returns an issue' do
-        expect(project.get_issue(issue.iid)).to eq issue
+        expect(project.get_issue(issue.iid, user)).to eq issue
       end
 
       it 'returns count of open issues' do
@@ -372,7 +377,12 @@ describe Project, models: true do
       end
 
       it 'returns nil when no issue found' do
-        expect(project.get_issue(999)).to be_nil
+        expect(project.get_issue(999, user)).to be_nil
+      end
+
+      it "returns nil when user doesn't have access" do
+        user = create(:user)
+        expect(project.get_issue(issue.iid, user)).to eq nil
       end
     end
 
@@ -382,7 +392,7 @@ describe Project, models: true do
       end
 
       it 'returns an ExternalIssue' do
-        issue = project.get_issue('FOO-1234')
+        issue = project.get_issue('FOO-1234', user)
         expect(issue).to be_kind_of(ExternalIssue)
         expect(issue.iid).to eq 'FOO-1234'
         expect(issue.project).to eq project
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 04afb8ebc982729dbb486e70b73333dffc76c81b..b797d19161d1ec323d72a6de6f8c8ea79058466d 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1303,32 +1303,36 @@ describe Repository, models: true do
         repository.add_tag(user, '8.5', 'master', 'foo')
       end
 
-      it 'does not create a tag when a pre-hook fails' do
-        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
-
-        expect do
-          repository.add_tag(user, '8.5', 'master', 'foo')
-        end.to raise_error(GitHooksService::PreReceiveError)
+      it 'returns a Gitlab::Git::Tag object' do
+        tag = repository.add_tag(user, '8.5', 'master', 'foo')
 
-        repository.expire_tags_cache
-        expect(repository.find_tag('8.5')).to be_nil
+        expect(tag).to be_a(Gitlab::Git::Tag)
       end
 
-      it 'passes tag SHA to hooks' do
-        spy = GitHooksService.new
-        allow(GitHooksService).to receive(:new).and_return(spy)
-        allow(spy).to receive(:execute).and_call_original
+      it 'passes commit SHA to pre-receive and update hooks,\
+        and tag SHA to post-receive hook' do
+        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo)
+        update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo)
+        post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo)
 
-        tag = repository.add_tag(user, '8.5', 'master', 'foo')
+        allow(Gitlab::Git::Hook).to receive(:new).
+          and_return(pre_receive_hook, update_hook, post_receive_hook)
 
-        expect(spy).to have_received(:execute).
-          with(anything, anything, anything, tag.target, anything)
-      end
+        allow(pre_receive_hook).to receive(:trigger).and_call_original
+        allow(update_hook).to receive(:trigger).and_call_original
+        allow(post_receive_hook).to receive(:trigger).and_call_original
 
-      it 'returns a Gitlab::Git::Tag object' do
         tag = repository.add_tag(user, '8.5', 'master', 'foo')
 
-        expect(tag).to be_a(Gitlab::Git::Tag)
+        commit_sha = repository.commit('master').id
+        tag_sha = tag.target
+
+        expect(pre_receive_hook).to have_received(:trigger).
+          with(anything, anything, commit_sha, anything)
+        expect(update_hook).to have_received(:trigger).
+          with(anything, anything, commit_sha, anything)
+        expect(post_receive_hook).to have_received(:trigger).
+          with(anything, anything, tag_sha, anything)
       end
     end
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 91826e5884df56101b9ebfe17c293f4f240a3a4f..2244803f90c263b39cb338e67437d460c70c8ac4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -575,6 +575,23 @@ describe User, models: true do
         end
       end
     end
+
+    describe '#require_ssh_key?' do
+      protocol_and_expectation = {
+        'http' => false,
+        'ssh' => true,
+        '' => true,
+      }
+
+      protocol_and_expectation.each do |protocol, expected|
+        it "has correct require_ssh_key?" do
+          stub_application_setting(enabled_git_access_protocol: protocol)
+          user = build(:user)
+
+          expect(user.require_ssh_key?).to eq(expected)
+        end
+      end
+    end
   end
 
   describe '.find_by_any_email' do
@@ -1349,4 +1366,31 @@ describe User, models: true do
       expect(projects).to be_empty
     end
   end
+
+  describe '#refresh_authorized_projects', redis: true do
+    let(:project1) { create(:empty_project) }
+    let(:project2) { create(:empty_project) }
+    let(:user) { create(:user) }
+
+    before do
+      project1.team << [user, :reporter]
+      project2.team << [user, :guest]
+
+      user.project_authorizations.delete_all
+      user.refresh_authorized_projects
+    end
+
+    it 'refreshes the list of authorized projects' do
+      expect(user.project_authorizations.count).to eq(2)
+    end
+
+    it 'sets the authorized_projects_populated column' do
+      expect(user.authorized_projects_populated).to eq(true)
+    end
+
+    it 'stores the correct access levels' do
+      expect(user.project_authorizations.where(access_level: Gitlab::Access::GUEST).exists?).to eq(true)
+      expect(user.project_authorizations.where(access_level: Gitlab::Access::REPORTER).exists?).to eq(true)
+    end
+  end
 end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 96249a7d8c352a7ef32d6644a6439ea6736855ad..b49e4f3a8bc7ee31706c630aea8755aa824b98cf 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -23,7 +23,7 @@ describe ProjectPolicy, models: true do
       :download_code, :fork_project, :create_project_snippet, :update_issue,
       :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build,
       :read_container_image, :read_pipeline, :read_environment, :read_deployment,
-      :read_merge_request
+      :read_merge_request, :download_wiki_code
     ]
   end
 
@@ -56,7 +56,8 @@ describe ProjectPolicy, models: true do
   let(:public_permissions) do
     [
       :download_code, :fork_project, :read_commit_status, :read_pipeline,
-      :read_container_image, :build_download_code, :build_read_container_image
+      :read_container_image, :build_download_code, :build_read_container_image,
+      :download_wiki_code
     ]
   end
 
@@ -87,6 +88,15 @@ describe ProjectPolicy, models: true do
     expect(Ability.allowed?(user, :read_issue, project)).to be_falsy
   end
 
+  it 'does not include the wiki permissions when the feature is disabled' do
+    project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+    wiki_permissions = [:read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code]
+
+    permissions = described_class.abilities(owner, project).to_set
+
+    expect(permissions).not_to include(*wiki_permissions)
+  end
+
   context 'abilities for non-public projects' do
     let(:project) { create(:empty_project, namespace: owner.namespace) }
 
diff --git a/spec/rake_helper.rb b/spec/rake_helper.rb
index 9b5b4bf9feaceca152c63183f92a9583983b6031..298a520f5ca8e305428a6ca10232d1df7b8585a1 100644
--- a/spec/rake_helper.rb
+++ b/spec/rake_helper.rb
@@ -8,7 +8,7 @@ RSpec.configure do |config|
   config.before(:all) do
     $stdout = StringIO.new
 
-    Rake.application.rake_require 'tasks/gitlab/task_helpers'
+    Rake.application.rake_require 'tasks/gitlab/helpers'
     Rake::Task.define_task :environment
   end
 
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index 01bb9e955e01c4201e4fe75b2524cda398076f9b..36517ad0f8c5d0f7f07108813afdc0d5f282719d 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -47,7 +47,7 @@ describe API::Helpers, api: true do
   end
 
   def error!(message, status)
-    raise Exception
+    raise Exception.new("#{status} - #{message}")
   end
 
   describe ".current_user" do
@@ -290,4 +290,56 @@ describe API::Helpers, api: true do
       handle_api_exception(exception)
     end
   end
+
+  describe '.authenticate_non_get!' do
+    %w[HEAD GET].each do |method_name|
+      context "method is #{method_name}" do
+        before do
+          expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
+        end
+
+        it 'does not raise an error' do
+          expect_any_instance_of(self.class).not_to receive(:authenticate!)
+
+          expect { authenticate_non_get! }.not_to raise_error
+        end
+      end
+    end
+
+    %w[POST PUT PATCH DELETE].each do |method_name|
+      context "method is #{method_name}" do
+        before do
+          expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
+        end
+
+        it 'calls authenticate!' do
+          expect_any_instance_of(self.class).to receive(:authenticate!)
+
+          authenticate_non_get!
+        end
+      end
+    end
+  end
+
+  describe '.authenticate!' do
+    context 'current_user is nil' do
+      before do
+        expect_any_instance_of(self.class).to receive(:current_user).and_return(nil)
+      end
+
+      it 'returns a 401 response' do
+        expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}'
+      end
+    end
+
+    context 'current_user is present' do
+      before do
+        expect_any_instance_of(self.class).to receive(:current_user).and_return(true)
+      end
+
+      it 'does not raise an error' do
+        expect { authenticate! }.not_to raise_error
+      end
+    end
+  end
 end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 5ad4fc4865aebf7cd14cbb29c6329011e50d7aa4..c8e8f31cc1f4390167c892c2d9cbdd517506cd55 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::AwardEmoji, api: true  do
   include ApiHelpers
   let(:user)            { create(:user) }
   let!(:project)        { create(:empty_project) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index 4f5c09a30291a99a29fb8057b7855909194e24e4..3019724f52ebf661f436cbb616255b4fd347dbc8 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Boards, api: true  do
   include ApiHelpers
 
   let(:user)        { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index fe6b875b997b554f0d1816359efeaebe2cecb031..55b8c8c0c69b6c01c5763378b65a188e53a45de8 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'mime/types'
 
-describe API::API, api: true  do
+describe API::Branches, api: true  do
   include ApiHelpers
 
   let(:user) { create(:user) }
@@ -31,11 +31,22 @@ describe API::API, api: true  do
 
       expect(json_response['name']).to eq(branch_name)
       expect(json_response['commit']['id']).to eq(branch_sha)
+      expect(json_response['merged']).to eq(false)
       expect(json_response['protected']).to eq(false)
       expect(json_response['developers_can_push']).to eq(false)
       expect(json_response['developers_can_merge']).to eq(false)
     end
 
+    context 'on a merged branch' do
+      it "returns the branch information for a single branch" do
+        get api("/projects/#{project.id}/repository/branches/merge-test", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq('merge-test')
+        expect(json_response['merged']).to eq(true)
+      end
+    end
+
     it "returns a 403 error if guest" do
       get api("/projects/#{project.id}/repository/branches", user2)
       expect(response).to have_http_status(403)
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index fc72a44d663c1a99334f1940817cc3657efd6080..0ea991b18b88ec5d2f5a01995a6484039c8bc34e 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true do
+describe API::Builds, api: true do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a6e8550fac39e27c1e732564f9aaced67430b492..52491e1b9edd3941a0b67ac0dcfa255686c72971 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'mime/types'
 
-describe API::API, api: true  do
+describe API::Commits, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 65897edba7f885cbb79feda60de483b794bb4753..5fa7299044e1faf68513e80057c5daa9252a960b 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::DeployKeys, api: true  do
   include ApiHelpers
 
   let(:user)        { create(:user) }
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 8fa8c66db6ce117981e30e88ea6fafafb51e7890..31e3cfa1b2ff7f0c273f514058a6c61fb7d95be9 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Deployments, api: true  do
   include ApiHelpers
 
   let(:user)        { create(:user) }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 1898b07835d68f0fb66ea01c4095e9c89bd22414..126496c43a5bbf5656859447372d6c274de20b54 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Environments, api: true  do
   include ApiHelpers
 
   let(:user)          { create(:user) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 050d0dd082d1737f08b1fa17e9e281c0e17c6b68..2081f80ccc14f761bc328d5413afa90221624b21 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Files, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let!(:project) { create(:project, namespace: user.namespace ) }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index d9fdafde05ec3b6889a092cdad2fd181ca0fb15f..548ed8e1892382cef606224edf45a29a7a57b650 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Groups, api: true  do
   include ApiHelpers
 
   let(:user1) { create(:user, can_create_group: false) }
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index e88a7e27d45d286bbe48fd91ad7e65a2929482a9..35644bd8cc92f12661b2d798f5924db450a2d44e 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Internal, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:key) { create(:key, user: user) }
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 7bae055b2413ee8be532164c130a08ba41d1e5b1..5700f800c2e919b5baf38f69b7200a368578148a 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -1,7 +1,8 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Issues, api: true  do
   include ApiHelpers
+  include EmailHelpers
 
   let(:user)        { create(:user) }
   let(:user2)       { create(:user) }
@@ -365,6 +366,24 @@ describe API::API, api: true  do
     let(:base_url) { "/projects/#{project.id}" }
     let(:title) { milestone.title }
 
+    it "returns 404 on private projects for other users" do
+      private_project = create(:empty_project, :private)
+      create(:issue, project: private_project)
+
+      get api("/projects/#{private_project.id}/issues", non_member)
+
+      expect(response).to have_http_status(404)
+    end
+
+    it 'returns no issues when user has access to project but not issues' do
+      restricted_project = create(:empty_project, :public, issues_access_level: ProjectFeature::PRIVATE)
+      create(:issue, project: restricted_project)
+
+      get api("/projects/#{restricted_project.id}/issues", non_member)
+
+      expect(json_response).to eq([])
+    end
+
     it 'returns project issues without confidential issues for non project members' do
       get api("#{base_url}/issues", non_member)
       expect(response).to have_http_status(200)
@@ -697,6 +716,14 @@ describe API::API, api: true  do
         expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
       end
     end
+
+    context 'the user can only read the issue' do
+      it 'cannot create new labels' do
+        expect do
+          post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2'
+        end.not_to change { project.labels.count }
+      end
+    end
   end
 
   describe 'POST /projects/:id/issues with spam filtering' do
@@ -839,8 +866,8 @@ describe API::API, api: true  do
     end
 
     it 'removes all labels' do
-      put api("/projects/#{project.id}/issues/#{issue.id}", user),
-          labels: ''
+      put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: ''
+
       expect(response).to have_http_status(200)
       expect(json_response['labels']).to eq([])
     end
@@ -892,8 +919,8 @@ describe API::API, api: true  do
         update_time = 2.weeks.ago
         put api("/projects/#{project.id}/issues/#{issue.id}", user),
           labels: 'label3', state_event: 'close', updated_at: update_time
-        expect(response).to have_http_status(200)
 
+        expect(response).to have_http_status(200)
         expect(json_response['labels']).to include 'label3'
         expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
       end
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 893ed5c2b10d979b8584e3e6ca2859a19e6adce5..4c80987d6805e817f497ee502514d1f3d7e32c51 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Keys, api: true  do
   include ApiHelpers
 
   let(:user)  { create(:user) }
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index aaf4163927749f8c820065e348572c3d2c07f261..b29ce1ea25eccf58ca54704be0a77576f17e3792 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Labels, api: true  do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index 131c2d406ea46b7bfd496086539d10b529c2e768..e1887138aab527ced9ea697c37f414994f11ebaf 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe API::API, 'MergeRequestDiffs', api: true  do
+describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true  do
   include ApiHelpers
 
   let!(:user)          { create(:user) }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 37fcb2bc3a931b39febd02ac8507ac19163da645..918a71129f76c2937d84dd0793bf0fbd94230d04 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe API::API, api: true  do
+describe API::MergeRequests, api: true  do
   include ApiHelpers
   let(:base_time)   { Time.now }
   let(:user)        { create(:user) }
@@ -402,14 +402,6 @@ describe API::API, api: true  do
     end
   end
 
-  describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
-    it "returns merge_request" do
-      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
-      expect(response).to have_http_status(200)
-      expect(json_response['state']).to eq('closed')
-    end
-  end
-
   describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
     let(:pipeline) { create(:ci_pipeline_without_jobs) }
 
@@ -474,7 +466,7 @@ describe API::API, api: true  do
     end
 
     it "enables merge when build succeeds if the ci is active" do
-      allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+      allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
       allow(pipeline).to receive(:active?).and_return(true)
 
       put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
@@ -486,6 +478,15 @@ describe API::API, api: true  do
   end
 
   describe "PUT /projects/:id/merge_requests/:merge_request_id" do
+    context "to close a MR" do
+      it "returns merge_request" do
+        put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
+
+        expect(response).to have_http_status(200)
+        expect(json_response['state']).to eq('closed')
+      end
+    end
+
     it "updates title and returns merge_request" do
       put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
       expect(response).to have_http_status(200)
@@ -511,10 +512,10 @@ describe API::API, api: true  do
     end
 
     it 'allows special label names' do
-      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
-              user),
-          title: 'new issue',
-          labels: 'label, label?, label&foo, ?, &'
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
+        title: 'new issue',
+        labels: 'label, label?, label&foo, ?, &'
+
       expect(response.status).to eq(200)
       expect(json_response['labels']).to include 'label'
       expect(json_response['labels']).to include 'label?'
@@ -543,7 +544,7 @@ describe API::API, api: true  do
 
     it "returns 404 if note is attached to non existent merge request" do
       post api("/projects/#{project.id}/merge_requests/404/comments", user),
-           note: 'My comment'
+        note: 'My comment'
       expect(response).to have_http_status(404)
     end
   end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index b0946a838a1752952210da96af0523ee3fbc1284..8beef821d6c257aeeba8e17b34692dd53bc47778 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Milestones, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let!(:project) { create(:empty_project, namespace: user.namespace ) }
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 5347cf4f7bcce9af6fc4b1c5c9a36ea00f7187b8..c1edf384d5cccd3622b31bb985f7ec1f1f12cd35 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Namespaces, api: true  do
   include ApiHelpers
   let(:admin) { create(:admin) }
   let(:user) { create(:user) }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 0124b7271b3d6a17d06c3b49fb3a8e22f4a7498e..028f93c8561426dd5ccaf3e9fd481bf01d1e8df5 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Notes, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let!(:project) { create(:project, :public, namespace: user.namespace) }
@@ -25,7 +25,7 @@ describe API::API, api: true  do
   let!(:cross_reference_note) do
     create :note,
     noteable: ext_issue, project: ext_proj,
-    note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}",
+    note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
     system: true
   end
 
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index e6d8a5ee95407914659bace6e3055fdb30421a10..8691a81420f5a6b2f7a4ae834eca8089e35d78eb 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true do
+describe API::NotificationSettings, api: true do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index d83f7883c7847c221a7ff694851fa46699c27f0c..9a01f7fa1c4cbb194151f639f5a94b657fa63858 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true do
+describe API::Pipelines, api: true do
   include ApiHelpers
 
   let(:user)        { create(:user) }
@@ -103,6 +103,18 @@ describe API::API, api: true do
         expect(json_response['message']).to eq '404 Not found'
         expect(json_response['id']).to be nil
       end
+
+      context 'with coverage' do
+        before do
+          create(:ci_build, coverage: 30, pipeline: pipeline)
+        end
+
+        it 'exposes the coverage' do
+          get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+          expect(json_response["coverage"].to_i).to eq(30)
+        end
+      end
     end
 
     context 'unauthorized user' do
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 5f39329a1b821c7392d01c81f97c216d4fb108e1..a42cedae614cdb91a34ea3f62e0ee46d56da4627 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, 'ProjectHooks', api: true do
+describe API::ProjectHooks, 'ProjectHooks', api: true do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:user3) { create(:user) }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 1c25fd0433926d42d84afeccfb9f7210d28d286a..01032c0929bd403645998fff3ebcbc7a38d54622 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-describe API::API, api: true do
+describe API::ProjectSnippets, api: true do
   include ApiHelpers
 
   let(:project) { create(:empty_project, :public) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index e53ee2a4e761db33631b82e9950434f2eca20d99..c5d67a90abc194813a123721399e7a26d5867977 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Projects, api: true  do
   include ApiHelpers
   include Gitlab::CurrentSettings
   let(:user) { create(:user) }
@@ -200,32 +200,43 @@ describe API::API, api: true  do
   end
 
   describe 'GET /projects/visible' do
-    let(:public_project) { create(:project, :public) }
+    shared_examples_for 'visible projects response' do
+      it 'returns the visible projects' do
+        get api('/projects/visible', current_user)
 
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
+      end
+    end
+
+    let!(:public_project) { create(:project, :public) }
     before do
-      public_project
       project
       project2
       project3
       project4
     end
 
-    it 'returns the projects viewable by the user' do
-      get api('/projects/visible', user)
-
-      expect(response).to have_http_status(200)
-      expect(json_response).to be_an Array
-      expect(json_response.map { |project| project['id'] }).
-        to contain_exactly(public_project.id, project.id, project2.id, project3.id)
+    context 'when unauthenticated' do
+      it_behaves_like 'visible projects response' do
+        let(:current_user) { nil }
+        let(:projects) { [public_project] }
+      end
     end
 
-    it 'shows only public projects when the user only has access to those' do
-      get api('/projects/visible', user2)
+    context 'when authenticated' do
+      it_behaves_like 'visible projects response' do
+        let(:current_user) { user }
+        let(:projects) { [public_project, project, project2, project3] }
+      end
+    end
 
-      expect(response).to have_http_status(200)
-      expect(json_response).to be_an Array
-      expect(json_response.map { |project| project['id'] }).
-        to contain_exactly(public_project.id)
+    context 'when authenticated as a different user' do
+      it_behaves_like 'visible projects response' do
+        let(:current_user) { user2 }
+        let(:projects) { [public_project] }
+      end
     end
   end
 
@@ -415,16 +426,7 @@ describe API::API, api: true  do
         not_to change { Project.count }
 
       expect(response).to have_http_status(400)
-      expect(json_response['message']['name']).to eq([
-        'can\'t be blank',
-        'is too short (minimum is 0 characters)',
-        Gitlab::Regex.project_name_regex_message
-      ])
-      expect(json_response['message']['path']).to eq([
-        'can\'t be blank',
-        'is too short (minimum is 0 characters)',
-        Gitlab::Regex.send(:project_path_regex_message)
-      ])
+      expect(json_response['error']).to eq('name is missing')
     end
 
     it 'assigns attributes to project' do
@@ -438,6 +440,7 @@ describe API::API, api: true  do
 
       post api("/projects/user/#{user.id}", admin), project
 
+      expect(response).to have_http_status(201)
       project.each_pair do |k, v|
         next if %i[has_external_issue_tracker path].include?(k)
         expect(json_response[k.to_s]).to eq(v)
@@ -447,6 +450,8 @@ describe API::API, api: true  do
     it 'sets a project as public' do
       project = attributes_for(:project, :public)
       post api("/projects/user/#{user.id}", admin), project
+
+      expect(response).to have_http_status(201)
       expect(json_response['public']).to be_truthy
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
     end
@@ -454,6 +459,8 @@ describe API::API, api: true  do
     it 'sets a project as public using :public' do
       project = attributes_for(:project, { public: true })
       post api("/projects/user/#{user.id}", admin), project
+
+      expect(response).to have_http_status(201)
       expect(json_response['public']).to be_truthy
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
     end
@@ -461,6 +468,8 @@ describe API::API, api: true  do
     it 'sets a project as internal' do
       project = attributes_for(:project, :internal)
       post api("/projects/user/#{user.id}", admin), project
+
+      expect(response).to have_http_status(201)
       expect(json_response['public']).to be_falsey
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
     end
@@ -468,6 +477,7 @@ describe API::API, api: true  do
     it 'sets a project as internal overriding :public' do
       project = attributes_for(:project, :internal, { public: true })
       post api("/projects/user/#{user.id}", admin), project
+      expect(response).to have_http_status(201)
       expect(json_response['public']).to be_falsey
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
     end
@@ -529,135 +539,150 @@ describe API::API, api: true  do
   end
 
   describe 'GET /projects/:id' do
-    before { project }
-    before { project_member }
-
-    it 'returns a project by id' do
-      group = create(:group)
-      link = create(:project_group_link, project: project, group: group)
+    context 'when unauthenticated' do
+      it 'returns the public projects' do
+        public_project = create(:project, :public)
 
-      get api("/projects/#{project.id}", user)
+        get api("/projects/#{public_project.id}")
 
-      expect(response).to have_http_status(200)
-      expect(json_response['id']).to eq(project.id)
-      expect(json_response['description']).to eq(project.description)
-      expect(json_response['default_branch']).to eq(project.default_branch)
-      expect(json_response['tag_list']).to be_an Array
-      expect(json_response['public']).to be_falsey
-      expect(json_response['archived']).to be_falsey
-      expect(json_response['visibility_level']).to be_present
-      expect(json_response['ssh_url_to_repo']).to be_present
-      expect(json_response['http_url_to_repo']).to be_present
-      expect(json_response['web_url']).to be_present
-      expect(json_response['owner']).to be_a Hash
-      expect(json_response['owner']).to be_a Hash
-      expect(json_response['name']).to eq(project.name)
-      expect(json_response['path']).to be_present
-      expect(json_response['issues_enabled']).to be_present
-      expect(json_response['merge_requests_enabled']).to be_present
-      expect(json_response['wiki_enabled']).to be_present
-      expect(json_response['builds_enabled']).to be_present
-      expect(json_response['snippets_enabled']).to be_present
-      expect(json_response['container_registry_enabled']).to be_present
-      expect(json_response['created_at']).to be_present
-      expect(json_response['last_activity_at']).to be_present
-      expect(json_response['shared_runners_enabled']).to be_present
-      expect(json_response['creator_id']).to be_present
-      expect(json_response['namespace']).to be_present
-      expect(json_response['avatar_url']).to be_nil
-      expect(json_response['star_count']).to be_present
-      expect(json_response['forks_count']).to be_present
-      expect(json_response['public_builds']).to be_present
-      expect(json_response['shared_with_groups']).to be_an Array
-      expect(json_response['shared_with_groups'].length).to eq(1)
-      expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
-      expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
-      expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
-      expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
-      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
-    end
-
-    it 'returns a project by path name' do
-      get api("/projects/#{project.id}", user)
-      expect(response).to have_http_status(200)
-      expect(json_response['name']).to eq(project.name)
+        expect(response).to have_http_status(200)
+        expect(json_response['id']).to eq(public_project.id)
+        expect(json_response['description']).to eq(public_project.description)
+        expect(json_response.keys).not_to include('permissions')
+      end
     end
 
-    it 'returns a 404 error if not found' do
-      get api('/projects/42', user)
-      expect(response).to have_http_status(404)
-      expect(json_response['message']).to eq('404 Project Not Found')
-    end
+    context 'when authenticated' do
+      before do
+        project
+        project_member
+      end
 
-    it 'returns a 404 error if user is not a member' do
-      other_user = create(:user)
-      get api("/projects/#{project.id}", other_user)
-      expect(response).to have_http_status(404)
-    end
+      it 'returns a project by id' do
+        group = create(:group)
+        link = create(:project_group_link, project: project, group: group)
 
-    it 'handles users with dots' do
-      dot_user = create(:user, username: 'dot.user')
-      project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
+        get api("/projects/#{project.id}", user)
 
-      get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
-      expect(response).to have_http_status(200)
-      expect(json_response['name']).to eq(project.name)
-    end
+        expect(response).to have_http_status(200)
+        expect(json_response['id']).to eq(project.id)
+        expect(json_response['description']).to eq(project.description)
+        expect(json_response['default_branch']).to eq(project.default_branch)
+        expect(json_response['tag_list']).to be_an Array
+        expect(json_response['public']).to be_falsey
+        expect(json_response['archived']).to be_falsey
+        expect(json_response['visibility_level']).to be_present
+        expect(json_response['ssh_url_to_repo']).to be_present
+        expect(json_response['http_url_to_repo']).to be_present
+        expect(json_response['web_url']).to be_present
+        expect(json_response['owner']).to be_a Hash
+        expect(json_response['owner']).to be_a Hash
+        expect(json_response['name']).to eq(project.name)
+        expect(json_response['path']).to be_present
+        expect(json_response['issues_enabled']).to be_present
+        expect(json_response['merge_requests_enabled']).to be_present
+        expect(json_response['wiki_enabled']).to be_present
+        expect(json_response['builds_enabled']).to be_present
+        expect(json_response['snippets_enabled']).to be_present
+        expect(json_response['container_registry_enabled']).to be_present
+        expect(json_response['created_at']).to be_present
+        expect(json_response['last_activity_at']).to be_present
+        expect(json_response['shared_runners_enabled']).to be_present
+        expect(json_response['creator_id']).to be_present
+        expect(json_response['namespace']).to be_present
+        expect(json_response['avatar_url']).to be_nil
+        expect(json_response['star_count']).to be_present
+        expect(json_response['forks_count']).to be_present
+        expect(json_response['public_builds']).to be_present
+        expect(json_response['shared_with_groups']).to be_an Array
+        expect(json_response['shared_with_groups'].length).to eq(1)
+        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
+        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
+        expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
+        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+      end
+
+      it 'returns a project by path name' do
+        get api("/projects/#{project.id}", user)
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq(project.name)
+      end
 
-    describe 'permissions' do
-      context 'all projects' do
-        before { project.team << [user, :master] }
+      it 'returns a 404 error if not found' do
+        get api('/projects/42', user)
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Project Not Found')
+      end
 
-        it 'contains permission information' do
-          get api("/projects", user)
+      it 'returns a 404 error if user is not a member' do
+        other_user = create(:user)
+        get api("/projects/#{project.id}", other_user)
+        expect(response).to have_http_status(404)
+      end
 
-          expect(response).to have_http_status(200)
-          expect(json_response.first['permissions']['project_access']['access_level']).
-              to eq(Gitlab::Access::MASTER)
-          expect(json_response.first['permissions']['group_access']).to be_nil
-        end
+      it 'handles users with dots' do
+        dot_user = create(:user, username: 'dot.user')
+        project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
+
+        get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq(project.name)
       end
 
-      context 'personal project' do
-        it 'sets project access and returns 200' do
-          project.team << [user, :master]
-          get api("/projects/#{project.id}", user)
+      describe 'permissions' do
+        context 'all projects' do
+          before { project.team << [user, :master] }
 
-          expect(response).to have_http_status(200)
-          expect(json_response['permissions']['project_access']['access_level']).
+          it 'contains permission information' do
+            get api("/projects", user)
+
+            expect(response).to have_http_status(200)
+            expect(json_response.first['permissions']['project_access']['access_level']).
             to eq(Gitlab::Access::MASTER)
-          expect(json_response['permissions']['group_access']).to be_nil
+            expect(json_response.first['permissions']['group_access']).to be_nil
+          end
         end
-      end
 
-      context 'group project' do
-        let(:project2) { create(:project, group: create(:group)) }
+        context 'personal project' do
+          it 'sets project access and returns 200' do
+            project.team << [user, :master]
+            get api("/projects/#{project.id}", user)
 
-        before { project2.group.add_owner(user) }
+            expect(response).to have_http_status(200)
+            expect(json_response['permissions']['project_access']['access_level']).
+            to eq(Gitlab::Access::MASTER)
+            expect(json_response['permissions']['group_access']).to be_nil
+          end
+        end
 
-        it 'sets the owner and return 200' do
-          get api("/projects/#{project2.id}", user)
+        context 'group project' do
+          let(:project2) { create(:project, group: create(:group)) }
 
-          expect(response).to have_http_status(200)
-          expect(json_response['permissions']['project_access']).to be_nil
-          expect(json_response['permissions']['group_access']['access_level']).
+          before { project2.group.add_owner(user) }
+
+          it 'sets the owner and return 200' do
+            get api("/projects/#{project2.id}", user)
+
+            expect(response).to have_http_status(200)
+            expect(json_response['permissions']['project_access']).to be_nil
+            expect(json_response['permissions']['group_access']['access_level']).
             to eq(Gitlab::Access::OWNER)
+          end
         end
       end
     end
   end
 
   describe 'GET /projects/:id/events' do
-    before { project_member2 }
-
-    context 'valid request' do
-      before do
+    shared_examples_for 'project events response' do
+      it 'returns the project events' do
+        member = create(:user)
+        create(:project_member, :developer, user: member, project: project)
         note = create(:note_on_issue, note: 'What an awesome day!', project: project)
         EventCreateService.new.leave_note(note, note.author)
-      end
 
-      it 'returns all events' do
-        get api("/projects/#{project.id}/events", user)
+        get api("/projects/#{project.id}/events", current_user)
 
         expect(response).to have_http_status(200)
 
@@ -670,24 +695,90 @@ describe API::API, api: true  do
 
         expect(last_event['action_name']).to eq('joined')
         expect(last_event['project_id'].to_i).to eq(project.id)
-        expect(last_event['author_username']).to eq(user3.username)
-        expect(last_event['author']['name']).to eq(user3.name)
+        expect(last_event['author_username']).to eq(member.username)
+        expect(last_event['author']['name']).to eq(member.name)
       end
     end
 
-    it 'returns a 404 error if not found' do
-      get api('/projects/42/events', user)
+    context 'when unauthenticated' do
+      it_behaves_like 'project events response' do
+        let(:project) { create(:project, :public) }
+        let(:current_user) { nil }
+      end
+    end
 
-      expect(response).to have_http_status(404)
-      expect(json_response['message']).to eq('404 Project Not Found')
+    context 'when authenticated' do
+      context 'valid request' do
+        it_behaves_like 'project events response' do
+          let(:current_user) { user }
+        end
+      end
+
+      it 'returns a 404 error if not found' do
+        get api('/projects/42/events', user)
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Project Not Found')
+      end
+
+      it 'returns a 404 error if user is not a member' do
+        other_user = create(:user)
+
+        get api("/projects/#{project.id}/events", other_user)
+
+        expect(response).to have_http_status(404)
+      end
     end
+  end
 
-    it 'returns a 404 error if user is not a member' do
-      other_user = create(:user)
+  describe 'GET /projects/:id/users' do
+    shared_examples_for 'project users response' do
+      it 'returns the project users' do
+        member = create(:user)
+        create(:project_member, :developer, user: member, project: project)
 
-      get api("/projects/#{project.id}/events", other_user)
+        get api("/projects/#{project.id}/users", current_user)
 
-      expect(response).to have_http_status(404)
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(1)
+
+        first_user = json_response.first
+
+        expect(first_user['username']).to eq(member.username)
+        expect(first_user['name']).to eq(member.name)
+        expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
+      end
+    end
+
+    context 'when unauthenticated' do
+      it_behaves_like 'project users response' do
+        let(:project) { create(:project, :public) }
+        let(:current_user) { nil }
+      end
+    end
+
+    context 'when authenticated' do
+      context 'valid request' do
+        it_behaves_like 'project users response' do
+          let(:current_user) { user }
+        end
+      end
+
+      it 'returns a 404 error if not found' do
+        get api('/projects/42/users', user)
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Project Not Found')
+      end
+
+      it 'returns a 404 error if user is not a member' do
+        other_user = create(:user)
+
+        get api("/projects/#{project.id}/users", other_user)
+
+        expect(response).to have_http_status(404)
+      end
     end
   end
 
@@ -848,7 +939,7 @@ describe API::API, api: true  do
         it 'is idempotent if not forked' do
           expect(project_fork_target.forked_from_project).to be_nil
           delete api("/projects/#{project_fork_target.id}/fork", admin)
-          expect(response).to have_http_status(200)
+          expect(response).to have_http_status(304)
           expect(project_fork_target.reload.forked_from_project).to be_nil
         end
       end
@@ -865,7 +956,7 @@ describe API::API, api: true  do
         post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at
       end.to change { ProjectGroupLink.count }.by(1)
 
-      expect(response.status).to eq 201
+      expect(response).to have_http_status(201)
       expect(json_response['group_id']).to eq(group.id)
       expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER)
       expect(json_response['expires_at']).to eq(expires_at.to_s)
@@ -873,18 +964,18 @@ describe API::API, api: true  do
 
     it "returns a 400 error when group id is not given" do
       post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
-      expect(response.status).to eq 400
+      expect(response).to have_http_status(400)
     end
 
     it "returns a 400 error when access level is not given" do
       post api("/projects/#{project.id}/share", user), group_id: group.id
-      expect(response.status).to eq 400
+      expect(response).to have_http_status(400)
     end
 
     it "returns a 400 error when sharing is disabled" do
       project.namespace.update(share_with_group_lock: true)
       post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
-      expect(response.status).to eq 400
+      expect(response).to have_http_status(400)
     end
 
     it 'returns a 404 error when user cannot read group' do
@@ -892,19 +983,20 @@ describe API::API, api: true  do
 
       post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER
 
-      expect(response.status).to eq 404
+      expect(response).to have_http_status(404)
     end
 
     it 'returns a 404 error when group does not exist' do
       post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER
 
-      expect(response.status).to eq 404
+      expect(response).to have_http_status(404)
     end
 
-    it "returns a 409 error when wrong params passed" do
+    it "returns a 400 error when wrong params passed" do
       post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
-      expect(response.status).to eq 409
-      expect(json_response['message']).to eq 'Group access is not included in the list'
+
+      expect(response).to have_http_status(400)
+      expect(json_response['error']).to eq 'group_access does not have a valid value'
     end
   end
 
@@ -950,35 +1042,37 @@ describe API::API, api: true  do
     let!(:public)           { create(:empty_project, :public, name: "public #{query}") }
     let!(:unfound_public)   { create(:empty_project, :public, name: 'unfound public') }
 
+    shared_examples_for 'project search response' do |args = {}|
+      it 'returns project search responses' do
+        get api("/projects/search/#{query}", current_user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(args[:results])
+        json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*query.*/) }
+      end
+    end
+
     context 'when unauthenticated' do
-      it 'returns authentication error' do
-        get api("/projects/search/#{query}")
-        expect(response).to have_http_status(401)
+      it_behaves_like 'project search response', results: 1 do
+        let(:current_user) { nil }
       end
     end
 
     context 'when authenticated' do
-      it 'returns an array of projects' do
-        get api("/projects/search/#{query}", user)
-        expect(response).to have_http_status(200)
-        expect(json_response).to be_an Array
-        expect(json_response.size).to eq(6)
-        json_response.each {|project| expect(project['name']).to match(/.*query.*/)}
+      it_behaves_like 'project search response', results: 6 do
+        let(:current_user) { user }
       end
     end
 
     context 'when authenticated as a different user' do
-      it 'returns matching public projects' do
-        get api("/projects/search/#{query}", user2)
-        expect(response).to have_http_status(200)
-        expect(json_response).to be_an Array
-        expect(json_response.size).to eq(2)
-        json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)}
+      it_behaves_like 'project search response', results: 2, match_regex: /(internal|public) query/ do
+        let(:current_user) { user2 }
       end
     end
   end
 
-  describe 'PUT /projects/:id̈́' do
+  describe 'PUT /projects/:id' do
     before { project }
     before { user }
     before { user3 }
@@ -1017,7 +1111,6 @@ describe API::API, api: true  do
 
       it 'updates visibility_level from public to private' do
         project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
-
         project_param = { public: false }
         put api("/projects/#{project3.id}", user), project_param
         expect(response).to have_http_status(200)
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 38c8ad34f9d834f9da1ac79e6ad012b64d520a2d..c90b69e8ebbcc69edbdf7d21af5d8b65aed7698c 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'mime/types'
 
-describe API::API, api: true  do
+describe API::Repositories, api: true  do
   include ApiHelpers
   include RepoHelpers
   include WorkhorseHelpers
@@ -44,7 +44,6 @@ describe API::API, api: true  do
     end
   end
 
-  
   describe 'GET /projects/:id/repository/tree?recursive=1' do
     context 'authorized user' do
       before { project.team << [user2, :reporter] }
@@ -67,7 +66,7 @@ describe API::API, api: true  do
 
         expect(json_response).to be_an Object
         json_response['message'] == '404 Tree Not Found'
-      end      
+      end
     end
 
     context "unauthorized user" do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index ce9c96ace21c11e8e1dea05929eb79158808f80f..d30361f53d4aa0de064bf336d912a1fa3724d422 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe API::API, api: true  do
+describe API::Services, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:admin) { create(:admin) }
@@ -128,7 +128,7 @@ describe API::API, api: true  do
           )
         end
 
-        it 'retusn status 200' do
+        it 'returns status 200' do
           post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
 
           expect(response).to have_http_status(200)
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index e3f22b4c5788df516d33b1786fda2d9f5352ac19..794e2b5c04dd4546df619f13929fcd8d4c09bbb9 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Session, api: true  do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 096a8ebab706fe5a20d1214793001226699c5e57..9a8d633d657a6a7ad2dabab9dcbd34f235b6e4a8 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, 'Settings', api: true  do
+describe API::Settings, 'Settings', api: true  do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 6c9df21f5983843feda4ad2d08fbbbba0fcf7d80..b3e5afdadb1599c8d1eb8ea6ab18735fea3f8b74 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::SystemHooks, api: true  do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index d563883cd4767fb66a2ce74fdb7c18bd984044a6..06fa94fae879d07dc9b51758b54bbcaca72b4c1a 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 require 'mime/types'
 
-describe API::API, api: true  do
+describe API::Tags, api: true  do
   include ApiHelpers
   include RepoHelpers
 
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index c890a51ae42b8988f7f2965870f2559af7ca8ce6..67ec31686794c54deeea4e6aaa8c0100a27ada5e 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API do
+describe API::Triggers do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 1a6e7716b2fbdd6d68b92979e7d5f91577a9f05c..f82f52e73997edaaf309241df97a60772205763a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::Users, api: true  do
   include ApiHelpers
 
   let(:user)  { create(:user) }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 05fbdb909dce664920b5a6f932f5f4228712be68..7435f3206075ed845b84280afce04e5c850fe24d 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true do
+describe API::Variables, api: true do
   include ApiHelpers
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 54b69a0cae79edc0e21bff64704cf892e6eb0066..da1b2fda70e22df67b7448f6556d179aa1a45ce6 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe API::API, api: true do
+describe API::Version, api: true do
   include ApiHelpers
 
   describe 'GET /version' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index a09d8689ff2d843a23a55f6d312b51293e4c9e26..806521299284810c5de36c0bf17c794fe0a89757 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Ci::API::API do
+describe Ci::API::Builds do
   include ApiHelpers
 
   let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index d6c26fd8a94bb6f426b5d0ef9d512c7e244113d8..bd55934d0c8f9563559b294c1ce51018871c989a 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Ci::API::API do
+describe Ci::API::Runners do
   include ApiHelpers
   include StubGitlabCalls
 
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index 0a0f979f57d659e57acb9a1748e614ad88f1f90f..2d434ab5dd81e4731f441d072af1f601258c8640 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Ci::API::API do
+describe Ci::API::Triggers do
   include ApiHelpers
 
   describe 'POST /projects/:project_id/refs/:ref/trigger' do
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 705dbb7d1c065c4342db4e6471d60355f12ea136..f5e0fdcda2dd5b3c7d95d851c72224f44dd17f1b 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -40,7 +40,7 @@ describe 'cycle analytics events' do
 
       expect(json_response['events']).not_to be_empty
 
-      first_mr_iid = MergeRequest.order(created_at: :desc).pluck(:iid).first.to_s
+      first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s
 
       expect(json_response['events'].first['iid']).to eq(first_mr_iid)
     end
@@ -135,6 +135,6 @@ describe 'cycle analytics events' do
 
     merge_merge_requests_closing_issue(issue)
 
-    ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha)
+    ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash)
   end
 end
diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb
index c0b7e86b17cb3d0bf31cdf70fb21141bf4ff12a0..6b33fe66a63767944b02cb0f1ecb2a84567806ef 100644
--- a/spec/serializers/analytics_build_entity_spec.rb
+++ b/spec/serializers/analytics_build_entity_spec.rb
@@ -7,7 +7,9 @@ describe AnalyticsBuildEntity do
 
   context 'build with an author' do
     let(:user) { create(:user) }
-    let(:build) { create(:ci_build, author: user, started_at: 2.hours.ago, finished_at: 1.hour.ago) }
+    let(:started_at) { 2.hours.ago }
+    let(:finished_at) { 1.hour.ago }
+    let(:build) { create(:ci_build, author: user, started_at: started_at, finished_at: finished_at) }
 
     subject { entity.as_json }
 
@@ -31,5 +33,54 @@ describe AnalyticsBuildEntity do
     it 'contains the duration' do
       expect(subject[:total_time]).to eq(hours: 1 )
     end
+
+    context 'no started at or finished at date' do
+      let(:started_at) { nil }
+      let(:finished_at) { nil }
+
+      it 'does not blow up' do
+        expect{ subject[:date] }.not_to raise_error
+      end
+
+      it 'shows the right message' do
+        expect(subject[:date]).to eq('Not started')
+      end
+
+      it 'shows the right total time' do
+        expect(subject[:total_time]).to eq({})
+      end
+    end
+
+    context 'no started at date' do
+      let(:started_at) { nil }
+
+      it 'does not blow up' do
+        expect{ subject[:date] }.not_to raise_error
+      end
+
+      it 'shows the right message' do
+        expect(subject[:date]).to eq('Not started')
+      end
+
+      it 'shows the right total time' do
+        expect(subject[:total_time]).to eq({})
+      end
+    end
+
+    context 'no finished at date' do
+      let(:finished_at) { nil }
+
+      it 'does not blow up' do
+        expect{ subject[:date] }.not_to raise_error
+      end
+
+      it 'shows the right message' do
+        expect(subject[:date]).to eq('about 2 hours ago')
+      end
+
+      it 'shows the right total time' do
+        expect(subject[:total_time]).to eq({ hours: 2 })
+      end
+    end
   end
 end
diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb
index a0a9d9a5f12997ea8b3b2993b57d878bc5507a85..f0551c78671308b6cbc2c942afe1ad8916f15f99 100644
--- a/spec/serializers/analytics_build_serializer_spec.rb
+++ b/spec/serializers/analytics_build_serializer_spec.rb
@@ -10,10 +10,6 @@ describe AnalyticsBuildSerializer do
   let(:resource) { create(:ci_build) }
 
   context 'when there is a single object provided' do
-    it 'it generates payload for single object' do
-      expect(json).to be_an_instance_of Hash
-    end
-
     it 'contains important elements of analyticsBuild' do
       expect(json)
         .to include(:name, :branch, :short_sha, :date, :total_time, :url, :author)
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 2842e1ba52fa96d1297b640a2cf3eb69678bc662..6afbb2df35c0164f41040464dbd08beb396bb153 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -22,10 +22,6 @@ describe AnalyticsIssueSerializer do
   end
 
   context 'when there is a single object provided' do
-    it 'it generates payload for single object' do
-      expect(json).to be_an_instance_of Hash
-    end
-
     it 'contains important elements of the issue' do
       expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author)
     end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index 564207984dff8d5c9acff7b0e94bf03886ed833f..cdfae27193fbe3449f11f09ab289499174d77c13 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -23,10 +23,6 @@ describe AnalyticsMergeRequestSerializer do
   end
 
   context 'when there is a single object provided' do
-    it 'it generates payload for single object' do
-      expect(json).to be_an_instance_of Hash
-    end
-
     it 'contains important elements of the merge request' do
       expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author, :state)
     end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index 6dcfaec259e15ea84afdf69cb88c5530246c8ed9..60c9642ee2c6662c6433ebed77b77f2c7750a682 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -1,23 +1,30 @@
 require 'spec_helper'
 
 describe BuildEntity do
+  let(:build) { create(:ci_build) }
+
   let(:entity) do
     described_class.new(build, request: double)
   end
 
   subject { entity.as_json }
 
-  context 'when build is a regular job' do
-    let(:build) { create(:ci_build) }
+  it 'contains paths to build page and retry action' do
+    expect(subject).to include(:build_path, :retry_path)
+  end
 
-    it 'contains paths to build page and retry action' do
-      expect(subject).to include(:build_path, :retry_path)
-      expect(subject).not_to include(:play_path)
-    end
+  it 'does not contain sensitive information' do
+    expect(subject).not_to include(/token/)
+    expect(subject).not_to include(/variables/)
+  end
+
+  it 'contains timestamps' do
+    expect(subject).to include(:created_at, :updated_at)
+  end
 
-    it 'does not contain sensitive information' do
-      expect(subject).not_to include(/token/)
-      expect(subject).not_to include(/variables/)
+  context 'when build is a regular job' do
+    it 'does not contain path to play action' do
+      expect(subject).not_to include(:play_path)
     end
   end
 
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 8f95c9250b0d4ca9a74b2d5c1a76dfa80baa7d9c..b7ed4eb0239d76547aeb28cf77b925300a1fce02 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -27,10 +27,6 @@ describe EnvironmentSerializer do
     let(:deployable) { create(:ci_build) }
     let(:resource) { deployment.environment }
 
-    it 'it generates payload for single object' do
-      expect(json).to be_an_instance_of Hash
-    end
-
     it 'contains important elements of environment' do
       expect(json)
         .to include(:name, :external_url, :environment_path, :last_deployment)
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index ff113efd916befea2ec0f05da4582085e88a745d..ebb11166964a8f1f1b0e55c4d176a22ddcf1efa7 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -1,31 +1,10 @@
 require 'spec_helper'
 
 describe Ci::ProcessPipelineService, services: true do
-  let(:pipeline) { create(:ci_pipeline, ref: 'master') }
+  let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') }
   let(:user) { create(:user) }
-  let(:config) { nil }
-
-  before do
-    allow(pipeline).to receive(:ci_yaml_file).and_return(config)
-  end
 
   describe '#execute' do
-    def all_builds
-      pipeline.builds
-    end
-
-    def builds
-      all_builds.where.not(status: [:created, :skipped])
-    end
-
-    def process_pipeline
-      described_class.new(pipeline.project, user).execute(pipeline)
-    end
-
-    def succeed_pending
-      builds.pending.update_all(status: 'success')
-    end
-
     context 'start queuing next builds' do
       before do
         create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0)
@@ -223,10 +202,6 @@ describe Ci::ProcessPipelineService, services: true do
           pipeline.builds.running_or_pending.each(&:success)
           expect(manual_actions).to be_many # production and clear cache
         end
-
-        def manual_actions
-          pipeline.manual_actions
-        end
       end
     end
 
@@ -282,15 +257,6 @@ describe Ci::ProcessPipelineService, services: true do
           expect(builds.map(&:status)).to eq(%w[success skipped pending])
         end
       end
-
-      def create_build(name, stage_idx, when_value = nil)
-        create(:ci_build,
-               :created,
-               pipeline: pipeline,
-               name: name,
-               stage_idx: stage_idx,
-               when: when_value)
-      end
     end
 
     context 'when failed build in the middle stage is retried' do
@@ -327,65 +293,92 @@ describe Ci::ProcessPipelineService, services: true do
       end
     end
 
-    context 'creates a builds from .gitlab-ci.yml' do
-      let(:config) do
-        YAML.dump({
-          rspec: {
-            stage: 'test',
-            script: 'rspec'
-          },
-          rubocop: {
-            stage: 'test',
-            script: 'rubocop'
-          },
-          deploy: {
-            stage: 'deploy',
-            script: 'deploy'
-          }
-        })
+    context 'when there are builds that are not created yet' do
+      let(:pipeline) do
+        create(:ci_pipeline, config: config)
       end
 
-      # Using stubbed .gitlab-ci.yml created in commit factory
-      #
+      let(:config) do
+        { rspec: { stage: 'test', script: 'rspec' },
+          deploy: { stage: 'deploy', script: 'rsync' } }
+      end
 
       before do
-        stub_ci_pipeline_yaml_file(config)
         create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0)
         create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0)
       end
 
-      it 'when processing a pipeline' do
-        # Currently we have two builds with state created
+      it 'processes the pipeline' do
+        # Currently we have five builds with state created
+        #
         expect(builds.count).to eq(0)
         expect(all_builds.count).to eq(2)
 
-        # Create builds will mark the created as pending
-        expect(process_pipeline).to be_truthy
+        # Process builds service will enqueue builds from the first stage.
+        #
+        process_pipeline
+
         expect(builds.count).to eq(2)
         expect(all_builds.count).to eq(2)
 
-        # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml
-        # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy)
+        # When builds succeed we will enqueue remaining builds.
+        #
+        # We will have 2 succeeded, 1 pending (from stage test), total 4 (two
+        # additional build from `.gitlab-ci.yml`).
+        #
         succeed_pending
-        expect(process_pipeline).to be_truthy
+        process_pipeline
+
         expect(builds.success.count).to eq(2)
-        expect(builds.pending.count).to eq(2)
-        expect(all_builds.count).to eq(5)
+        expect(builds.pending.count).to eq(1)
+        expect(all_builds.count).to eq(4)
 
-        # When we succeed the 2 pending from stage test,
-        # We will queue a deploy stage, no new builds will be created
+        # When pending build succeeds in stage test, we enqueue deploy stage.
+        #
         succeed_pending
-        expect(process_pipeline).to be_truthy
+        process_pipeline
+
         expect(builds.pending.count).to eq(1)
-        expect(builds.success.count).to eq(4)
-        expect(all_builds.count).to eq(5)
+        expect(builds.success.count).to eq(3)
+        expect(all_builds.count).to eq(4)
 
-        # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created
+        # When the last one succeeds we have 4 successful builds.
+        #
         succeed_pending
-        expect(process_pipeline).to be_falsey
-        expect(builds.success.count).to eq(5)
-        expect(all_builds.count).to eq(5)
+        process_pipeline
+
+        expect(builds.success.count).to eq(4)
+        expect(all_builds.count).to eq(4)
       end
     end
   end
+
+  def all_builds
+    pipeline.builds
+  end
+
+  def builds
+    all_builds.where.not(status: [:created, :skipped])
+  end
+
+  def process_pipeline
+    described_class.new(pipeline.project, user).execute(pipeline)
+  end
+
+  def succeed_pending
+    builds.pending.update_all(status: 'success')
+  end
+
+  def manual_actions
+    pipeline.manual_actions
+  end
+
+  def create_build(name, stage_idx, when_value = nil)
+    create(:ci_build,
+           :created,
+           pipeline: pipeline,
+           name: name,
+           stage_idx: stage_idx,
+           when: when_value)
+  end
 end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 9d7702f5c96f8b26f36d21ed211c828b0f917eba..e7624e707254451b69d3b15353129e0113414687 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -263,7 +263,7 @@ describe GitPushService, services: true do
         author_email: commit_author.email
       )
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
         and_return(commit)
 
       allow(project.repository).to receive(:commits_between).and_return([commit])
@@ -321,7 +321,7 @@ describe GitPushService, services: true do
         committed_date: commit_time
       )
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
         and_return(commit)
 
       allow(project.repository).to receive(:commits_between).and_return([commit])
@@ -360,7 +360,7 @@ describe GitPushService, services: true do
       allow(project.repository).to receive(:commits_between).
         and_return([closing_commit])
 
-      allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit).
+      allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit).
         and_return(closing_commit)
 
       project.team << [commit_author, :master]
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 4465f22a001ac48873168bff0daf5ce252475970..7a54373963e54bbba865402155a0f3d8c3012015 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -62,7 +62,7 @@ describe Issues::CloseService, services: true do
 
       it 'creates system note about issue reassign' do
         note = issue.notes.last
-        expect(note.note).to include "Status changed to closed"
+        expect(note.note).to include "closed"
       end
 
       it 'marks todos as done' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index f0ded06b78504428863a4c44b9794040b1ae8d2e..c7de0d0c534b80cf958ed090668d25067b4454ac 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -81,11 +81,11 @@ describe Issues::MoveService, services: true do
         end
 
         it 'adds system note to old issue at the end' do
-          expect(old_issue.notes.last.note).to match /^Moved to/
+          expect(old_issue.notes.last.note).to start_with 'moved to'
         end
 
         it 'adds system note to new issue at the end' do
-          expect(new_issue.notes.last.note).to match /^Moved from/
+          expect(new_issue.notes.last.note).to start_with 'moved from'
         end
 
         it 'closes old issue' do
@@ -151,7 +151,7 @@ describe Issues::MoveService, services: true do
           end
 
           it 'adds a system note about move after rewritten notes' do
-            expect(system_notes.last.note).to match /^Moved from/
+            expect(system_notes.last.note).to match /^moved from/
           end
 
           it 'preserves orignal author of comment' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 4777a90639e459f7545d208b94bb6fb6d89d5a37..500d224ff98434fa39671e17b419ab13aec52e1c 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -2,6 +2,8 @@
 require 'spec_helper'
 
 describe Issues::UpdateService, services: true do
+  include EmailHelpers
+
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:user3) { create(:user) }
@@ -91,24 +93,24 @@ describe Issues::UpdateService, services: true do
         end
 
         it 'creates system note about issue reassign' do
-          note = find_note('Reassigned to')
+          note = find_note('assigned to')
 
           expect(note).not_to be_nil
-          expect(note.note).to include "Reassigned to \@#{user2.username}"
+          expect(note.note).to include "assigned to #{user2.to_reference}"
         end
 
         it 'creates system note about issue label edit' do
-          note = find_note('Added ~')
+          note = find_note('added ~')
 
           expect(note).not_to be_nil
-          expect(note.note).to include "Added ~#{label.id} label"
+          expect(note.note).to include "added #{label.to_reference} label"
         end
 
         it 'creates system note about title change' do
-          note = find_note('Changed title:')
+          note = find_note('changed title')
 
           expect(note).not_to be_nil
-          expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
+          expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**'
         end
       end
     end
@@ -128,10 +130,10 @@ describe Issues::UpdateService, services: true do
       it 'creates system note about confidentiality change' do
         update_issue(confidential: true)
 
-        note = find_note('Made the issue confidential')
+        note = find_note('made the issue confidential')
 
         expect(note).not_to be_nil
-        expect(note.note).to eq 'Made the issue confidential'
+        expect(note.note).to eq 'made the issue confidential'
       end
 
       it 'executes confidential issue hooks' do
@@ -269,8 +271,8 @@ describe Issues::UpdateService, services: true do
         before { update_issue(description: "- [x] Task 1\n- [X] Task 2") }
 
         it 'creates system note about task status change' do
-          note1 = find_note('Marked the task **Task 1** as completed')
-          note2 = find_note('Marked the task **Task 2** as completed')
+          note1 = find_note('marked the task **Task 1** as completed')
+          note2 = find_note('marked the task **Task 2** as completed')
 
           expect(note1).not_to be_nil
           expect(note2).not_to be_nil
@@ -284,8 +286,8 @@ describe Issues::UpdateService, services: true do
         end
 
         it 'creates system note about task status change' do
-          note1 = find_note('Marked the task **Task 1** as incomplete')
-          note2 = find_note('Marked the task **Task 2** as incomplete')
+          note1 = find_note('marked the task **Task 1** as incomplete')
+          note2 = find_note('marked the task **Task 2** as incomplete')
 
           expect(note1).not_to be_nil
           expect(note2).not_to be_nil
@@ -299,7 +301,7 @@ describe Issues::UpdateService, services: true do
         end
 
         it 'does not create a system note' do
-          note = find_note('Marked the task **Task 2** as incomplete')
+          note = find_note('marked the task **Task 2** as incomplete')
 
           expect(note).to be_nil
         end
@@ -312,7 +314,7 @@ describe Issues::UpdateService, services: true do
         end
 
         it 'does not create a system note referencing the position the old item' do
-          note = find_note('Marked the task **Two** as incomplete')
+          note = find_note('marked the task **Two** as incomplete')
 
           expect(note).to be_nil
         end
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index ddf3527dc0ffee0207549a9d5e28efe4d13807ae..13654a0881c23a63d4c903e090ecc9879bad3cc6 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Labels::TransferService, services: true do
   describe '#execute' do
-    let(:user)    { create(:user) }
+    let(:user)    { create(:admin) }
     let(:group_1) { create(:group) }
     let(:group_2) { create(:group) }
     let(:group_3) { create(:group) }
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index a44312dd36390e4b2920e9a4a074a3ab5aa398b3..bb7830c7eea782e79f356a08b6a9e1ab4b1a3135 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -20,13 +20,19 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
   let(:todo_service) { TodoService.new }
 
   let(:merge_request) do
-    create(:merge_request, merge_user: user, source_branch: 'master',
-                           target_branch: 'feature', source_project: project, target_project: project,
+    create(:merge_request, merge_user: user,
+                           source_branch: 'master',
+                           target_branch: 'feature',
+                           source_project: project,
+                           target_project: project,
                            state: 'opened')
   end
 
   before do
-    allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+    allow_any_instance_of(MergeRequest)
+      .to receive(:head_pipeline)
+      .and_return(pipeline)
+
     allow(service).to receive(:todo_service).and_return(todo_service)
   end
 
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 3f5df049ea2aeb4afbebec55bcc872e2ecd66a52..dc945ca486878042cba6da040fceb05ec6e76a44 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -24,6 +24,8 @@ describe MergeRequests::BuildService, services: true do
   end
 
   before do
+    project.team << [user, :guest]
+
     allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
     allow(project).to receive(:commit).and_return(commit_1)
     allow(project).to receive(:commit).and_return(commit_2)
@@ -168,6 +170,16 @@ describe MergeRequests::BuildService, services: true do
           expect(merge_request.title).to eq("Resolve \"#{issue.title}\"")
         end
 
+        context 'when issue is not accessible to user' do
+          before do
+            project.team.truncate
+          end
+
+          it 'uses branch title as the merge request title' do
+            expect(merge_request.title).to eq("#{issue.iid} fix issue")
+          end
+        end
+
         context 'issue does not exist' do
           let(:source_branch) { "#{issue.iid.succ}-fix-issue" }
 
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 24c25e4350f9b3913a9e6a8025f069abd5c8a697..5f6a7716beb3319b110ec06b2335d31734bdd144 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -42,7 +42,7 @@ describe MergeRequests::CloseService, services: true do
 
       it 'creates system note about merge_request reassign' do
         note = @merge_request.notes.last
-        expect(note.note).to include 'Status changed to closed'
+        expect(note.note).to include 'closed'
       end
 
       it 'marks todos as done' do
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index 807f89e80b76736270ceda59138d48f47164bd11..05cdbe5287a4dbf4b7349a277e7036de8ea31551 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -10,6 +10,7 @@ describe MergeRequests::MergeRequestDiffCacheService do
 
       expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
       expect(Rails.cache).to receive(:write).with(cache_key, anything)
+      allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => true))
 
       subject.execute(merge_request)
     end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 7db32a33c93c97793e504c184436a2d0b1fc44e3..5a89acc96a4f941a180bdeaa0013f90171cbbd18 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -34,7 +34,7 @@ describe MergeRequests::MergeService, services: true do
 
       it 'creates system note about merge_request merge' do
         note = merge_request.notes.last
-        expect(note.note).to include 'Status changed to merged'
+        expect(note.note).to include 'merged'
       end
     end
 
@@ -75,7 +75,7 @@ describe MergeRequests::MergeService, services: true do
           commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
           allow(merge_request).to receive(:commits).and_return([commit])
 
-          expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, an_instance_of(JIRA::Resource::Issue)).once
+          expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once
 
           service.execute(merge_request)
         end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 1f90efdbd6af69f2dc36bf8393cc2a670479bcb0..963d9573ac410f72fa13e882765cbad9703863f1 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -21,7 +21,10 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
 
     context 'first time enabling' do
       before do
-        allow(merge_request).to receive(:pipeline).and_return(pipeline)
+        allow(merge_request)
+          .to receive(:head_pipeline)
+          .and_return(pipeline)
+
         service.execute(merge_request)
       end
 
@@ -34,7 +37,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
 
       it 'creates a system note' do
         note = merge_request.notes.last
-        expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/
+        expect(note.note).to match /enabled an automatic merge when the build for (\w+\/\w+@)?\h{8}/
       end
     end
 
@@ -43,8 +46,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
       let(:build)   { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
 
       before do
-        allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline)
-        allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true)
+        allow(mr_merge_if_green_enabled).to receive(:head_pipeline)
+          .and_return(pipeline)
+
+        allow(mr_merge_if_green_enabled).to receive(:mergeable?)
+          .and_return(true)
+
         allow(pipeline).to receive(:success?).and_return(true)
       end
 
@@ -113,7 +120,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
 
     it 'Posts a system note' do
       note = mr_merge_if_green_enabled.notes.last
-      expect(note.note).to include 'Canceled the automatic merge'
+      expect(note.note).to include 'canceled the automatic merge'
     end
   end
 
@@ -138,9 +145,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
 
       before do
         # This behavior of MergeRequest: we instantiate a new object
-        allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do
-          Ci::Pipeline.find(pipeline.id)
-        end
+        #
+        allow_any_instance_of(MergeRequest)
+          .to receive(:head_pipeline)
+          .and_wrap_original do
+            Ci::Pipeline.find(pipeline.id)
+          end
       end
 
       it "doesn't merge if any of stages failed" do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index e515bc9f89c2dabc066b3c9b776635a070026cca..bc340ff9d3c9be0a3973d230209c812d5f897276 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -76,10 +76,10 @@ describe MergeRequests::RefreshService, services: true do
         reload_mrs
       end
 
-      it { expect(@merge_request.notes.last.note).to include('changed to merged') }
+      it { expect(@merge_request.notes.last.note).to include('merged') }
       it { expect(@merge_request).to be_merged }
       it { expect(@fork_merge_request).to be_merged }
-      it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
+      it { expect(@fork_merge_request.notes.last.note).to include('merged') }
       it { expect(@build_failed_todo).to be_done }
       it { expect(@fork_build_failed_todo).to be_done }
     end
@@ -95,11 +95,11 @@ describe MergeRequests::RefreshService, services: true do
         reload_mrs
       end
 
-      it { expect(@merge_request.notes.last.note).to include('changed to merged') }
+      it { expect(@merge_request.notes.last.note).to include('merged') }
       it { expect(@merge_request).to be_merged }
       it { expect(@merge_request.diffs.size).to be > 0 }
       it { expect(@fork_merge_request).to be_merged }
-      it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
+      it { expect(@fork_merge_request.notes.last.note).to include('merged') }
       it { expect(@build_failed_todo).to be_done }
       it { expect(@fork_build_failed_todo).to be_done }
     end
@@ -119,7 +119,7 @@ describe MergeRequests::RefreshService, services: true do
 
       it { expect(@merge_request.notes).to be_empty }
       it { expect(@merge_request).to be_open }
-      it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') }
+      it { expect(@fork_merge_request.notes.last.note).to include('added 28 commits') }
       it { expect(@fork_merge_request).to be_open }
       it { expect(@build_failed_todo).to be_pending }
       it { expect(@fork_build_failed_todo).to be_pending }
@@ -146,7 +146,7 @@ describe MergeRequests::RefreshService, services: true do
         reload_mrs
       end
 
-      it { expect(@merge_request.notes.last.note).to include('changed to merged') }
+      it { expect(@merge_request.notes.last.note).to include('merged') }
       it { expect(@merge_request).to be_merged }
       it { expect(@fork_merge_request).to be_open }
       it { expect(@fork_merge_request.notes).to be_empty }
@@ -169,8 +169,8 @@ describe MergeRequests::RefreshService, services: true do
         expect(@merge_request).to be_open
 
         notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
-        expect(notes[0]).to include('Restored source branch `master`')
-        expect(notes[1]).to include('Added 28 commits')
+        expect(notes[0]).to include('restored source branch `master`')
+        expect(notes[1]).to include('added 28 commits')
         expect(@fork_merge_request).to be_open
       end
     end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index af7424a76a98c2c7231d07edacc7fd0afb40c83b..a99d4eac9bde5db6288496b3c8cc6597218029e0 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -41,7 +41,7 @@ describe MergeRequests::ReopenService, services: true do
 
       it 'creates system note about merge_request reopen' do
         note = merge_request.notes.last
-        expect(note.note).to include 'Status changed to reopened'
+        expect(note.note).to include 'reopened'
       end
     end
 
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index cb5d7cdb467bde2d1cbe0cb0e1b51d74053f3280..790ef765f3a1b641faacbf46c9a431a19cf1b78a 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe MergeRequests::UpdateService, services: true do
+  include EmailHelpers
+
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
@@ -79,31 +81,31 @@ describe MergeRequests::UpdateService, services: true do
       end
 
       it 'creates system note about merge_request reassign' do
-        note = find_note('Reassigned to')
+        note = find_note('assigned to')
 
         expect(note).not_to be_nil
-        expect(note.note).to include "Reassigned to \@#{user2.username}"
+        expect(note.note).to include "assigned to #{user2.to_reference}"
       end
 
       it 'creates system note about merge_request label edit' do
-        note = find_note('Added ~')
+        note = find_note('added ~')
 
         expect(note).not_to be_nil
-        expect(note.note).to include "Added ~#{label.id} label"
+        expect(note.note).to include "added #{label.to_reference} label"
       end
 
       it 'creates system note about title change' do
-        note = find_note('Changed title:')
+        note = find_note('changed title')
 
         expect(note).not_to be_nil
-        expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
+        expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**'
       end
 
       it 'creates system note about branch change' do
-        note = find_note('Target')
+        note = find_note('changed target')
 
         expect(note).not_to be_nil
-        expect(note.note).to eq 'Target branch changed from `master` to `target`'
+        expect(note.note).to eq 'changed target branch from `master` to `target`'
       end
 
       context 'when not including source branch removal options' do
@@ -258,8 +260,8 @@ describe MergeRequests::UpdateService, services: true do
         before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) }
 
         it 'creates system note about task status change' do
-          note1 = find_note('Marked the task **Task 1** as completed')
-          note2 = find_note('Marked the task **Task 2** as completed')
+          note1 = find_note('marked the task **Task 1** as completed')
+          note2 = find_note('marked the task **Task 2** as completed')
 
           expect(note1).not_to be_nil
           expect(note2).not_to be_nil
@@ -273,8 +275,8 @@ describe MergeRequests::UpdateService, services: true do
         end
 
         it 'creates system note about task status change' do
-          note1 = find_note('Marked the task **Task 1** as incomplete')
-          note2 = find_note('Marked the task **Task 2** as incomplete')
+          note1 = find_note('marked the task **Task 1** as incomplete')
+          note2 = find_note('marked the task **Task 2** as incomplete')
 
           expect(note1).not_to be_nil
           expect(note2).not_to be_nil
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 08ae61708a556060c9157b80def38ec8e5750285..f3e80ac22a07468c0b78a4044cf3f10fb7518402 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe NotificationService, services: true do
+  include EmailHelpers
+
   let(:notification) { NotificationService.new }
 
   around(:each) do |example|
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 7dcd03496bbcae7df6be43437d632d175e260041..90771825f5ce85280921ce1703b987b8db5eadc5 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -7,15 +7,21 @@ describe Projects::DestroyService, services: true do
   let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
   let!(:async) { false } # execute or async_execute
 
+  shared_examples 'deleting the project' do
+    it 'deletes the project' do
+      expect(Project.all).not_to include(project)
+      expect(Dir.exist?(path)).to be_falsey
+      expect(Dir.exist?(remove_path)).to be_falsey
+    end
+  end
+
   context 'Sidekiq inline' do
     before do
       # Run sidekiq immediatly to check that renamed repository will be removed
       Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
     end
 
-    it { expect(Project.all).not_to include(project) }
-    it { expect(Dir.exist?(path)).to be_falsey }
-    it { expect(Dir.exist?(remove_path)).to be_falsey }
+    it_behaves_like 'deleting the project'
   end
 
   context 'Sidekiq fake' do
@@ -38,11 +44,21 @@ describe Projects::DestroyService, services: true do
       Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
     end
 
-    it 'deletes the project' do
-      expect(Project.all).not_to include(project)
-      expect(Dir.exist?(path)).to be_falsey
-      expect(Dir.exist?(remove_path)).to be_falsey
+    it_behaves_like 'deleting the project'
+  end
+
+  context 'delete with pipeline' do # which has optimistic locking
+    let!(:pipeline) { create(:ci_pipeline, project: project) }
+
+    before do
+      expect(project).to receive(:destroy!).and_call_original
+
+      perform_enqueued_jobs do
+        destroy_project(project, user, {})
+      end
     end
+
+    it_behaves_like 'deleting the project'
   end
 
   context 'container registry' do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 2a5709c6322f76e5603b7c27a50774ca1a39718d..4a8f6c321aa4ebc606996fb29f2313137d9bdfd9 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -50,7 +50,7 @@ describe SystemNoteService, services: true do
 
       context 'without existing commits' do
         it 'adds a message header' do
-          expect(note_lines[0]).to eq "Added #{new_commits.size} commits:"
+          expect(note_lines[0]).to eq "added #{new_commits.size} commits"
         end
 
         it 'adds a message line for each commit' do
@@ -120,7 +120,7 @@ describe SystemNoteService, services: true do
 
     context 'when assignee added' do
       it 'sets the note text' do
-        expect(subject.note).to eq "Reassigned to @#{assignee.username}"
+        expect(subject.note).to eq "assigned to @#{assignee.username}"
       end
     end
 
@@ -128,7 +128,7 @@ describe SystemNoteService, services: true do
       let(:assignee) { nil }
 
       it 'sets the note text' do
-        expect(subject.note).to eq 'Assignee removed'
+        expect(subject.note).to eq 'removed assignee'
       end
     end
   end
@@ -147,7 +147,7 @@ describe SystemNoteService, services: true do
       let(:removed) { [] }
 
       it 'sets the note text' do
-        expect(subject.note).to eq "Added ~#{labels[0].id} ~#{labels[1].id} labels"
+        expect(subject.note).to eq "added ~#{labels[0].id} ~#{labels[1].id} labels"
       end
     end
 
@@ -156,7 +156,7 @@ describe SystemNoteService, services: true do
       let(:removed) { labels }
 
       it 'sets the note text' do
-        expect(subject.note).to eq "Removed ~#{labels[0].id} ~#{labels[1].id} labels"
+        expect(subject.note).to eq "removed ~#{labels[0].id} ~#{labels[1].id} labels"
       end
     end
 
@@ -165,7 +165,7 @@ describe SystemNoteService, services: true do
       let(:removed) { [labels[1]] }
 
       it 'sets the note text' do
-        expect(subject.note).to eq "Added ~#{labels[0].id} and removed ~#{labels[1].id} labels"
+        expect(subject.note).to eq "added ~#{labels[0].id} and removed ~#{labels[1].id} labels"
       end
     end
   end
@@ -179,7 +179,7 @@ describe SystemNoteService, services: true do
 
     context 'when milestone added' do
       it 'sets the note text' do
-        expect(subject.note).to eq "Milestone changed to #{milestone.to_reference}"
+        expect(subject.note).to eq "changed milestone to #{milestone.to_reference}"
       end
     end
 
@@ -187,7 +187,7 @@ describe SystemNoteService, services: true do
       let(:milestone) { nil }
 
       it 'sets the note text' do
-        expect(subject.note).to eq 'Milestone removed'
+        expect(subject.note).to eq 'removed milestone'
       end
     end
   end
@@ -204,13 +204,13 @@ describe SystemNoteService, services: true do
       let(:source) { double('commit', gfm_reference: 'commit 123456') }
 
       it 'sets the note text' do
-        expect(subject.note).to eq "Status changed to #{status} by commit 123456"
+        expect(subject.note).to eq "#{status} via commit 123456"
       end
     end
 
     context 'without a source' do
       it 'sets the note text' do
-        expect(subject.note).to eq "Status changed to #{status}"
+        expect(subject.note).to eq status
       end
     end
   end
@@ -226,7 +226,7 @@ describe SystemNoteService, services: true do
     it_behaves_like 'a system note'
 
     it "posts the Merge When Build Succeeds system note" do
-      expect(subject.note).to match  /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/
+      expect(subject.note).to match  /enabled an automatic merge when the build for (\w+\/\w+@)?\h{40} succeeds/
     end
   end
 
@@ -240,7 +240,7 @@ describe SystemNoteService, services: true do
     it_behaves_like 'a system note'
 
     it "posts the Merge When Build Succeeds system note" do
-      expect(subject.note).to eq  "Canceled the automatic merge"
+      expect(subject.note).to eq  "canceled the automatic merge"
     end
   end
 
@@ -252,7 +252,7 @@ describe SystemNoteService, services: true do
 
       it 'sets the note text' do
         expect(subject.note).
-          to eq "Changed title: **{-Old title-}** → **{+#{noteable.title}+}**"
+          to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**"
       end
     end
   end
@@ -264,7 +264,7 @@ describe SystemNoteService, services: true do
       it_behaves_like 'a system note'
 
       it 'sets the note text' do
-        expect(subject.note).to eq 'Made the issue visible'
+        expect(subject.note).to eq 'made the issue visible to everyone'
       end
     end
   end
@@ -278,7 +278,7 @@ describe SystemNoteService, services: true do
 
     context 'when target branch name changed' do
       it 'sets the note text' do
-        expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`"
+        expect(subject.note).to eq "changed target branch from `#{old_branch}` to `#{new_branch}`"
       end
     end
   end
@@ -290,7 +290,7 @@ describe SystemNoteService, services: true do
 
     context 'when source branch deleted' do
       it 'sets the note text' do
-        expect(subject.note).to eq "Deleted source branch `feature`"
+        expect(subject.note).to eq "deleted source branch `feature`"
       end
     end
   end
@@ -302,7 +302,7 @@ describe SystemNoteService, services: true do
 
     context 'when a branch is created from the new branch button' do
       it 'sets the note text' do
-        expect(subject.note).to match /\AStarted branch [`1-mepmep`]/
+        expect(subject.note).to match /\Acreated branch [`1-mepmep`]/
       end
     end
   end
@@ -338,13 +338,13 @@ describe SystemNoteService, services: true do
             let(:mentioner) { project2.repository.commit }
 
             it 'references the mentioning commit' do
-              expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}"
+              expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}"
             end
           end
 
           context 'from non-Commit' do
             it 'references the mentioning object' do
-              expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}"
+              expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}"
             end
           end
         end
@@ -354,13 +354,13 @@ describe SystemNoteService, services: true do
             let(:mentioner) { project.repository.commit }
 
             it 'references the mentioning commit' do
-              expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}"
+              expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}"
             end
           end
 
           context 'from non-Commit' do
             it 'references the mentioning object' do
-              expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}"
+              expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}"
             end
           end
         end
@@ -370,7 +370,11 @@ describe SystemNoteService, services: true do
 
   describe '.cross_reference?' do
     it 'is truthy when text begins with expected text' do
-      expect(described_class.cross_reference?('Mentioned in something')).to be_truthy
+      expect(described_class.cross_reference?('mentioned in something')).to be_truthy
+    end
+
+    it 'is truthy when text begins with legacy capitalized expected text' do
+      expect(described_class.cross_reference?('mentioned in something')).to be_truthy
     end
 
     it 'is falsey when text does not begin with expected text' do
@@ -433,6 +437,19 @@ describe SystemNoteService, services: true do
         expect(described_class.cross_reference_exists?(noteable, commit1)).
           to be_falsey
       end
+
+      context 'legacy capitalized cross reference' do
+        before do
+          # Mention issue (noteable) from commit0
+          system_note = described_class.cross_reference(noteable, commit0, author)
+          system_note.update(note: system_note.note.capitalize)
+        end
+
+        it 'is truthy when already mentioned' do
+          expect(described_class.cross_reference_exists?(noteable, commit0)).
+            to be_truthy
+        end
+      end
     end
 
     context 'commit from commit' do
@@ -450,6 +467,19 @@ describe SystemNoteService, services: true do
         expect(described_class.cross_reference_exists?(commit1, commit0)).
           to be_falsey
       end
+
+      context 'legacy capitalized cross reference' do
+        before do
+          # Mention commit1 from commit0
+          system_note = described_class.cross_reference(commit0, commit1, author)
+          system_note.update(note: system_note.note.capitalize)
+        end
+
+        it 'is truthy when already mentioned' do
+          expect(described_class.cross_reference_exists?(commit0, commit1)).
+            to be_truthy
+        end
+      end
     end
 
     context 'commit with cross-reference from fork' do
@@ -465,6 +495,18 @@ describe SystemNoteService, services: true do
         expect(described_class.cross_reference_exists?(noteable, commit2)).
             to be true
       end
+
+      context 'legacy capitalized cross reference' do
+        before do
+          system_note = described_class.cross_reference(noteable, commit0, author2)
+          system_note.update(note: system_note.note.capitalize)
+        end
+
+        it 'is true when a fork mentions an external issue' do
+          expect(described_class.cross_reference_exists?(noteable, commit2)).
+              to be true
+        end
+      end
     end
   end
 
@@ -498,7 +540,7 @@ describe SystemNoteService, services: true do
       it_behaves_like 'cross project mentionable'
 
       it 'notifies about noteable being moved to' do
-        expect(subject.note).to match /Moved to/
+        expect(subject.note).to match /moved to/
       end
     end
 
@@ -508,7 +550,7 @@ describe SystemNoteService, services: true do
       it_behaves_like 'cross project mentionable'
 
       it 'notifies about noteable being moved from' do
-        expect(subject.note).to match /Moved from/
+        expect(subject.note).to match /moved from/
       end
     end
 
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bead1a006d149118acdd6251576fb8ce53c6f07c..6ee3307512ddff66db439c892e81069a6401cbaf 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -32,7 +32,7 @@ RSpec.configure do |config|
   config.include LoginHelpers, type: :feature
   config.include SearchHelpers, type: :feature
   config.include StubConfiguration
-  config.include EmailHelpers
+  config.include EmailHelpers, type: :mailer
   config.include TestEnv
   config.include ActiveJob::TestHelper
   config.include ActiveSupport::Testing::TimeHelpers
@@ -55,8 +55,12 @@ RSpec.configure do |config|
 
   config.around(:each, :redis) do |example|
     Gitlab::Redis.with(&:flushall)
+    Sidekiq.redis(&:flushall)
+
     example.run
+
     Gitlab::Redis.with(&:flushall)
+    Sidekiq.redis(&:flushall)
   end
 end
 
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index e1f90e17cceae7d9b3b7eac0b44589ac714d1998..16d5f2bf0b8421eac464307fe631fe240de97278 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -7,7 +7,15 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10
 
 Capybara.javascript_driver = :poltergeist
 Capybara.register_driver :poltergeist do |app|
-  Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
+  Capybara::Poltergeist::Driver.new(
+    app,
+    js_errors: true,
+    timeout: timeout,
+    window_size: [1366, 768],
+    phantomjs_options: [
+      '--load-images=no'
+    ]
+  )
 end
 
 Capybara.default_max_wait_time = timeout
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
index aa89afd8fb36d53e6b69a2d76cad8b71b25c59cc..72af2c70324ca10db0d7acf5a568d5299ea7155d 100644
--- a/spec/support/carrierwave.rb
+++ b/spec/support/carrierwave.rb
@@ -1,7 +1,7 @@
 CarrierWave.root = 'tmp/tests/uploads'
 
 RSpec.configure do |config|
-  config.after(:suite) do
+  config.after(:each) do
     FileUtils.rm_rf('tmp/tests/uploads')
   end
 end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
index adc3f48b43407c16dbb88b101b283a167baa124e..99e98eebdb4d3df9636169d146d917c2e7e4710c 100644
--- a/spec/support/javascript_fixtures_helpers.rb
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -1,3 +1,4 @@
+require 'action_dispatch/testing/test_request'
 require 'fileutils'
 require 'gitlab/popen'
 
@@ -30,13 +31,17 @@ module JavaScriptFixturesHelpers
     if response_mime_type.html?
       doc = Nokogiri::HTML::DocumentFragment.parse(fixture)
 
+      link_tags = doc.css('link')
+      link_tags.remove
+
       scripts = doc.css('script')
       scripts.remove
 
       fixture = doc.to_html
 
       # replace relative links
-      fixture.gsub!(%r{="/}, '="https://fixture.invalid/')
+      test_host = ActionDispatch::TestRequest::DEFAULT_ENV['HTTP_HOST']
+      fixture.gsub!(%r{="/}, "=\"http://#{test_host}/")
     end
 
     FileUtils.mkdir_p(File.dirname(fixture_file_name))
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index 0497e39186006381781a6743c3a47805b0b39860..ceddb6565961a0d5aa67c5bd65c552828f6016ce 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -7,7 +7,7 @@ module AccessMatchers
   extend RSpec::Matchers::DSL
   include Warden::Test::Helpers
 
-  def emulate_user(user)
+  def emulate_user(user, membership = nil)
     case user
     when :user
       login_as(create(:user))
@@ -18,6 +18,19 @@ module AccessMatchers
     when :external
       login_as(create(:user, external: true))
     when User
+      login_as(user)
+    when *Gitlab::Access.sym_options_with_owner.keys
+      raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership
+
+      role = user
+
+      if role == :owner && membership.owner
+        user = membership.owner
+      else
+        user = create(:user)
+        membership.public_send(:"add_#{role}", user)
+      end
+
       login_as(user)
     else
       raise ArgumentError, "cannot emulate user #{user}"
@@ -26,8 +39,7 @@ module AccessMatchers
 
   def description_for(user, type)
     if user.kind_of?(User)
-      # User#inspect displays too much information for RSpec's description
-      # messages
+      # User#inspect displays too much information for RSpec's descriptions
       "be #{type} for the specified user"
     else
       "be #{type} for #{user}"
@@ -36,21 +48,31 @@ module AccessMatchers
 
   matcher :be_allowed_for do |user|
     match do |url|
-      emulate_user(user)
-      visit url
+      emulate_user(user, @membership)
+      visit(url)
+
       status_code != 404 && current_path != new_user_session_path
     end
 
+    chain :of do |membership|
+      @membership = membership
+    end
+
     description { description_for(user, 'allowed') }
   end
 
   matcher :be_denied_for do |user|
     match do |url|
-      emulate_user(user)
-      visit url
+      emulate_user(user, @membership)
+      visit(url)
+
       status_code == 404 || current_path == new_user_session_path
     end
 
+    chain :of do |membership|
+      @membership = membership
+    end
+
     description { description_for(user, 'denied') }
   end
 end
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 02605d6b70e0bac8e0a6d1aa8b4e658b930d0fb3..92cf3de544821ff2318fa41e18056e125ac7617b 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -1,9 +1,9 @@
 RSpec::Matchers.define :have_issuable_counts do |opts|
-  match do |actual|
-    expected_counts = opts.map do |state, count|
-      "#{state.to_s.humanize} #{count}"
-    end
+  expected_counts = opts.map do |state, count|
+    "#{state.to_s.humanize} #{count}"
+  end
 
+  match do |actual|
     actual.within '.issues-state-filters' do
       expected_counts.each do |expected_count|
         expect(actual).to have_content(expected_count)
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 8c98b1f988cb46df98ed1c1d351f899375bd1d83..97b8b342eb22a46bed8383a62213a8880c6269f1 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -38,9 +38,9 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('h1 a#gitlab-markdown')
-      expect(actual).to have_selector('h2 a#markdown')
-      expect(actual).to have_selector('h3 a#autolinkfilter')
+      expect(actual).to have_selector('h1 a#user-content-gitlab-markdown')
+      expect(actual).to have_selector('h2 a#user-content-markdown')
+      expect(actual).to have_selector('h3 a#user-content-autolinkfilter')
     end
   end
 
diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb
index 52d80c698355e26c40b823bc0d1eb558b6ef079a..4a8158ed79bde74cb9f26d20e65b35f86aa46126 100644
--- a/spec/support/rake_helpers.rb
+++ b/spec/support/rake_helpers.rb
@@ -1,7 +1,7 @@
 module RakeHelpers
-  def run_rake_task(task_name)
+  def run_rake_task(task_name, *args)
     Rake::Task[task_name].reenable
-    Rake.application.invoke_task task_name
+    Rake.application.invoke_task("#{task_name}[#{args.join(',')}]")
   end
 
   def stub_warn_user_is_not_gitlab
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index a4f21e953381cf58797158bd8c7734a9f91b8e31..2e7c88bfc09062650277fb9e19d18c625e3bdd4b 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -1,19 +1,18 @@
 RSpec.configure do |config|
   def builds_path
-    Rails.root.join('tmp/builds')
+    Rails.root.join('tmp/tests/builds')
   end
 
-  config.before(:each) do
-    FileUtils.mkdir_p(builds_path)
-    FileUtils.touch(File.join(builds_path, ".gitkeep"))
+  config.before(:suite) do
     Settings.gitlab_ci['builds_path'] = builds_path
   end
 
-  config.after(:suite) do
-    Dir[File.join(builds_path, '*')].each do |path|
-      next if File.basename(path) == '.gitkeep'
+  config.before(:all) do
+    FileUtils.mkdir_p(builds_path)
+  end
 
-      FileUtils.rm_rf(path)
-    end
+  config.before(:each) do
+    FileUtils.rm_rf(builds_path)
+    FileUtils.mkdir_p(builds_path)
   end
 end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 287d83344db454f2f9c32ea776cc3afbd8832cd7..a9fea5f1e81943377d29e29dc434a5a155a25cff 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -5,7 +5,7 @@ describe 'gitlab:app namespace rake task' do
   let(:enable_registry) { true }
 
   before :all do
-    Rake.application.rake_require 'tasks/gitlab/task_helpers'
+    Rake.application.rake_require 'tasks/gitlab/helpers'
     Rake.application.rake_require 'tasks/gitlab/backup'
     Rake.application.rake_require 'tasks/gitlab/shell'
     Rake.application.rake_require 'tasks/gitlab/db'
@@ -333,4 +333,35 @@ describe 'gitlab:app namespace rake task' do
       expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
     end
   end
+
+  describe "Human Readable Backup Name" do
+    def tars_glob
+      Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
+    end
+
+    before :all do
+      @origin_cd = Dir.pwd
+
+      reenable_backup_sub_tasks
+
+      FileUtils.rm tars_glob
+
+      # Redirect STDOUT and run the rake task
+      orig_stdout = $stdout
+      $stdout = StringIO.new
+      run_rake_task('gitlab:backup:create')
+      $stdout = orig_stdout
+
+      @backup_tar = tars_glob.first
+    end
+
+    after :all do
+      FileUtils.rm(@backup_tar)
+      Dir.chdir @origin_cd
+    end
+
+    it 'name has human readable time' do
+      expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_gitlab_backup.tar$/)
+    end
+  end
 end # gitlab:app namespace
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
index 37feb5e6faf735751ff2c5cc00a4cdecf67c0528..80fc8c48fedcad1d0454e11cbb291da7913118d3 100644
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -3,7 +3,7 @@ require 'rake'
 
 describe 'gitlab:mail_google_schema_whitelisting rake task' do
   before :all do
-    Rake.application.rake_require "tasks/gitlab/task_helpers"
+    Rake.application.rake_require "tasks/gitlab/helpers"
     Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
     # empty task as env is already loaded
     Rake::Task.define_task :environment
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86e42d845cea48de85c8c290d8be9e0b6335c420
--- /dev/null
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+require 'tasks/gitlab/task_helpers'
+
+class TestHelpersTest
+  include Gitlab::TaskHelpers
+end
+
+describe Gitlab::TaskHelpers do
+  subject { TestHelpersTest.new }
+
+  let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
+  let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s }
+  let(:tag) { 'v1.1.0' }
+
+  describe '#checkout_or_clone_tag' do
+    before do
+      allow(subject).to receive(:run_command!)
+      expect(subject).to receive(:reset_to_tag).with(tag, clone_path)
+    end
+
+    context 'target_dir does not exist' do
+      it 'clones the repo, retrieve the tag from origin, and checkout the tag' do
+        expect(subject).to receive(:clone_repo).with(repo, clone_path)
+
+        subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+      end
+    end
+
+    context 'target_dir exists' do
+      before do
+        expect(Dir).to receive(:exist?).and_return(true)
+      end
+
+      it 'fetch and checkout the tag' do
+        expect(subject).to receive(:checkout_tag).with(tag, clone_path)
+
+        subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+      end
+    end
+  end
+
+  describe '#clone_repo' do
+    it 'clones the repo in the target dir' do
+      expect(subject).
+        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}])
+
+      subject.clone_repo(repo, clone_path)
+    end
+  end
+
+  describe '#checkout_tag' do
+    it 'clones the repo in the target dir' do
+      expect(subject).
+        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet])
+      expect(subject).
+        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
+
+      subject.checkout_tag(tag, clone_path)
+    end
+  end
+
+  describe '#reset_to_tag' do
+    let(:tag) { 'v1.1.0' }
+    before do
+      expect(subject).
+        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
+    end
+
+    context 'when the tag is not checked out locally' do
+      before do
+        expect(subject).
+          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError)
+      end
+
+      it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do
+        expect(subject).
+          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin])
+        expect(subject).
+          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag)
+
+        subject.reset_to_tag(tag, clone_path)
+      end
+    end
+
+    context 'when the tag is checked out locally' do
+      before do
+        expect(subject).
+          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag)
+      end
+
+      it 'resets --hard to the given tag' do
+        subject.reset_to_tag(tag, clone_path)
+      end
+    end
+  end
+end
diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb
index e6ebef82b78d902571246e8600f1d18979a36cbe..972670e7f913a6580c290b0be57e2fe6fb529756 100644
--- a/spec/tasks/gitlab/users_rake_spec.rb
+++ b/spec/tasks/gitlab/users_rake_spec.rb
@@ -5,7 +5,7 @@ describe 'gitlab:users namespace rake task' do
   let(:enable_registry) { true }
 
   before :all do
-    Rake.application.rake_require 'tasks/gitlab/task_helpers'
+    Rake.application.rake_require 'tasks/gitlab/helpers'
     Rake.application.rake_require 'tasks/gitlab/users'
 
     # empty task as env is already loaded
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6de66c3cf07af2e74a211ab233053ae77eff1441
--- /dev/null
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -0,0 +1,81 @@
+require 'rake_helper'
+
+describe 'gitlab:workhorse namespace rake task' do
+  before :all do
+    Rake.application.rake_require 'tasks/gitlab/workhorse'
+  end
+
+  describe 'install' do
+    let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
+    let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
+    let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" }
+    before do
+      allow(ENV).to receive(:[])
+    end
+
+    context 'no dir given' do
+      it 'aborts and display a help message' do
+        # avoid writing task output to spec progress
+        allow($stderr).to receive :write
+        expect { run_rake_task('gitlab:workhorse:install') }.to raise_error /Please specify the directory where you want to install gitlab-workhorse/
+      end
+    end
+
+    context 'when an underlying Git command fail' do
+      it 'aborts and display a help message' do
+        expect_any_instance_of(Object).
+          to receive(:checkout_or_clone_tag).and_raise 'Git error'
+
+        expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
+      end
+    end
+
+    describe 'checkout or clone' do
+      before do
+        expect(Dir).to receive(:chdir).with(clone_path)
+      end
+
+      it 'calls checkout_or_clone_tag with the right arguments' do
+        expect_any_instance_of(Object).
+          to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path)
+
+        run_rake_task('gitlab:workhorse:install', clone_path)
+      end
+    end
+
+    describe 'gmake/make' do
+      before do
+        FileUtils.mkdir_p(clone_path)
+        expect(Dir).to receive(:chdir).with(clone_path).and_call_original
+      end
+
+      context 'gmake is available' do
+        before do
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+        end
+
+        it 'calls gmake in the gitlab-workhorse directory' do
+          expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
+          expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+
+          run_rake_task('gitlab:workhorse:install', clone_path)
+        end
+      end
+
+      context 'gmake is not available' do
+        before do
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
+        end
+
+        it 'calls make in the gitlab-workhorse directory' do
+          expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
+          expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
+
+          run_rake_task('gitlab:workhorse:install', clone_path)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 3fddfb3b62f491b46f82fa30e03ca1809fe9bb80..8020faa1f9cfc91ff07c4639ecf1f1882e988231 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -1,10 +1,6 @@
 require 'spec_helper'
 
 describe 'layouts/_head' do
-  before do
-    stub_template 'layouts/_user_styles.html.haml' => ''
-  end
-
   it 'escapes HTML-safe strings in page_title' do
     stub_helper_with_safe_string(:page_title)
 
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index e0c77201116a55a2dc9c0906bef836663b10adca..745d0c745bd76e6ec41ee3838a0f715c965facc0 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -88,16 +88,46 @@ describe 'projects/builds/show', :view do
         create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
       end
 
-      let!(:environment) do
-        create(:environment, name: 'staging', project: project)
-      end
-
-      it 'shows deployment message' do
-        expected_text = 'This build is creating a deployment to staging'
-        render
-
-        expect(rendered).to have_css(
-          '.environment-information', text: expected_text)
+      context 'when environment exists' do
+        let!(:environment) do
+          create(:environment, name: 'staging', project: project)
+        end
+
+        it 'shows deployment message' do
+          expected_text = 'This build is creating a deployment to staging'
+          render
+
+          expect(rendered).to have_css(
+            '.environment-information', text: expected_text)
+        end
+
+        context 'when it has deployment' do
+          let!(:deployment) do
+            create(:deployment, environment: environment)
+          end
+
+          it 'shows that deployment will be overwritten' do
+            expected_text = 'This build is creating a deployment to staging'
+            render
+
+            expect(rendered).to have_css(
+              '.environment-information', text: expected_text)
+            expect(rendered).to have_css(
+              '.environment-information', text: 'latest deployment')
+          end
+        end
+      end
+
+      context 'when environment does not exist' do
+        it 'shows deployment message' do
+          expected_text = 'This build is creating a deployment to staging'
+          render
+
+          expect(rendered).to have_css(
+            '.environment-information', text: expected_text)
+          expect(rendered).not_to have_css(
+            '.environment-information', text: 'latest deployment')
+        end
       end
     end
 
@@ -134,6 +164,8 @@ describe 'projects/builds/show', :view do
 
         expect(rendered).to have_css(
           '.environment-information', text: expected_text)
+        expect(rendered).not_to have_css(
+          '.environment-information', text: 'latest deployment')
       end
     end
   end
diff --git a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f698a34ab53b83dc40c794517d372c203d0f4de
--- /dev/null
+++ b/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/_new_submit.html.haml', :view do
+  let(:merge_request) { create(:merge_request) }
+  let!(:pipeline) { create(:ci_empty_pipeline) }
+
+  before do
+    controller.prepend_view_path('app/views/projects')
+
+    assign(:merge_request, merge_request)
+    assign(:commits, merge_request.commits)
+    assign(:project, merge_request.target_project)
+
+    allow(view).to receive(:can?).and_return(true)
+    allow(view).to receive(:url_for).and_return('#')
+    allow(view).to receive(:current_user).and_return(merge_request.author)
+  end
+
+  context 'when there are pipelines for merge request but no pipeline for last commit' do
+    before do
+      assign(:pipelines, Ci::Pipeline.all)
+      assign(:pipeline, nil)
+    end
+
+    it 'shows <<Pipelines>> tab and hides <<Builds>> tab' do
+      render
+      expect(rendered).to have_text('Pipelines 1')
+      expect(rendered).not_to have_text('Builds')
+    end
+  end
+end
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index 18a1aab766cbf652641b308ae6e1e6edb8ac14c0..95e2458da35f28dceeee2db64a93ddcb2afe8f98 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -1,22 +1,33 @@
 require 'spec_helper'
 
 describe AuthorizedProjectsWorker do
+  let(:worker) { described_class.new }
+
   describe '#perform' do
     it "refreshes user's authorized projects" do
       user = create(:user)
 
-      expect(User).to receive(:find_by).with(id: user.id).and_return(user)
-      expect(user).to receive(:refresh_authorized_projects)
+      expect(worker).to receive(:refresh).with(an_instance_of(User))
 
-      described_class.new.perform(user.id)
+      worker.perform(user.id)
     end
 
-    context "when user is not found" do
+    context "when the user is not found" do
       it "does nothing" do
-        expect_any_instance_of(User).not_to receive(:refresh_authorized_projects)
+        expect(worker).not_to receive(:refresh)
 
-        described_class.new.perform(999_999)
+        described_class.new.perform(-1)
       end
     end
   end
+
+  describe '#refresh', redis: true do
+    it 'refreshes the authorized projects of the user' do
+      user = create(:user)
+
+      expect(user).to receive(:refresh_authorized_projects)
+
+      worker.refresh(user)
+    end
+  end
 end
diff --git a/spec/workers/build_email_worker_spec.rb b/spec/workers/build_email_worker_spec.rb
index a1aa336361a3166fbb9424146b185086e897d757..542e674c15015e9b9c6c51863022a6aac5955544 100644
--- a/spec/workers/build_email_worker_spec.rb
+++ b/spec/workers/build_email_worker_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 describe BuildEmailWorker do
+  include EmailHelpers
   include RepoHelpers
 
   let(:build) { create(:ci_build) }
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index fc652f6f4c36cb4180eb6ce69e4e837bbb24fc30..f27e413f7b8ac1aed1ca8b6618a3f8d12d42e921 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
 
 describe EmailsOnPushWorker do
   include RepoHelpers
+  include EmailHelpers
   include EmailSpec::Matchers
 
   let(:project) { create(:project) }
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index d487a7196800fc7298fb547fd03f6bd4a42aeb43..739f9b639670283788e2ffaad2de069300827e06 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe PipelineNotificationWorker do
+  include EmailHelpers
+
   let(:pipeline) do
     create(:ci_pipeline,
            project: project,
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 3e4fee422409e3eea2cf079b409141db77189ca5..75c7fc1efd2a0b2b8adf693f49e6ae87169c3cf5 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -11,31 +11,25 @@ describe ProcessCommitWorker do
     it 'does not process the commit when the project does not exist' do
       expect(worker).not_to receive(:close_issues)
 
-      worker.perform(-1, user.id, commit.id)
+      worker.perform(-1, user.id, commit.to_hash)
     end
 
     it 'does not process the commit when the user does not exist' do
       expect(worker).not_to receive(:close_issues)
 
-      worker.perform(project.id, -1, commit.id)
-    end
-
-    it 'does not process the commit when the commit no longer exists' do
-      expect(worker).not_to receive(:close_issues)
-
-      worker.perform(project.id, user.id, 'this-should-does-not-exist')
+      worker.perform(project.id, -1, commit.to_hash)
     end
 
     it 'processes the commit message' do
       expect(worker).to receive(:process_commit_message).and_call_original
 
-      worker.perform(project.id, user.id, commit.id)
+      worker.perform(project.id, user.id, commit.to_hash)
     end
 
     it 'updates the issue metrics' do
       expect(worker).to receive(:update_issue_metrics).and_call_original
 
-      worker.perform(project.id, user.id, commit.id)
+      worker.perform(project.id, user.id, commit.to_hash)
     end
   end
 
@@ -106,4 +100,19 @@ describe ProcessCommitWorker do
       expect(metric.first_mentioned_in_commit_at).to eq(commit.committed_date)
     end
   end
+
+  describe '#build_commit' do
+    it 'returns a Commit' do
+      commit = worker.build_commit(project, id: '123')
+
+      expect(commit).to be_an_instance_of(Commit)
+    end
+
+    it 'parses date strings into Time instances' do
+      commit = worker.
+        build_commit(project, id: '123', authored_date: Time.now.to_s)
+
+      expect(commit.authored_date).to be_an_instance_of(Time)
+    end
+  end
 end