diff --git a/.eslintrc b/.eslintrc
index e13f76b213c4a0bba99fcca18595488cf26fb9d6..9ab0145820d7e52c0e4e0118e386d66e93f5dcf9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -15,6 +15,7 @@
     "filenames"
   ],
   "rules": {
-    "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
+    "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
+    "no-multiple-empty-lines": ["error", { "max": 1 }]
   }
 }
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 68690ff33daea76c4d2d61d45975fad76b2d5f29..deb5345d3bd2e562f8ea1bd486a98832ad9009e5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,7 +35,6 @@ stages:
 .dedicated-runner: &dedicated-runner
   tags:
     - gitlab-org
-    - 2gb
 
 .knapsack-state: &knapsack-state
   services: []
@@ -108,7 +107,7 @@ setup-test-env:
   <<: *dedicated-runner
   stage: prepare
   script:
-    - bundle exec rake assets:precompile 2>/dev/null
+    - bundle exec rake gitlab:assets:compile 2>/dev/null
     - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
   artifacts:
     expire_in: 7d
@@ -272,7 +271,7 @@ rake db:migrate:reset:
   <<: *use-db
   <<: *dedicated-runner
   script:
-    - rake db:migrate:reset
+    - bundle exec rake db:migrate:reset
 
 rake db:seed_fu:
   stage: test
@@ -303,7 +302,7 @@ teaspoon:
   script:
     - npm install
     - npm link istanbul
-    - rake teaspoon
+    - bundle exec rake teaspoon
   artifacts:
     name: coverage-javascript
     expire_in: 31d
@@ -354,10 +353,10 @@ migration paths:
     - cp config/resque.yml.example config/resque.yml
     - sed -i 's/localhost/redis/g' config/resque.yml
     - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
-    - rake db:drop db:create db:schema:load db:seed_fu
+    - bundle exec rake db:drop db:create db:schema:load db:seed_fu
     - git checkout $CI_BUILD_REF
     - source scripts/prepare_build.sh
-    - rake db:migrate
+    - bundle exec rake db:migrate
 
 coverage:
   stage: post-test
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 7c8a9c4fd1784ac737cd5773944a87b4909496f9..528f99d08d2ed154a6e6a072411b0069e5bd5342 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -46,7 +46,7 @@ linters:
     max: 80
 
   MultilinePipe:
-    enabled: false
+    enabled: true
 
   MultilineScript:
     enabled: true
@@ -77,7 +77,7 @@ linters:
       - Style/WhileUntilModifier
 
   RubyComments:
-    enabled: false
+    enabled: true
 
   SpaceBeforeScript:
     enabled: true
@@ -97,7 +97,7 @@ linters:
     enabled: true
 
   UnnecessaryInterpolation:
-    enabled: false
+    enabled: true
 
   UnnecessaryStringOutput:
-    enabled: false
+    enabled: true
diff --git a/.rubocop.yml b/.rubocop.yml
index bf2b2d8afc2a2b5c6faac0cfbb4a87ac257524de..cfff42e5c99f0eaad562cfc6c7ce20c9ad087c56 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -17,6 +17,7 @@ AllCops:
   # Exclude some GitLab files
   Exclude:
     - 'vendor/**/*'
+    - 'node_modules/**/*'
     - 'db/*'
     - 'db/fixtures/**/*'
     - 'tmp/**/*'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cabfef84b24da9fb378d2c02f03bce384d6f470e..25e02b1ae1cbb2e264f6d507c5d53ddd5fda12fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,163 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 8.16.3 (2017-01-27)
+
+- Add caching of droplab ajax requests. !8725
+- Fix access to the wiki code via HTTP when repository feature disabled. !8758
+- Revert 3f17f29a. !8785
+- Fix race conditions for AuthorizedProjectsWorker.
+- Fix autocomplete initial undefined state.
+- Fix Error 500 when repositories contain annotated tags pointing to blobs.
+- Fix /explore sorting.
+- Fixed label dropdown toggle text not correctly updating.
+
+## 8.16.2 (2017-01-25)
+
+- allow issue filter bar to be operated with mouse only. !8681
+- Fix CI requests concurrency for newer runners that prevents from picking pending builds (from 1.9.0-rc5). !8760
+- Add some basic fixes for IE11/Edge.
+- Remove blue border from comment box hover.
+- Fixed bug where links in merge dropdown wouldn't work.
+
+## 8.16.1 (2017-01-23)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
+## 8.16.0 (2017-01-22)
+
+- Add LDAP Rake task to rename a provider. !2181
+- Validate label's title length. !5767 (Tomáš Kukrál)
+- Allow to add deploy keys with write-access. !5807 (Ali Ibrahim)
+- Allow to use + symbol in filenames. !6644 (blackst0ne)
+- Search bar redesign first iteration. !7345
+- Fix date inconsistency on due date picker. !7422 (Giuliano Varriale)
+- Add email confirmation field to registration form. !7432
+- Updated project visibility settings UX. !7645
+- Go to a project order. !7737 (Jacopo Beschi @jacopo-beschi)
+- Support slash comand `/merge` for merging merge requests. !7746 (Jarka Kadlecova)
+- Add more storage statistics. !7754 (Markus Koller)
+- Add support for PlantUML diagrams in AsciiDoc documents. !7810 (Horacio Sanson)
+- Remove extra orphaned rows when removing stray namespaces. !7841
+- Added lighter count badge background-color for on white backgrounds. !7873
+- Fixes issue boards list colored top border visual glitch. !7898 (Pier Paolo Ramon)
+- change 'gray' color theme name to 'black' to match the actual color. !7908 (BM5k)
+- Remove trailing whitespace when generating changelog entry. !7948
+- Remove checking branches state in issue new branch button. !8023
+- Log LDAP blocking/unblocking events to application log. !8042 (Markus Koller)
+- ensure permalinks scroll to correct position on multiple clicks. !8046
+- Allow to use ENV variables in redis config. !8073 (Semyon Pupkov)
+- fix button layout issue on branches page. !8074
+- Reduce DB-load for build-queues by storing last_update in Redis. !8084
+- Record and show last used date of SSH Keys. !8113 (Vincent Wong)
+- Resolves overflow in compare branch and tags dropdown. !8118
+- Replace wording for slash command confirmation message. !8123
+- remove build_user. !8162 (Arsenev Vladislav)
+- Prevent empty pagination when list is not empty. !8172
+- Make successful pipeline emails off for watchers. !8176
+- Improve copy in Issue Tracker empty state. !8202
+- Adds CSS class to status icon on MR widget to prevent non-colored icon. !8219
+- Improve visibility of "Resolve conflicts" and "Merge locally" actions. !8229
+- Add Gitaly to the architecture documentation. !8264 (Pablo Carranza <pablo@gitlab.com>)
+- Sort numbers in build names more intelligently. !8277
+- Show nested groups tab on group page. !8308
+- Rename users with namespace ending with .git. !8309
+- Rename filename to file path in tooltip of file header in merge request diff. !8314
+- About GitLab link in sidebar that links to help page. !8316
+- Merged the 'Groups' and 'Projects' tabs when viewing user profiles. !8323 (James Gregory)
+- re-enable change username button after failure. !8332
+- Darkened hr border color in descriptions because of update of bootstrap. !8333
+- display merge request discussion tab for empty branches. !8347
+- Fix double spaced CI log. !8349 (Jared Deckard <jared.deckard@gmail.com>)
+- Refactored note edit form to improve frontend performance on MR and Issues pages, especially pages with has a lot of discussions in it. !8356
+- Make CTRL+Enter submits a new merge request. !8360 (Saad Shahd)
+- Fixes too short input for placeholder message in commit listing page. !8367
+- Fix typo: seach to search. !8370
+- Adds label to Environments "Date Created". !8376 (Saad Shahd)
+- Convert project setting text into protected branch path link. !8377 (Ken Ding)
+- Precompile all JavaScript fixtures. !8384
+- Use original casing for build action text. !8387
+- Scroll to bottom on build completion if autoscroll was active. !8391
+- Properly handle failed reCAPTCHA on user registration. !8403
+- Changed alerts to be responsive, centered text on smaller viewports. !8424 (Connor Smallman)
+- Pass Gitaly resource path to gitlab-workhorse if Gitaly is enabled. !8440
+- Fixes and Improves CSS and HTML problems in mini pipeline graph and builds dropdown. !8443
+- Don't instrument 405 Grape calls. !8445
+- Change CI template linter textarea with Ace Editor. !8452 (Didem Acet)
+- Removes unneeded `window` declaration in environments related code. !8456
+- API: fix query response for `/projects/:id/issues?milestone="No%20Milestone"`. !8457 (Panagiotis Atmatzidis, David Eisner)
+- Fix broken url on group avatar. !8464 (hogewest)
+- Fixes buttons not being accessible via the keyboard when creating new group. !8469
+- Restore backup correctly when "BACKUP" environment variable is passed. !8477
+- Add new endpoints for Time Tracking. !8483
+- Fix Compare page throws 500 error when any branch/reference is not selected. !8492 (Martin Cabrera)
+- Treat environments matching `production/*` as Production. !8500
+- Hide build artifacts keep button if operation is not allowed. !8501
+- Update the gitlab-markup gem to the version 1.5.1. !8509
+- Remove Lock Icon on Protected Tag. !8513 (Sergey Nikitin)
+- Use cached values to compute total issues count in milestone index pages. !8518
+- Speed up dashboard milestone index by scoping IssuesFinder to user authorized projects. !8524
+- Copy <some text> to clipboard. !8535
+- Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route. !8544
+- Fixes builds dropdown making request when clicked to be closed. !8545
+- Fixes pipeline status cell is too wide by adding missing classes in table head cells. !8549
+- Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility` concern. !8552
+- Fix links to commits pages on pipelines list page. !8558
+- Ensure updating project settings shows a flash message on success. !8579 (Sandish Chen)
+- Fixes big pipeline and small pipeline width problems and tooltips text being outside the tooltip. !8593
+- Autoresize markdown preview. !8607 (Didem Acet)
+- Link external build badge to its target URL. !8611
+- Adjust ProjectStatistic#repository_size with values saved as MB. !8616
+- Correct User-agent placement in robots.txt. !8623 (Eric Sabelhaus)
+- Record used SSH keys only once per day. !8655
+- Do not generate pipeline branch/tag path if not present. !8658
+- Fix Merge When Pipeline Succeeds immediate merge bug. !8685
+- Fix blame 500 error on invalid path. !25761 (Jeff Stubler)
+- Added animations to issue boards interactions.
+- Check if user can read project before being assigned to issue.
+- Show 'too many changes' message for created merge requests when they are too large.
+- Fix redirect after update file when user has forked project.
+- Parse JIRA issue references even if Issue Tracker is disabled.
+- Made download artifacts button accessible via keyboard by changing it from an anchor tag to an actual button. (Ryan Harris)
+- Make play button on Pipelines page accessible via keyboard. (Ryan Harris)
+- Decreases font-size on login page.
+- Fixed merge request tabs dont move when opening collapsed sidebar.
+- Display project avatars on Admin Area and Projects pages for mobile views. (Ryan Harris)
+- Fix participants margins to fit on one line.
+- 26352 Change Profile settings to User / Settings.
+- Fix Commits API to accept a Project path upon POST.
+- Expire related caches after changing HEAD. (Minqi Pan)
+- Add various hover animations throughout the application.
+- Re-order update steps in the 8.14 -> 8.15 upgrade guide.
+- Move award emoji's out of the discussion tab for merge requests.
+- Synchronize all project authorization refreshing work to prevent race conditions.
+- Remove the project_authorizations.id column.
+- Combined the settings options project members and groups into a single one called members.
+- Change earlier to task_status_short to avoid titlebar line wraps.
+- 25701 standardize text colors.
+- Handle HTTP errors in environment list.
+- Re-add Google Cloud Storage as a backup strategy.
+- Change status colors of runners to better defaults.
+- Added number_with_delimiter to counter on milestone panels. (Ryan Harris)
+- Query external CI statuses in the background.
+- Allow group and project paths when transferring projects via the API.
+- Don't validate environment urls on .gitlab-ci.yml.
+- Fix a Grape deprecation, use `#request_method` instead of `#route_method`.
+- Fill missing authorized projects rows.
+- Allow API query to find projects with dots in their name. (Bruno Melli)
+- Fix import/export wrong user mapping.
+- Removed bottom padding from merge manually from CLI because of repositioning award emoji's.
+- Fix project queued for deletion re-creation tooltip.
+- Fix search group/project filtering to show results.
+- Fix 500 error when POSTing to Users API with optional confirm param.
+- 26504 Fix styling of MR jump to discussion button.
+- Add margin to markdown math blocks.
+- Add hover state to MR comment reply button.
+
 ## 8.15.4 (2017-01-09)
 
 - Make successful pipeline emails off for watchers. !8176
@@ -265,6 +422,14 @@ entry.
 - Whitelist next project names: help, ci, admin, search. !8227
 - Adds back CSS for progress-bars. !8237
 
+## 8.14.8 (2017-01-25)
+
+- Accept environment variables from the `pre-receive` script. !7967
+- Milestoneish SQL performance partially improved and memoized. !8146
+- Fix N+1 queries on milestone show pages. !8185
+- Speed up group milestone index by passing group_id to IssuesFinder. !8363
+- Ensure issuable state changes only fire webhooks once.
+
 ## 8.14.6 (2017-01-10)
 
 - Update the gitlab-markup gem to the version 1.5.1. !8509
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b68c4a67826209c015d4a526f604120ac55f53aa..d404f1b91df1878eda834eafaea8791c04898cf1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,11 +80,9 @@ the remaining issues on the GitHub issue tracker.
 ## I want to contribute!
 
 If you want to contribute to GitLab, but are not sure where to start,
-look for [issues with the label `up-for-grabs`][up-for-grabs]. These issues
-will be of reasonable size and challenge, for anyone to start contributing to
-GitLab.
-
-This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
+look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight].
+These issues will be of reasonable size and challenge, for anyone to start
+contributing to GitLab.
 
 ## Implement design & UI elements
 
@@ -214,16 +212,19 @@ associated with in the description of the issue.
 ## Merge requests
 
 We welcome merge requests with fixes and improvements to GitLab code, tests,
-and/or documentation. The features we would really like a merge request for are
-listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
-and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note
-that if an issue is marked for the current milestone either before or while you
-are working on it, a team member may take over the merge request in order to
-ensure the work is finished before the release date.
+and/or documentation. The issues that are specifically suitable for
+community contributions are listed with the label
+[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
+and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
+you want.
+
+Please note that if an issue is marked for the current milestone either before
+or while you are working on it, a team member may take over the merge request
+in order to ensure the work is finished before the release date.
 
 If you want to add a new feature that is not labeled it is best to first create
 a feedback issue (if there isn't one already) and leave a comment asking for it
-to be marked as `Accepting merge requests`. Please include screenshots or
+to be marked as `Accepting Merge Requests`. Please include screenshots or
 wireframes if the feature will also change the UI.
 
 Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
@@ -285,14 +286,6 @@ request is as follows:
 1. For tests that use Capybara or PhantomJS, see this [article on how
    to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
 
-The **official merge window** is in the beginning of the month from the 1st to
-the 7th day of the month. This is the best time to submit an MR and get
-feedback fast. Before this time the GitLab Inc. team is still dealing with work
-that is created by the monthly release such as regressions requiring patch
-releases. After the 7th it is already getting closer to the release date of the
-next version. This means there is less time to fix the issues created by
-merging large new features.
-
 Please keep the change in a single MR **as small as possible**. If you want to
 contribute a large feature think very hard what the minimum viable change is.
 Can you split the functionality? Can you only submit the backend/API code? Can
@@ -450,8 +443,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
 [core team]: https://about.gitlab.com/core-team/
 [getting-help]: https://about.gitlab.com/getting-help/
 [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
-[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
+[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
 [ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
 [ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
 [google-group]: https://groups.google.com/forum/#!forum/gitlabhq
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 6085e946503a10fb4d58a5c7d9a6e572c21ddd2e..f0bb29e76388856b273698ae6064b0380ce5e5d2 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-1.2.1
+1.3.0
diff --git a/Gemfile b/Gemfile
index 83ba5d31b92fc2b8147b85d672082ee497797f95..479df411881301ae6aa2afdcd4bf885057925704 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0'
 # Authentication libraries
 gem 'devise',                 '~> 4.2'
 gem 'doorkeeper',             '~> 4.2.0'
-gem 'omniauth',               '~> 1.3.1'
+gem 'omniauth',               '~> 1.3.2'
 gem 'omniauth-auth0',         '~> 1.4.1'
 gem 'omniauth-azure-oauth2',  '~> 0.0.6'
 gem 'omniauth-cas3',          '~> 1.1.2'
@@ -36,7 +36,7 @@ gem 'omniauth-twitter',       '~> 1.2.0'
 gem 'omniauth_crowd',         '~> 2.2.0'
 gem 'omniauth-authentiq',     '~> 0.2.0'
 gem 'rack-oauth2',            '~> 1.2.1'
-gem 'jwt'
+gem 'jwt',                    '~> 1.5.6'
 
 # Spam and anti-bot protection
 gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
@@ -109,7 +109,7 @@ gem 'org-ruby',             '~> 0.9.12'
 gem 'creole',               '~> 0.5.0'
 gem 'wikicloth',            '0.8.1'
 gem 'asciidoctor',          '~> 1.5.2'
-gem 'asciidoctor-plantuml', '0.0.6'
+gem 'asciidoctor-plantuml', '0.0.7'
 gem 'rouge',                '~> 2.0'
 gem 'truncato',             '~> 0.7.8'
 
@@ -280,6 +280,7 @@ group :development, :test do
   gem 'rspec-retry',        '~> 0.4.5'
   gem 'spinach-rails',      '~> 0.2.1'
   gem 'spinach-rerun-reporter', '~> 0.0.2'
+  gem 'rspec_profiling'
 
   # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
   gem 'minitest', '~> 5.7.0'
@@ -322,7 +323,7 @@ group :test do
   gem 'email_spec', '~> 1.6.0'
   gem 'json-schema', '~> 2.6.2'
   gem 'webmock', '~> 1.21.0'
-  gem 'test_after_commit', '~> 0.4.2'
+  gem 'test_after_commit', '~> 1.1'
   gem 'sham_rack', '~> 1.3.6'
   gem 'timecop', '~> 0.8.0'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index 104e6444803e1386b2c82d1f0a8239183a83856c..f6b889dcca482366d7abd02e3fba71d364009e22 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -54,7 +54,7 @@ GEM
       faraday_middleware-multi_json (~> 0.0)
       oauth2 (~> 1.0)
     asciidoctor (1.5.3)
-    asciidoctor-plantuml (0.0.6)
+    asciidoctor-plantuml (0.0.7)
       asciidoctor (~> 1.5)
     ast (2.3.0)
     attr_encrypted (3.0.3)
@@ -379,7 +379,7 @@ GEM
     json (1.8.3)
     json-schema (2.6.2)
       addressable (~> 2.3.8)
-    jwt (1.5.4)
+    jwt (1.5.6)
     kaminari (0.17.0)
       actionpack (>= 3.0.0)
       activesupport (>= 3.0.0)
@@ -449,7 +449,7 @@ GEM
     octokit (4.6.2)
       sawyer (~> 0.8.0, >= 0.5.3)
     oj (2.17.4)
-    omniauth (1.3.1)
+    omniauth (1.3.2)
       hashie (>= 1.2, < 4)
       rack (>= 1.0, < 3)
     omniauth-auth0 (1.4.1)
@@ -642,6 +642,11 @@ GEM
     rspec-retry (0.4.5)
       rspec-core
     rspec-support (3.5.0)
+    rspec_profiling (0.0.4)
+      activerecord
+      pg
+      rails
+      sqlite3
     rubocop (0.46.0)
       parser (>= 2.3.1.1, < 3.0)
       powerpack (~> 0.1)
@@ -743,6 +748,7 @@ GEM
       actionpack (>= 4.0)
       activesupport (>= 4.0)
       sprockets (>= 3.0.0)
+    sqlite3 (1.3.11)
     stackprof (0.2.10)
     state_machines (0.4.0)
     state_machines-activemodel (0.4.0)
@@ -760,7 +766,7 @@ GEM
     teaspoon-jasmine (2.2.0)
       teaspoon (>= 1.0.0)
     temple (0.7.7)
-    test_after_commit (0.4.2)
+    test_after_commit (1.1.0)
       activerecord (>= 3.2)
     thin (1.7.0)
       daemons (~> 1.0, >= 1.0.9)
@@ -835,7 +841,7 @@ DEPENDENCIES
   allocations (~> 1.0)
   asana (~> 0.4.0)
   asciidoctor (~> 1.5.2)
-  asciidoctor-plantuml (= 0.0.6)
+  asciidoctor-plantuml (= 0.0.7)
   attr_encrypted (~> 3.0.0)
   awesome_print (~> 1.2.0)
   babosa (~> 1.0.2)
@@ -906,7 +912,7 @@ DEPENDENCIES
   jquery-rails (~> 4.1.0)
   jquery-ui-rails (~> 5.0.0)
   json-schema (~> 2.6.2)
-  jwt
+  jwt (~> 1.5.6)
   kaminari (~> 0.17.0)
   knapsack (~> 1.11.0)
   kubeclient (~> 2.2.0)
@@ -925,7 +931,7 @@ DEPENDENCIES
   oauth2 (~> 1.2.0)
   octokit (~> 4.6.2)
   oj (~> 2.17.4)
-  omniauth (~> 1.3.1)
+  omniauth (~> 1.3.2)
   omniauth-auth0 (~> 1.4.1)
   omniauth-authentiq (~> 0.2.0)
   omniauth-azure-oauth2 (~> 0.0.6)
@@ -965,6 +971,7 @@ DEPENDENCIES
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.5.0)
   rspec-retry (~> 0.4.5)
+  rspec_profiling
   rubocop (~> 0.46.0)
   rubocop-rspec (~> 1.9.1)
   ruby-fogbugz (~> 0.2.1)
@@ -997,7 +1004,7 @@ DEPENDENCIES
   sys-filesystem (~> 1.1.6)
   teaspoon (~> 1.1.0)
   teaspoon-jasmine (~> 2.2.0)
-  test_after_commit (~> 0.4.2)
+  test_after_commit (~> 1.1)
   thin (~> 1.7.0)
   timecop (~> 0.8.0)
   truncato (~> 0.7.8)
@@ -1015,4 +1022,4 @@ DEPENDENCIES
   wikicloth (= 0.8.1)
 
 BUNDLED WITH
-   1.13.7
+   1.14.2
diff --git a/PROCESS.md b/PROCESS.md
index cbeb781cd3ce31552e0e24935f58a487f00b3ba9..993d60bbba8dc9231f199741933f1d928560da98 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -12,106 +12,54 @@ etc.).
 
 ## Common actions
 
-### Issue team
-
-- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
-  issue
-- Closes invalid issues with a comment (duplicates,
-  [fixed in newer version](#issue-fixed-in-newer-version),
-  [issue report for old version](#issue-report-for-old-version), not a problem
-  in GitLab, etc.)
-- Asks for feedback from issue reporter
-  ([invalid issue reports](#improperly-formatted-issue),
-  [format code](#code-format), etc.)
-- Monitors all issues for feedback (but especially ones commented on since
-  automatically watching them)
-- Closes issues with no feedback from the reporter for two weeks
-
-### Merge marshall & merge request coach
-
-- Responds to merge requests the issue team mentions them in and monitors for
-  new merge requests
-- Provides feedback to the merge request submitter to improve the merge request
-  (style, tests, etc.)
-- Mark merge requests `Ready for Merge` when they meet the
-  [contribution acceptance criteria]
-- Mention developer(s) based on the
-  [list of members and their specialities][team]
-- Closes merge requests with no feedback from the reporter for two weeks
-
-## Priorities of the issue team
-
-1. Mentioning people (critical)
-1. Workflow labels (normal)
-1. Functional labels (minor)
-1. Assigning issues (avoid if possible)
-
-## Mentioning people
+### Issue triaging
+
+Our issue triage policies are [described in our handbook]. You are very welcome
+to help the GitLab team triage issues. We also organize [issue bash events] once
+every quarter.
 
 The most important thing is making sure valid issues receive feedback from the
 development team. Therefore the priority is mentioning developers that can help
 on those issues. Please select someone with relevant experience from
-[GitLab core team][core-team]. If there is nobody mentioned with that expertise
+[GitLab team][team]. If there is nobody mentioned with that expertise
 look in the commit history for the affected files to find someone. Avoid
 mentioning the lead developer, this is the person that is least likely to give a
 timely response. If the involvement of the lead developer is needed the other
 core team members will mention this person.
 
-## Workflow labels
+[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
+[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
 
-Workflow labels are purposely not very detailed since that would be hard to keep
-updated as you would need to re-evaluate them after every comment. We optionally
-use functional labels on demand when we want to group related issues to get an
-overview (for example all issues related to RVM, to tackle them in one go) and
-to add details to the issue.
+### Merge request coaching
 
-- ~"Awaiting Feedback" Feedback pending from the reporter
-- ~UX needs help from a UX designer
-- ~Frontend needs help from a Front-end engineer. Please follow the
-  ["Implement design & UI elements" guidelines].
-- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
-  encourage people to contribute to. Not exclusive with other labels.
-- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
-in support or comment for further detail. Do not use `feature request`.
-- ~bug is an issue reporting undesirable or incorrect behavior.
-- ~customer is an issue reported by enterprise subscribers. This label should
-be accompanied by *bug* or *feature proposal* labels.
+Several people from the [GitLab team][team] are helping community members to get
+their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done].
 
-Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
+What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
+
+## Workflow labels
 
-## Functional labels
+Labelling issues is described in the [GitLab Inc engineering workflow].
 
-These labels describe what development specialities are involved such as: `CI`,
-`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
-`Release`, `Repository`, `UX`.
+[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
 
 ## Assigning issues
 
 If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
 
-## Label colors
-
-- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
-  feedback, awaiting confirmation of fix)
-- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
-  awaiting developer action/feedback)
-- Light blue `#82C5FF`: functional labels
-- Green labels `#009800`: issues that can generally be ignored. For example,
-  issues given the following labels normally can be closed immediately:
-  - Support (see copy & paste response:
-    [Support requests and configuration questions](#support-requests-and-configuration-questions)
-
 ## Be kind
 
 Be kind to people trying to contribute. Be aware that people may be a non-native
 English speaker, they might not understand things or they might be very
 sensitive as to how you word things. Use Emoji to express your feelings (heart,
-star, smile, etc.). Some good tips about giving feedback to merge requests is in
-the [Thoughtbot code review guide].
+star, smile, etc.). Some good tips about code reviews can be found in our
+[Code Review Guidelines].
+
+[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
 
 ## Feature Freeze
 
-5 working days before the 22nd the stable branches for the upcoming release will
+On the 7th of each month, the stable branches for the upcoming release will
 be frozen for major changes. Merge requests may still be merged into master
 during this period. By freezing the stable branches prior to a release there's
 no need to worry about last minute merge requests potentially breaking a lot of
@@ -120,10 +68,9 @@ things.
 What is considered to be a major change is determined on a case by case basis as
 this definition depends very much on the context of changes. For example, a 5
 line change might have a big impact on the entire application. Ultimately the
-decision will be made by those reviewing a merge request and the release
-manager.
+decision will be made by the maintainers and the release managers.
 
-During the feature freeze all merge requests that are meant to go into the next
+During the feature freeze all merge requests that are meant to go into the upcoming
 release should have the correct milestone assigned _and_ have the label
 ~"Pick into Stable" set. Merge requests without a milestone and this label will
 not be merged into any stable branches.
@@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker.
 Since this is an older issue I'll be closing this for now. If you think this is
 still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
 
-[core-team]: https://about.gitlab.com/core-team/
 [team]: https://about.gitlab.com/team/
 [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
 ["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
diff --git a/README.md b/README.md
index 4e28f3aacfd69a59c8de9d26944f6f487a95c574..4f85fac4a569a293994d8512787ee53554ccfb76 100644
--- a/README.md
+++ b/README.md
@@ -113,4 +113,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
 ## Is it awesome?
 
 Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlab/favorites) seem to like it.
+[These people](https://twitter.com/gitlab/likes) seem to like it.
diff --git a/VERSION b/VERSION
index 8e9258150a94805cf1980413fdb2d9aaf3dd3f55..5c99c061a476a2d06502fb459d72b82843af7851 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.16.0-pre
+8.17.0-pre
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index f0615481ed2cd18091b49c927101c5b7ddf82ec9..4849aab50f4a745b44013ffd8b386a7de3d06bdb 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -84,7 +84,6 @@
     var $sidebarGutterToggle = $('.js-sidebar-toggle');
     var $flash = $('.flash-container');
     var bootstrapBreakpoint = bp.getBreakpointSize();
-    var checkInitialSidebarSize;
     var fitSidebarForSize;
 
     // Set the default path for all cookies to GitLab's root directory
@@ -246,19 +245,11 @@
         return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
       }
     };
-    checkInitialSidebarSize = function () {
-      bootstrapBreakpoint = bp.getBreakpointSize();
-      if (bootstrapBreakpoint === 'xs' || 'sm') {
-        return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
-      }
-    };
     $window.off('resize.app').on('resize.app', function () {
       return fitSidebarForSize();
     });
     gl.awardsHandler = new AwardsHandler();
-    checkInitialSidebarSize();
     new Aside();
-
     // bind sidebar events
     new gl.Sidebar();
   });
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index a6bc262b6578992c5f8a4b2c3266548eb0445557..7e6c44fa1cd846f53a203461c5a7ddacd80dfd3c 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,7 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
 /* global autosize */
 
-/*= require jquery.ba-resize */
 /*= require autosize */
 
 (function() {
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 6a49715590c875a5f76ba307c601a44e7883fec4..a7181904ac9bbfeb257425eff92edcc50f8ce5ea 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,6 +1,19 @@
 /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
 (function(w) {
   $(function() {
+    var toggleContainer = function(container, /* optional */toggleState) {
+      var $container = $(container);
+
+      $container
+        .find('.js-toggle-button .fa')
+        .toggleClass('fa-chevron-up', toggleState)
+        .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
+
+      $container
+        .find('.js-toggle-content')
+        .toggle(toggleState);
+    };
+
     // Toggle button. Show/hide content inside parent container.
     // Button does not change visibility. If button has icon - it changes chevron style.
     //
@@ -10,14 +23,7 @@
     //
     $('body').on('click', '.js-toggle-button', function(e) {
       e.preventDefault();
-      $(this)
-        .find('.fa')
-          .toggleClass('fa-chevron-down fa-chevron-up')
-        .end()
-        .closest('.js-toggle-container')
-          .find('.js-toggle-content')
-            .toggle()
-      ;
+      toggleContainer($(this).closest('.js-toggle-container'));
     });
 
     // If we're accessing a permalink, ensure it is not inside a
@@ -26,8 +32,8 @@
     var anchor = hash && document.getElementById(hash);
     var container = anchor && $(anchor).closest('.js-toggle-container');
 
-    if (container && container.find('.js-toggle-content').is(':hidden')) {
-      container.find('.js-toggle-button').trigger('click');
+    if (container) {
+      toggleContainer(container, true);
       anchor.scrollIntoView();
     }
   });
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6
index 02459722bbfbc4cc669e465af4f1b31836216ef9..75dfcb66bb078d180a09c356a526aa0218b42fd0 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js.es6
+++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6
@@ -29,6 +29,12 @@
     watch: {
       detail: {
         handler () {
+          if (this.issue.id !== this.detail.issue.id) {
+            $('.js-issue-board-sidebar', this.$el).each((i, el) => {
+              $(el).data('glDropdown').clearMenu();
+            });
+          }
+
           this.issue = this.detail.issue;
         },
         deep: true
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index cabeae74ae3e9037a840ef87ed44bef2a95ed624..c6fdfbcaa105ac889bed3f7a1cfb777b76078b03 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -3,7 +3,7 @@
 
 (function() {
   this.CommitsList = (function() {
-    function CommitsList() {}
+    var CommitsList = {};
 
     CommitsList.timer = null;
 
@@ -20,6 +20,7 @@
       });
       this.content = $("#commits-list");
       this.searchField = $("#commits-search");
+      this.lastSearch = this.searchField.val();
       return this.initSearch();
     };
 
@@ -37,6 +38,7 @@
       var commitsUrl, form, search;
       form = $(".commits-search-form");
       search = CommitsList.searchField.val();
+      if (search === CommitsList.lastSearch) return;
       commitsUrl = form.attr("action") + '?' + form.serialize();
       CommitsList.content.fadeTo('fast', 0.5);
       return $.ajax({
@@ -47,12 +49,16 @@
           return CommitsList.content.fadeTo('fast', 1.0);
         },
         success: function(data) {
+          CommitsList.lastSearch = search;
           CommitsList.content.html(data.html);
           return history.replaceState({
             page: commitsUrl
           // Change url so if user reload a page - search results are saved
           }, document.title, commitsUrl);
         },
+        error: function() {
+          CommitsList.lastSearch = null;
+        },
         dataType: "json"
       });
     };
diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..b94125a421033df0f6d40cf3b4db60e2ef67dfcd
--- /dev/null
+++ b/app/assets/javascripts/copy_as_gfm.js.es6
@@ -0,0 +1,355 @@
+/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
+/* jshint esversion: 6 */
+
+/*= require lib/utils/common_utils */
+
+(() => {
+  const gfmRules = {
+    // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
+    // GitLab Flavored Markdown (GFM) to HTML.
+    // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
+    // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
+    // from GFM should have a handler here, in reverse order.
+    // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
+    InlineDiffFilter: {
+      'span.idiff.addition'(el, text) {
+        return `{+${text}+}`;
+      },
+      'span.idiff.deletion'(el, text) {
+        return `{-${text}-}`;
+      },
+    },
+    TaskListFilter: {
+      'input[type=checkbox].task-list-item-checkbox'(el, text) {
+        return `[${el.checked ? 'x' : ' '}]`;
+      },
+    },
+    ReferenceFilter: {
+      'a.gfm:not([data-link=true])'(el, text) {
+        return el.dataset.original || text;
+      },
+    },
+    AutolinkFilter: {
+      'a'(el, text) {
+        // Fallback on the regular MarkdownFilter's `a` handler.
+        if (text !== el.getAttribute('href')) return false;
+
+        return text;
+      },
+    },
+    TableOfContentsFilter: {
+      'ul.section-nav'(el, text) {
+        return '[[_TOC_]]';
+      },
+    },
+    EmojiFilter: {
+      'img.emoji'(el, text) {
+        return el.getAttribute('alt');
+      },
+    },
+    ImageLinkFilter: {
+      'a.no-attachment-icon'(el, text) {
+        return text;
+      },
+    },
+    VideoLinkFilter: {
+      '.video-container'(el, text) {
+        const videoEl = el.querySelector('video');
+        if (!videoEl) return false;
+
+        return CopyAsGFM.nodeToGFM(videoEl);
+      },
+      'video'(el, text) {
+        return `![${el.dataset.title}](${el.getAttribute('src')})`;
+      },
+    },
+    MathFilter: {
+      'pre.code.math[data-math-style=display]'(el, text) {
+        return `\`\`\`math\n${text.trim()}\n\`\`\``;
+      },
+      'code.code.math[data-math-style=inline]'(el, text) {
+        return `$\`${text}\`$`;
+      },
+      'span.katex-display span.katex-mathml'(el, text) {
+        const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+        if (!mathAnnotation) return false;
+
+        return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
+      },
+      'span.katex-mathml'(el, text) {
+        const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+        if (!mathAnnotation) return false;
+
+        return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
+      },
+      'span.katex-html'(el, text) {
+        // We don't want to include the content of this element in the copied text.
+        return '';
+      },
+      'annotation[encoding="application/x-tex"]'(el, text) {
+        return text.trim();
+      },
+    },
+    SanitizationFilter: {
+      'dl'(el, text) {
+        let lines = text.trim().split('\n');
+        // Add two spaces to the front of subsequent list items lines,
+        // or leave the line entirely blank.
+        lines = lines.map((l) => {
+          const line = l.trim();
+          if (line.length === 0) return '';
+
+          return `  ${line}`;
+        });
+
+        return `<dl>\n${lines.join('\n')}\n</dl>`;
+      },
+      'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) {
+        const tag = el.nodeName.toLowerCase();
+        return `<${tag}>${text}</${tag}>`;
+      },
+    },
+    SyntaxHighlightFilter: {
+      'pre.code.highlight'(el, t) {
+        const text = t.trim();
+
+        let lang = el.getAttribute('lang');
+        if (lang === 'plaintext') {
+          lang = '';
+        }
+
+        // Prefixes lines with 4 spaces if the code contains triple backticks
+        if (lang === '' && text.match(/^```/gm)) {
+          return text.split('\n').map((l) => {
+            const line = l.trim();
+            if (line.length === 0) return '';
+
+            return `    ${line}`;
+          }).join('\n');
+        }
+
+        return `\`\`\`${lang}\n${text}\n\`\`\``;
+      },
+      'pre > code'(el, text) {
+         // Don't wrap code blocks in ``
+        return text;
+      },
+    },
+    MarkdownFilter: {
+      'br'(el, text) {
+        // Two spaces at the end of a line are turned into a BR
+        return '  ';
+      },
+      'code'(el, text) {
+        let backtickCount = 1;
+        const backtickMatch = text.match(/`+/);
+        if (backtickMatch) {
+          backtickCount = backtickMatch[0].length + 1;
+        }
+
+        const backticks = Array(backtickCount + 1).join('`');
+        const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
+
+        return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks;
+      },
+      'blockquote'(el, text) {
+        return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
+      },
+      'img'(el, text) {
+        return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+      },
+      'a.anchor'(el, text) {
+        // Don't render a Markdown link for the anchor link inside a heading
+        return text;
+      },
+      'a'(el, text) {
+        return `[${text}](${el.getAttribute('href')})`;
+      },
+      'li'(el, text) {
+        const lines = text.trim().split('\n');
+        const firstLine = `- ${lines.shift()}`;
+        // Add four spaces to the front of subsequent list items lines,
+        // or leave the line entirely blank.
+        const nextLines = lines.map((s) => {
+          if (s.trim().length === 0) return '';
+
+          return `    ${s}`;
+        });
+
+        return `${firstLine}\n${nextLines.join('\n')}`;
+      },
+      'ul'(el, text) {
+        return text;
+      },
+      'ol'(el, text) {
+        // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
+        return text.replace(/^- /mg, '1. ');
+      },
+      'h1'(el, text) {
+        return `# ${text.trim()}`;
+      },
+      'h2'(el, text) {
+        return `## ${text.trim()}`;
+      },
+      'h3'(el, text) {
+        return `### ${text.trim()}`;
+      },
+      'h4'(el, text) {
+        return `#### ${text.trim()}`;
+      },
+      'h5'(el, text) {
+        return `##### ${text.trim()}`;
+      },
+      'h6'(el, text) {
+        return `###### ${text.trim()}`;
+      },
+      'strong'(el, text) {
+        return `**${text}**`;
+      },
+      'em'(el, text) {
+        return `_${text}_`;
+      },
+      'del'(el, text) {
+        return `~~${text}~~`;
+      },
+      'sup'(el, text) {
+        return `^${text}`;
+      },
+      'hr'(el, text) {
+        return '-----';
+      },
+      'table'(el, text) {
+        const theadEl = el.querySelector('thead');
+        const tbodyEl = el.querySelector('tbody');
+        if (!theadEl || !tbodyEl) return false;
+
+        const theadText = CopyAsGFM.nodeToGFM(theadEl);
+        const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
+
+        return theadText + tbodyText;
+      },
+      'thead'(el, text) {
+        const cells = _.map(el.querySelectorAll('th'), (cell) => {
+          let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
+
+          let before = '';
+          let after = '';
+          switch (cell.style.textAlign) {
+            case 'center':
+              before = ':';
+              after = ':';
+              chars -= 2;
+              break;
+            case 'right':
+              after = ':';
+              chars -= 1;
+              break;
+            default:
+              break;
+          }
+
+          chars = Math.max(chars, 3);
+
+          const middle = Array(chars + 1).join('-');
+
+          return before + middle + after;
+        });
+
+        return `${text}|${cells.join('|')}|`;
+      },
+      'tr'(el, text) {
+        const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
+        return `| ${cells.join(' | ')} |`;
+      },
+    },
+  };
+
+  class CopyAsGFM {
+    constructor() {
+      $(document).on('copy', '.md, .wiki', this.handleCopy);
+      $(document).on('paste', '.js-gfm-input', this.handlePaste);
+    }
+
+    handleCopy(e) {
+      const clipboardData = e.originalEvent.clipboardData;
+      if (!clipboardData) return;
+
+      const documentFragment = window.gl.utils.getSelectedFragment();
+      if (!documentFragment) return;
+
+      // If the documentFragment contains more than just Markdown, don't copy as GFM.
+      if (documentFragment.querySelector('.md, .wiki')) return;
+
+      e.preventDefault();
+      clipboardData.setData('text/plain', documentFragment.textContent);
+
+      const gfm = CopyAsGFM.nodeToGFM(documentFragment);
+      clipboardData.setData('text/x-gfm', gfm);
+    }
+
+    handlePaste(e) {
+      const clipboardData = e.originalEvent.clipboardData;
+      if (!clipboardData) return;
+
+      const gfm = clipboardData.getData('text/x-gfm');
+      if (!gfm) return;
+
+      e.preventDefault();
+
+      window.gl.utils.insertText(e.target, gfm);
+    }
+
+    static nodeToGFM(node) {
+      if (node.nodeType === Node.TEXT_NODE) {
+        return node.textContent;
+      }
+
+      const text = this.innerGFM(node);
+
+      if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+        return text;
+      }
+
+      for (const filter in gfmRules) {
+        const rules = gfmRules[filter];
+
+        for (const selector in rules) {
+          const func = rules[selector];
+
+          if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
+
+          const result = func(node, text);
+          if (result === false) continue;
+
+          return result;
+        }
+      }
+
+      return text;
+    }
+
+    static innerGFM(parentNode) {
+      const nodes = parentNode.childNodes;
+
+      const clonedParentNode = parentNode.cloneNode(true);
+      const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
+
+      for (let i = 0; i < nodes.length; i += 1) {
+        const node = nodes[i];
+        const clonedNode = clonedNodes[i];
+
+        const text = this.nodeToGFM(node);
+
+        // `clonedNode.replaceWith(text)` is not yet widely supported
+        clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
+      }
+
+      return clonedParentNode.innerText || clonedParentNode.textContent;
+    }
+  }
+
+  window.gl = window.gl || {};
+  window.gl.CopyAsGFM = CopyAsGFM;
+
+  new CopyAsGFM();
+})();
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6
index 5e1a4c948aa8d3bfcee515561b0e9edbc574e93f..35a029194d0802a4be76fe0b36012afb1060936a 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js.es6
@@ -1,5 +1,7 @@
 /* eslint-disable class-methods-use-this */
 
+//= require lib/utils/url_utility */
+
 (() => {
   const UNFOLD_COUNT = 20;
 
@@ -104,11 +106,11 @@
     }
 
     highlighSelectedLine() {
+      const hash = gl.utils.getLocationHash();
       const $diffFiles = $('.diff-file');
       $diffFiles.find('.hll').removeClass('hll');
 
-      if (window.location.hash !== '') {
-        const hash = window.location.hash.replace('#', '');
+      if (hash) {
         $diffFiles
           .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
           .addClass('hll');
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index dcf67a8fd686c82882e429fbd408a8937f77666a..edec21e3b63d249890c6206076e460b10444af63 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -8,7 +8,6 @@
 /* global ShortcutsIssuable */
 /* global ZenMode */
 /* global Milestone */
-/* global GLForm */
 /* global IssuableForm */
 /* global LabelsSelect */
 /* global MilestoneSelect */
@@ -64,17 +63,6 @@
           new UsernameValidator();
           new ActiveTabMemoizer();
           break;
-        case 'sessions:create':
-          if (!gon.u2f) break;
-          window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
-            $("#js-authenticate-u2f"),
-            '#js-login-u2f-form',
-            gon.u2f,
-            document.querySelector('#js-login-2fa-device'),
-            document.querySelector('.js-2fa-form'),
-          );
-          window.gl.u2fAuthenticate.start();
-          break;
         case 'projects:boards:show':
         case 'projects:boards:index':
           shortcut_handler = new ShortcutsNavigation();
@@ -110,7 +98,7 @@
         case 'projects:milestones:edit':
           new ZenMode();
           new gl.DueDateSelectors();
-          new GLForm($('.milestone-form'));
+          new gl.GLForm($('.milestone-form'));
           break;
         case 'groups:milestones:new':
           new ZenMode();
@@ -121,7 +109,7 @@
         case 'projects:issues:new':
         case 'projects:issues:edit':
           shortcut_handler = new ShortcutsNavigation();
-          new GLForm($('.issue-form'));
+          new gl.GLForm($('.issue-form'));
           new IssuableForm($('.issue-form'));
           new LabelsSelect();
           new MilestoneSelect();
@@ -131,7 +119,7 @@
         case 'projects:merge_requests:edit':
           new gl.Diff();
           shortcut_handler = new ShortcutsNavigation();
-          new GLForm($('.merge-request-form'));
+          new gl.GLForm($('.merge-request-form'));
           new IssuableForm($('.merge-request-form'));
           new LabelsSelect();
           new MilestoneSelect();
@@ -139,11 +127,11 @@
           break;
         case 'projects:tags:new':
           new ZenMode();
-          new GLForm($('.tag-form'));
+          new gl.GLForm($('.tag-form'));
           break;
         case 'projects:releases:edit':
           new ZenMode();
-          new GLForm($('.release-form'));
+          new gl.GLForm($('.release-form'));
           break;
         case 'projects:merge_requests:show':
           new gl.Diff();
@@ -261,6 +249,9 @@
         case 'projects:artifacts:browse':
           new BuildArtifacts();
           break;
+        case 'help:index':
+          gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
+          break;
         case 'search:show':
           new Search();
           break;
@@ -277,6 +268,17 @@
           break;
       }
       switch (path.first()) {
+        case 'sessions':
+        case 'omniauth_callbacks':
+          if (!gon.u2f) break;
+          gl.u2fAuthenticate = new gl.U2FAuthenticate(
+            $('#js-authenticate-u2f'),
+            '#js-login-u2f-form',
+            gon.u2f,
+            document.querySelector('#js-login-2fa-device'),
+            document.querySelector('.js-2fa-form'),
+          );
+          gl.u2fAuthenticate.start();
         case 'admin':
           new Admin();
           switch (path[1]) {
@@ -329,7 +331,7 @@
               new gl.Wikis();
               shortcut_handler = new ShortcutsNavigation();
               new ZenMode();
-              new GLForm($('.wiki-form'));
+              new gl.GLForm($('.wiki-form'));
               break;
             case 'snippets':
               shortcut_handler = new ShortcutsNavigation();
@@ -354,7 +356,7 @@
       }
       // If we haven't installed a custom shortcut handler, install the default one
       if (!shortcut_handler) {
-        return new Shortcuts();
+        new Shortcuts();
       }
     };
 
diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js
index ed545ec8748629a54cbb66e68aa2aaec91b8aed2..8b14191395b6ea7cc47a8b9c9ed4508006d7e5d2 100644
--- a/app/assets/javascripts/droplab/droplab.js
+++ b/app/assets/javascripts/droplab/droplab.js
@@ -58,10 +58,12 @@ var CustomEvent = require('./custom_event_polyfill');
 var utils = require('./utils');
 
 var DropDown = function(list) {
+  this.currentIndex = 0;
   this.hidden = true;
   this.list = list;
   this.items = [];
   this.getItems();
+  this.initTemplateString();
   this.addEvents();
   this.initialState = list.innerHTML;
 };
@@ -72,6 +74,17 @@ Object.assign(DropDown.prototype, {
     return this.items;
   },
 
+  initTemplateString: function() {
+    var items = this.items || this.getItems();
+
+    var templateString = '';
+    if(items.length > 0) {
+      templateString = items[items.length - 1].outerHTML;
+    }
+    this.templateString = templateString;
+    return this.templateString;
+  },
+
   clickEvent: function(e) {
     // climb up the tree to find the LI
     var selected = utils.closest(e.target, 'LI');
@@ -111,30 +124,21 @@ Object.assign(DropDown.prototype, {
 
   addData: function(data) {
     this.data = (this.data || []).concat(data);
-    this.render(data);
+    this.render(this.data);
   },
 
   // call render manually on data;
   render: function(data){
     // debugger
     // empty the list first
-    var sampleItem;
+    var templateString = this.templateString;
     var newChildren = [];
     var toAppend;
 
-    for(var i = 0; i < this.items.length; i++) {
-      var item = this.items[i];
-      sampleItem = item;
-      if(item.parentNode && item.parentNode.dataset.hasOwnProperty('dynamic')) {
-        item.parentNode.removeChild(item);  
-      }
-    }
-
-    newChildren = this.data.map(function(dat){
-      var html = utils.t(sampleItem.outerHTML, dat);
+    newChildren = (data ||[]).map(function(dat){
+      var html = utils.t(templateString, dat);
       var template = document.createElement('div');
       template.innerHTML = html;
-      // console.log(template.content)
 
       // Help set the image src template
       var imageTags = template.querySelectorAll('img[data-src]');
@@ -156,27 +160,30 @@ Object.assign(DropDown.prototype, {
     if(toAppend) {
       toAppend.innerHTML = newChildren.join('');
     } else {
-      this.list.innerHTML = newChildren.join('');  
+      this.list.innerHTML = newChildren.join('');
     }
   },
 
   show: function() {
-    // debugger
-    this.list.style.display = 'block';
-    this.hidden = false;
+    if (this.hidden) {
+      // debugger
+      this.list.style.display = 'block';
+      this.currentIndex = 0;
+      this.hidden = false;
+    }
   },
 
   hide: function() {
-    // debugger
-    this.list.style.display = 'none';
-    this.hidden = true;
-  },
-
-  destroy: function() {
     if (!this.hidden) {
-      this.hide();
+      // debugger
+      this.list.style.display = 'none';
+      this.currentIndex = 0;
+      this.hidden = true;
     }
+  },
 
+  destroy: function() {
+    this.hide();
     this.list.removeEventListener('click', this.clickWrapper);
   }
 });
@@ -278,7 +285,7 @@ require('./window')(function(w){
             self.hooks[i].list.hide();
           }
         }.bind(this);
-        w.addEventListener('click', this.windowClickedWrapper);
+        document.addEventListener('click', this.windowClickedWrapper);
       },
 
       removeEvents: function(){
@@ -307,7 +314,7 @@ require('./window')(function(w){
         if(!list){
           list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]);
         }
-        
+
         if(hook) {
           if(hook.tagName === 'A' || hook.tagName === 'BUTTON') {
             this.hooks.push(new HookButton(hook, list, plugins, config));
@@ -462,6 +469,8 @@ Object.assign(HookInput.prototype, {
     var self = this;
 
     this.mousedown = function mousedown(e) {
+      if(self.hasRemovedEvents) return;
+
       var mouseEvent = new CustomEvent('mousedown.dl', {
         detail: {
           hook: self,
@@ -474,6 +483,10 @@ Object.assign(HookInput.prototype, {
     }
 
     this.input = function input(e) {
+      if(self.hasRemovedEvents) return;
+
+      self.list.show();
+
       var inputEvent = new CustomEvent('input.dl', {
         detail: {
           hook: self,
@@ -483,18 +496,23 @@ Object.assign(HookInput.prototype, {
         cancelable: true
       });
       e.target.dispatchEvent(inputEvent);
-      self.list.show();
     }
 
     this.keyup = function keyup(e) {
+      if(self.hasRemovedEvents) return;
+
       keyEvent(e, 'keyup.dl');
     }
 
     this.keydown = function keydown(e) {
+      if(self.hasRemovedEvents) return;
+
       keyEvent(e, 'keydown.dl');
     }
 
     function keyEvent(e, keyEventName){
+      self.list.show();
+
       var keyEvent = new CustomEvent(keyEventName, {
         detail: {
           hook: self,
@@ -506,7 +524,6 @@ Object.assign(HookInput.prototype, {
         cancelable: true
       });
       e.target.dispatchEvent(keyEvent);
-      self.list.show();
     }
 
     this.events = this.events || {};
@@ -520,7 +537,8 @@ Object.assign(HookInput.prototype, {
     this.trigger.addEventListener('keydown', this.keydown);
   },
 
-  removeEvents: function(){
+  removeEvents: function() {
+    this.hasRemovedEvents = true;
     this.trigger.removeEventListener('mousedown', this.mousedown);
     this.trigger.removeEventListener('input', this.input);
     this.trigger.removeEventListener('keyup', this.keyup);
@@ -563,24 +581,43 @@ require('./window')(function(w){
   module.exports = function(){
     var currentKey;
     var currentFocus;
-    var currentIndex = 0;
     var isUpArrow = false;
     var isDownArrow = false;
     var removeHighlight = function removeHighlight(list) {
-      var listItems = list.list.querySelectorAll('li');
+      var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
+      var listItemsTmp = [];
       for(var i = 0; i < listItems.length; i++) {
-        listItems[i].classList.remove('dropdown-active');
+        var listItem = listItems[i];
+        listItem.classList.remove('dropdown-active');
+
+        if (listItem.style.display !== 'none') {
+          listItemsTmp.push(listItem);
+        }
       }
-      return listItems;
+      return listItemsTmp;
     };
 
     var setMenuForArrows = function setMenuForArrows(list) {
       var listItems = removeHighlight(list);
-      if(currentIndex>0){
-        if(!listItems[currentIndex-1]){
-          currentIndex = currentIndex-1; 
+      if(list.currentIndex>0){
+        if(!listItems[list.currentIndex-1]){
+          list.currentIndex = list.currentIndex-1;
+        }
+
+        if (listItems[list.currentIndex-1]) {
+          var el = listItems[list.currentIndex-1];
+          var filterDropdownEl = el.closest('.filter-dropdown');
+          el.classList.add('dropdown-active');
+
+          if (filterDropdownEl) {
+            var filterDropdownBottom = filterDropdownEl.offsetHeight;
+            var elOffsetTop = el.offsetTop - 30;
+
+            if (elOffsetTop > filterDropdownBottom) {
+              filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
+            }
+          }
         }
-        listItems[currentIndex-1].classList.add('dropdown-active');
       }
     };
 
@@ -588,13 +625,13 @@ require('./window')(function(w){
       var list = e.detail.hook.list;
       removeHighlight(list);
       list.show();
-      currentIndex = 0;
+      list.currentIndex = 0;
       isUpArrow = false;
       isDownArrow = false;
     };
     var selectItem = function selectItem(list) {
       var listItems = removeHighlight(list);
-      var currentItem = listItems[currentIndex-1];
+      var currentItem = listItems[list.currentIndex-1];
       var listEvent = new CustomEvent('click.dl', {
         detail: {
           list: list,
@@ -608,6 +645,8 @@ require('./window')(function(w){
 
     var keydown = function keydown(e){
       var typedOn = e.target;
+      var list = e.detail.hook.list;
+      var currentIndex = list.currentIndex;
       isUpArrow = false;
       isDownArrow = false;
 
@@ -630,7 +669,7 @@ require('./window')(function(w){
           return;
         }
         if(currentKey === 'ArrowUp') {
-          isUpArrow = true; 
+          isUpArrow = true;
         }
         if(currentKey === 'ArrowDown') {
           isDownArrow = true;
@@ -639,6 +678,7 @@ require('./window')(function(w){
       if(isUpArrow){ currentIndex--; }
       if(isDownArrow){ currentIndex++; }
       if(currentIndex < 0){ currentIndex = 0; }
+      list.currentIndex = currentIndex;
       setMenuForArrows(e.detail.hook.list);
     };
 
@@ -668,16 +708,16 @@ var camelize = function(str) {
 };
 
 var closest = function(thisTag, stopTag) {
-  while(thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){
+  while(thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){
     thisTag = thisTag.parentNode;
   }
   return thisTag;
 };
 
 var isDropDownParts = function(target) {
-  if(target.tagName === 'HTML') { return false; }
+  if(!target || target.tagName === 'HTML') { return false; }
   return (
-    target.hasAttribute(DATA_TRIGGER) || 
+    target.hasAttribute(DATA_TRIGGER) ||
       target.hasAttribute(DATA_DROPDOWN)
   );
 };
diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js
index f20610b3811de8fc8cb08a87e711125d17f9e49d..c290e1a8355d569422b8d86620232fab60245036 100644
--- a/app/assets/javascripts/droplab/droplab_ajax.js
+++ b/app/assets/javascripts/droplab/droplab_ajax.js
@@ -9,6 +9,7 @@ require('../window')(function(w){
 
   w.droplabAjax = {
     _loadUrlData: function _loadUrlData(url) {
+      var self = this;
       return new Promise(function(resolve, reject) {
         var xhr = new XMLHttpRequest;
         xhr.open('GET', url, true);
@@ -16,6 +17,7 @@ require('../window')(function(w){
           if(xhr.readyState === XMLHttpRequest.DONE) {
             if (xhr.status === 200) {
               var data = JSON.parse(xhr.responseText);
+              self.cache[url] = data;
               return resolve(data);
             } else {
               return reject([xhr.responseText, xhr.status]);
@@ -26,9 +28,23 @@ require('../window')(function(w){
       });
     },
 
+    _loadData: function _loadData(data, config, self) {
+      if (config.loadingTemplate) {
+        var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
+
+        if (dataLoadingTemplate) {
+          dataLoadingTemplate.outerHTML = self.listTemplate;
+        }
+      }
+
+      self.hook.list[config.method].call(self.hook.list, data);
+    },
+
     init: function init(hook) {
       var self = this;
+      self.cache = self.cache || {};
       var config = hook.config.droplabAjax;
+      this.hook = hook;
 
       if (!config || !config.endpoint || !config.method) {
         return;
@@ -49,22 +65,23 @@ require('../window')(function(w){
         dynamicList.outerHTML = loadingTemplate.outerHTML;
       }
 
-      this._loadUrlData(config.endpoint)
-        .then(function(d) {
-          if (config.loadingTemplate) {
-            var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]');
-
-            if (dataLoadingTemplate) {
-              dataLoadingTemplate.outerHTML = self.listTemplate;
-            }
-          }
-          hook.list[config.method].call(hook.list, d);
-        }).catch(function(e) {
-          throw new droplabAjaxException(e.message || e);
-        });
+      if (self.cache[config.endpoint]) {
+        self._loadData(self.cache[config.endpoint], config, self);
+      } else {
+        this._loadUrlData(config.endpoint)
+          .then(function(d) {
+            self._loadData(d, config, self);
+          }).catch(function(e) {
+            throw new droplabAjaxException(e.message || e);
+          });
+      }
     },
 
     destroy: function() {
+      if (this.listTemplate) {
+        var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
+        dynamicList.outerHTML = this.listTemplate;
+      }
     }
   };
 });
@@ -76,4 +93,4 @@ module.exports = function(callback) {
 };
 
 },{}]},{},[1])(1)
-});
\ No newline at end of file
+});
diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js
index af163f7685144a00e981e9b52919193250b4b577..b63d73066cb6717383bdc4ef08b81678fdd87ba1 100644
--- a/app/assets/javascripts/droplab/droplab_ajax_filter.js
+++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js
@@ -72,31 +72,22 @@ require('../window')(function(w){
       var params = config.params || {};
       params[config.searchKey] = searchValue;
       var self = this;
-      this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) {
-        if (config.loadingTemplate && self.hook.list.data === undefined ||
-          self.hook.list.data.length === 0) {
-          const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
-
-          if (dataLoadingTemplate) {
-            dataLoadingTemplate.outerHTML = self.listTemplate;
-          }
-        }
-
-        if (!self.destroyed) {
-          var hookListChildren = self.hook.list.list.children;
-          var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
-
-          if (onlyDynamicList && data.length === 0) {
-            self.hook.list.hide();
-          }
-
-          self.hook.list.setData.call(self.hook.list, data);
-        }
-        self.notLoading();
-      });
+      self.cache = self.cache || {};
+      var url = config.endpoint + this.buildParams(params);
+      var urlCachedData = self.cache[url];
+
+      if (urlCachedData) {
+        self._loadData(urlCachedData, config, self);
+      } else {
+        this._loadUrlData(url)
+          .then(function(data) {
+            self._loadData(data, config, self);
+          });
+      }
     },
 
     _loadUrlData: function _loadUrlData(url) {
+      var self = this;
       return new Promise(function(resolve, reject) {
         var xhr = new XMLHttpRequest;
         xhr.open('GET', url, true);
@@ -104,6 +95,7 @@ require('../window')(function(w){
           if(xhr.readyState === XMLHttpRequest.DONE) {
             if (xhr.status === 200) {
               var data = JSON.parse(xhr.responseText);
+              self.cache[url] = data;
               return resolve(data);
             } else {
               return reject([xhr.responseText, xhr.status]);
@@ -114,6 +106,30 @@ require('../window')(function(w){
       });
     },
 
+    _loadData: function _loadData(data, config, self) {
+      if (config.loadingTemplate && self.hook.list.data === undefined ||
+        self.hook.list.data.length === 0) {
+        const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
+
+        if (dataLoadingTemplate) {
+          dataLoadingTemplate.outerHTML = self.listTemplate;
+        }
+      }
+
+      if (!self.destroyed) {
+        var hookListChildren = self.hook.list.list.children;
+        var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
+
+        if (onlyDynamicList && data.length === 0) {
+          self.hook.list.hide();
+        }
+
+        self.hook.list.setData.call(self.hook.list, data);
+      }
+      self.notLoading();
+      self.hook.list.currentIndex = 0;
+    },
+
     buildParams: function(params) {
       if (!params) return '';
       var paramsArray = Object.keys(params).map(function(param) {
@@ -142,4 +158,4 @@ module.exports = function(callback) {
 };
 
 },{}]},{},[1])(1)
-});
\ No newline at end of file
+});
diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js
index 41a220831f96f753788238df1e1b7d2cadd866cf..9b40a3f20a434beee9af7d50cf5dacaf36259d5e 100644
--- a/app/assets/javascripts/droplab/droplab_filter.js
+++ b/app/assets/javascripts/droplab/droplab_filter.js
@@ -6,6 +6,8 @@ require('../window')(function(w){
   w.droplabFilter = {
 
     keydownWrapper: function(e){
+        var hiddenCount = 0;
+        var dataHiddenCount = 0;
         var list = e.detail.hook.list;
         var data = list.data;
         var value = e.detail.hook.trigger.value.toLowerCase();
@@ -27,10 +29,22 @@ require('../window')(function(w){
           };
         }
 
+        dataHiddenCount = data.filter(function(o) {
+          return !o.droplab_hidden;
+        }).length;
+
         matches = data.map(function(o) {
           return filterFunction(o, value);
         });
-        list.render(matches);
+
+        hiddenCount = matches.filter(function(o) {
+          return !o.droplab_hidden;
+        }).length;
+
+        if (dataHiddenCount !== hiddenCount) {
+          list.render(matches);
+          list.currentIndex = 0;
+        }
     },
 
     init: function init(hookInput) {
@@ -57,4 +71,4 @@ module.exports = function(callback) {
 };
 
 },{}]},{},[1])(1)
-});
\ No newline at end of file
+});
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index fea642467fa3532fb49b9b9baa51a9c9b9abc0bf..558828e1bc9cf9d89dce0ee755405e6e8c3fdc57 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -180,9 +180,9 @@
                 <tr>
                   <th class="environments-name">Environment</th>
                   <th class="environments-deploy">Last deployment</th>
-                  <th class="environments-build">Build</th>
+                  <th class="environments-build">Job</th>
                   <th class="environments-commit">Commit</th>
-                  <th class="environments-date">Created</th>
+                  <th class="environments-date">Updated</th>
                   <th class="hidden-xs environments-actions"></th>
                 </tr>
               </thead>
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6
index 9f24a6a4f88ac63abd98b7ad7e450a1f08b8b4e9..3b003f6f661cb6856b7d8b34a5057464b03b3113 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js.es6
@@ -3,7 +3,6 @@
 //= require ./components/environment
 //= require ./vue_resource_interceptor
 
-
 $(() => {
   window.gl = window.gl || {};
 
diff --git a/app/assets/javascripts/extensions/custom_event.js.es6 b/app/assets/javascripts/extensions/custom_event.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..abedae4c1c7a669fad71f73180153be3f09dcde8
--- /dev/null
+++ b/app/assets/javascripts/extensions/custom_event.js.es6
@@ -0,0 +1,12 @@
+/* global CustomEvent */
+/* eslint-disable no-global-assign */
+
+// Custom event support for IE
+CustomEvent = function CustomEvent(event, parameters) {
+  const params = parameters || { bubbles: false, cancelable: false, detail: undefined };
+  const evt = document.createEvent('CustomEvent');
+  evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
+  return evt;
+};
+
+CustomEvent.prototype = window.Event.prototype;
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
index 63c20f57520c1cc87a4f5a20e985a981dc4dba24..7d297b8eee8666af77c1297edc38e255fa37cd3c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
@@ -9,7 +9,7 @@
       this.config = {
         droplabFilter: {
           template: 'hint',
-          filterFunction: gl.DropdownUtils.filterHint,
+          filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
         },
       };
     }
@@ -20,6 +20,9 @@
       if (selected.tagName === 'LI') {
         if (selected.hasAttribute('data-value')) {
           this.dismissDropdown();
+        } else if (selected.getAttribute('data-action') === 'submit') {
+          this.dismissDropdown();
+          this.dispatchFormSubmitEvent();
         } else {
           const token = selected.querySelector('.js-filter-hint').innerText.trim();
           const tag = selected.querySelector('.js-filter-tag').innerText.trim();
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
index f06c3fc9c6f58d2389aa85c386902585196d70d3..13cbec1be4a038a3c50de1993449e61a415b6ff1 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
@@ -15,7 +15,7 @@
           loadingTemplate: this.loadingTemplate,
         },
         droplabFilter: {
-          filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol),
+          filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
         },
       };
     }
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
index e80d266ae899b1d829c36cfd04945ecf5bfdedcf..162fd6044e540b7514e438fd01a98010e99b64f6 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
@@ -37,10 +37,17 @@
     }
 
     getSearchInput() {
-      const query = this.input.value.trim();
+      const query = gl.DropdownUtils.getSearchInput(this.input);
       const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+      let value = lastToken.value || '';
 
-      return lastToken.value || '';
+      // Removes the first character if it is a quotation so that we can search
+      // with multiple words
+      if (value[0] === '"' || value[0] === '\'') {
+        value = value.slice(1);
+      }
+
+      return value;
     }
 
     init() {
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
index c27ef3042d1bdfdf2f6f082a55be9b6df4f96b5f..de3fa1167171baca9a0fc3ab4b5090f103a44826 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
@@ -20,14 +20,17 @@
       return escapedText;
     }
 
-    static filterWithSymbol(filterSymbol, item, query) {
+    static filterWithSymbol(filterSymbol, input, item) {
       const updatedItem = item;
+      const query = gl.DropdownUtils.getSearchInput(input);
       const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
 
       if (lastToken !== searchToken) {
         const title = updatedItem.title.toLowerCase();
         let value = lastToken.value.toLowerCase();
 
+        // Removes the first character if it is a quotation so that we can search
+        // with multiple words
         if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
           value = value.slice(1);
         }
@@ -44,8 +47,9 @@
       return updatedItem;
     }
 
-    static filterHint(item, query) {
+    static filterHint(input, item) {
       const updatedItem = item;
+      const query = gl.DropdownUtils.getSearchInput(input);
       let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
       lastToken = lastToken.key || lastToken || '';
 
@@ -72,6 +76,49 @@
       // Return boolean based on whether it was set
       return dataValue !== null;
     }
+
+    static getSearchInput(filteredSearchInput) {
+      const inputValue = filteredSearchInput.value;
+      const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
+
+      return inputValue.slice(0, right);
+    }
+
+    static getInputSelectionPosition(input) {
+      const selectionStart = input.selectionStart;
+      let inputValue = input.value;
+      // Replace all spaces inside quote marks with underscores
+      // (will continue to match entire string until an end quote is found if any)
+      // This helps with matching the beginning & end of a token:key
+      inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
+
+      // Get the right position for the word selected
+      // Regex matches first space
+      let right = inputValue.slice(selectionStart).search(/\s/);
+
+      if (right >= 0) {
+        right += selectionStart;
+      } else if (right < 0) {
+        right = inputValue.length;
+      }
+
+      // Get the left position for the word selected
+      // Regex matches last non-whitespace character
+      let left = inputValue.slice(0, right).search(/\S+$/);
+
+      if (selectionStart === 0) {
+        left = 0;
+      } else if (selectionStart === inputValue.length && left < 0) {
+        left = inputValue.length;
+      } else if (left < 0) {
+        left = selectionStart;
+      }
+
+      return {
+        left,
+        right,
+      };
+    }
   }
 
   window.gl = window.gl || {};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index 886d8113f4ab2457a9c28499924cc7ecdd4c8e38..859d6515531e7801b5883c92af9a6b27ab2d5f60 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -39,6 +39,7 @@
         }
 
         this.dismissDropdown();
+        this.dispatchInputEvent();
       }
     }
 
@@ -78,7 +79,16 @@
     dispatchInputEvent() {
       // Propogate input change to FilteredSearchDropdownManager
       // so that it can determine which dropdowns to open
-      this.input.dispatchEvent(new Event('input'));
+      this.input.dispatchEvent(new CustomEvent('input', {
+        bubbles: true,
+        cancelable: true,
+      }));
+    }
+
+    dispatchFormSubmitEvent() {
+      // dispatchEvent() is necessary as form.submit() does not
+      // trigger event handlers
+      this.input.form.dispatchEvent(new Event('submit'));
     }
 
     hideDropdown() {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
index 1cd0483877a297f66b9b92552e29a91722d719ad..00e1c28692f3f2264186b7713e44ef860545a03f 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
@@ -57,28 +57,33 @@
 
     static addWordToInput(tokenName, tokenValue = '') {
       const input = document.querySelector('.filtered-search');
+      const inputValue = input.value;
       const word = `${tokenName}:${tokenValue}`;
 
-      const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(input.value);
-      const lastSearchToken = searchToken.split(' ').last();
-      const lastInputCharacter = input.value[input.value.length - 1];
-      const lastInputTrimmedCharacter = input.value.trim()[input.value.trim().length - 1];
-
-      // Remove the typed tokenName
-      if (word.indexOf(lastSearchToken) === 0 && searchToken !== '') {
-        // Remove spaces after the colon
-        if (lastInputCharacter === ' ' && lastInputTrimmedCharacter === ':') {
-          input.value = input.value.trim();
-        }
-
-        input.value = input.value.slice(0, -1 * lastSearchToken.length);
-      } else if (lastInputCharacter !== ' ' || (lastToken && lastToken.value[lastToken.value.length - 1] === ' ')) {
-        // Remove the existing tokenValue
-        const lastTokenString = `${lastToken.key}:${lastToken.symbol}${lastToken.value}`;
-        input.value = input.value.slice(0, -1 * lastTokenString.length);
+      // Get the string to replace
+      let newCaretPosition = input.selectionStart;
+      const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
+
+      input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
+
+      // If we have added a tokenValue at the end of the input,
+      // add a space and set selection to the end
+      if (right >= inputValue.length && tokenValue !== '') {
+        input.value += ' ';
+        newCaretPosition = input.value.length;
       }
 
-      input.value += word;
+      gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input);
+    }
+
+    static updateInputCaretPosition(selectionStart, input) {
+      // Reset the position
+      // Sometimes can end up at end of input
+      input.setSelectionRange(selectionStart, selectionStart);
+
+      const { right } = gl.DropdownUtils.getInputSelectionPosition(input);
+
+      input.setSelectionRange(right, right);
     }
 
     updateCurrentDropdownOffset() {
@@ -90,9 +95,18 @@
         this.font = window.getComputedStyle(this.filteredSearchInput).font;
       }
 
+      const input = this.filteredSearchInput;
+      const inputText = input.value.slice(0, input.selectionStart);
       const filterIconPadding = 27;
-      const offset = gl.text
-        .getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding;
+      let offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding;
+
+      const currentDropdownWidth = this.mapping[key].element.clientWidth === 0 ? 200 :
+      this.mapping[key].element.clientWidth;
+      const offsetMaxWidth = this.filteredSearchInput.clientWidth - currentDropdownWidth;
+
+      if (offsetMaxWidth < offset) {
+        offset = offsetMaxWidth;
+      }
 
       this.mapping[key].reference.setOffset(offset);
     }
@@ -148,9 +162,9 @@
 
     setDropdown() {
       const { lastToken, searchToken } = this.tokenizer
-        .processTokens(this.filteredSearchInput.value);
+        .processTokens(gl.DropdownUtils.getSearchInput(this.filteredSearchInput));
 
-      if (this.filteredSearchInput.value.split('').last() === ' ') {
+      if (this.currentDropdown) {
         this.updateCurrentDropdownOffset();
       }
 
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
index ffd0d7e9cba086f9f4ea58840b7321a4182aface..029564ffc61618be9a0b7aa1118974a679217ff3 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -25,24 +25,32 @@
     }
 
     bindEvents() {
+      this.handleFormSubmit = this.handleFormSubmit.bind(this);
       this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
       this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
       this.checkForEnterWrapper = this.checkForEnter.bind(this);
       this.clearSearchWrapper = this.clearSearch.bind(this);
       this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
+      this.tokenChange = this.tokenChange.bind(this);
 
+      this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
       this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
       this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
       this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
       this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
+      this.filteredSearchInput.addEventListener('click', this.tokenChange);
+      this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
       this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
     }
 
     unbindEvents() {
+      this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
       this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
       this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
       this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
       this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
+      this.filteredSearchInput.removeEventListener('click', this.tokenChange);
+      this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
       this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
     }
 
@@ -56,13 +64,26 @@
     }
 
     checkForEnter(e) {
+      if (e.keyCode === 38 || e.keyCode === 40) {
+        const selectionStart = this.filteredSearchInput.selectionStart;
+
+        e.preventDefault();
+        this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
+      }
+
       if (e.keyCode === 13) {
+        const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+        const dropdownEl = dropdown.element;
+        const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
+
         e.preventDefault();
 
-        // Prevent droplab from opening dropdown
-        this.dropdownManager.destroyDroplab();
+        if (!activeElements.length) {
+          // Prevent droplab from opening dropdown
+          this.dropdownManager.destroyDroplab();
 
-        this.search();
+          this.search();
+        }
       }
     }
 
@@ -83,8 +104,14 @@
       this.dropdownManager.resetDropdowns();
     }
 
+    handleFormSubmit(e) {
+      e.preventDefault();
+      this.search();
+    }
+
     loadSearchParamsFromURL() {
       const params = gl.utils.getUrlParamsArray();
+      const usernameParams = this.getUsernameParams();
       const inputValues = [];
 
       params.forEach((p) => {
@@ -115,6 +142,16 @@
             }
 
             inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
+          } else if (!match && keyParam === 'assignee_id') {
+            const id = parseInt(value, 10);
+            if (usernameParams[id]) {
+              inputValues.push(`assignee:@${usernameParams[id]}`);
+            }
+          } else if (!match && keyParam === 'author_id') {
+            const id = parseInt(value, 10);
+            if (usernameParams[id]) {
+              inputValues.push(`author:@${usernameParams[id]}`);
+            }
           } else if (!match && keyParam === 'search') {
             inputValues.push(sanitizedValue);
           }
@@ -159,11 +196,33 @@
       });
 
       if (searchToken) {
-        paths.push(`search=${encodeURIComponent(searchToken)}`);
+        const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
+        paths.push(`search=${sanitized}`);
       }
 
       Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`);
     }
+
+    getUsernameParams() {
+      const usernamesById = {};
+      try {
+        const attribute = this.filteredSearchInput.getAttribute('data-username-params');
+        JSON.parse(attribute).forEach((user) => {
+          usernamesById[user.id] = user.username;
+        });
+      } catch (e) {
+        // do nothing
+      }
+      return usernamesById;
+    }
+
+    tokenChange() {
+      const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+      const currentDropdownRef = dropdown.reference;
+
+      this.setDropdownWrapper();
+      currentDropdownRef.dispatchInputEvent();
+    }
   }
 
   window.gl = window.gl || {};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
index e46373024b6223cc3bd0160379e969c558677aa0..e6b53cd4b55f66f194bdbcd109134a16fa184bdd 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
@@ -21,6 +21,15 @@
     symbol: '~',
   }];
 
+  const alternativeTokenKeys = [{
+    key: 'label',
+    type: 'string',
+    param: 'name',
+    symbol: '~',
+  }];
+
+  const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
+
   const conditions = [{
     url: 'assignee_id=0',
     tokenKey: 'assignee',
@@ -44,6 +53,10 @@
       return tokenKeys;
     }
 
+    static getAlternatives() {
+      return alternativeTokenKeys;
+    }
+
     static getConditions() {
       return conditions;
     }
@@ -57,7 +70,7 @@
     }
 
     static searchByKeyParam(keyParam) {
-      return tokenKeys.find((tokenKey) => {
+      return tokenKeysWithAlternative.find((tokenKey) => {
         let tokenKeyParam = tokenKey.key;
 
         if (tokenKey.param) {
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index a1b7b4428829f5ffc8b420d61eef6987f3c7b776..7f1f2a5d278c3ac7209df0c592be81f05895203a 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -83,12 +83,12 @@
         _a = decodeURI("%C3%80");
         _y = decodeURI("%C3%BF");
 
-        regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi');
+        regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
 
         match = regexp.exec(subtext);
 
         if (match) {
-          return match[2] || match[1];
+          return (match[1] || match[1] === "") ? match[1] : match[2];
         } else {
           return null;
         }
@@ -367,9 +367,14 @@
       return $input.trigger('keyup');
     },
     isLoading(data) {
-      if (!data || !data.length) return false;
-      if (Array.isArray(data)) data = data[0];
-      return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
+      var dataToInspect = data;
+      if (data && data.length > 0) {
+        dataToInspect = data[0];
+      }
+
+      var loadingState = this.defaultLoadingData[0];
+      return dataToInspect &&
+        (dataToInspect === loadingState || dataToInspect.name === loadingState);
     }
   };
 }).call(this);
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 7746535d9ed434fc12f1b1d485cb5b195b9e54b1..5c86e98567a87c47c28c7f97e3324af603b552fe 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -249,7 +249,7 @@
                 _this.fullData = data;
                 _this.parseData(_this.fullData);
                 _this.focusTextInput();
-                if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') {
+                if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
                   return _this.filter.input.trigger('input');
                 }
               };
@@ -512,12 +512,17 @@
 
     // Append the menu into the dropdown
     GitLabDropdown.prototype.appendMenu = function(html) {
+      return this.clearMenu().append(html);
+    };
+
+    GitLabDropdown.prototype.clearMenu = function() {
       var selector;
       selector = '.dropdown-content';
       if (this.dropdown.find(".dropdown-toggle-page").length) {
         selector = ".dropdown-page-one .dropdown-content";
       }
-      return $(selector, this.dropdown).empty().append(html);
+
+      return $(selector, this.dropdown).empty();
     };
 
     GitLabDropdown.prototype.renderItem = function(data, group, index) {
@@ -651,18 +656,14 @@
         isMarking = false;
         el.removeClass(ACTIVE_CLASS);
         if (field && field.length) {
-          if (isInput) {
-            field.val('');
-          } else {
-            field.remove();
-          }
+          this.clearField(field, isInput);
         }
       } else if (el.hasClass(INDETERMINATE_CLASS)) {
         isMarking = true;
         el.addClass(ACTIVE_CLASS);
         el.removeClass(INDETERMINATE_CLASS);
         if (field && field.length && value == null) {
-          field.remove();
+          this.clearField(field, isInput);
         }
         if ((!field || !field.length) && fieldName) {
           this.addInput(fieldName, value, selectedObject);
@@ -676,7 +677,7 @@
           }
         }
         if (field && field.length && value == null) {
-          field.remove();
+          this.clearField(field, isInput);
         }
         // Toggle active class for the tick mark
         el.addClass(ACTIVE_CLASS);
@@ -826,6 +827,10 @@
       return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
     };
 
+    GitLabDropdown.prototype.clearField = function(field, isInput) {
+      return isInput ? field.val('') : field.remove();
+    };
+
     return GitLabDropdown;
   })();
 
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
deleted file mode 100644
index 08b2494f3dfcc032553e861d01f40b08049cbc09..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/gl_form.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
-/* global GitLab */
-/* global DropzoneInput */
-/* global autosize */
-
-(function() {
-  this.GLForm = (function() {
-    function GLForm(form) {
-      this.form = form;
-      this.textarea = this.form.find('textarea.js-gfm-input');
-      // Before we start, we should clean up any previous data for this form
-      this.destroy();
-      // Setup the form
-      this.setupForm();
-      this.form.data('gl-form', this);
-    }
-
-    GLForm.prototype.destroy = function() {
-      // Clean form listeners
-      this.clearEventListeners();
-      return this.form.data('gl-form', null);
-    };
-
-    GLForm.prototype.setupForm = function() {
-      var isNewForm;
-      isNewForm = this.form.is(':not(.gfm-form)');
-      this.form.removeClass('js-new-note-form');
-      if (isNewForm) {
-        this.form.find('.div-dropzone').remove();
-        this.form.addClass('gfm-form');
-        // remove notify commit author checkbox for non-commit notes
-        gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
-        gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
-        new DropzoneInput(this.form);
-        autosize(this.textarea);
-        // form and textarea event listeners
-        this.addEventListeners();
-      }
-      gl.text.init(this.form);
-      // hide discard button
-      this.form.find('.js-note-discard').hide();
-      return this.form.show();
-    };
-
-    GLForm.prototype.clearEventListeners = function() {
-      this.textarea.off('focus');
-      this.textarea.off('blur');
-      return gl.text.removeListeners(this.form);
-    };
-
-    GLForm.prototype.addEventListeners = function() {
-      this.textarea.on('focus', function() {
-        return $(this).closest('.md-area').addClass('is-focused');
-      });
-      return this.textarea.on('blur', function() {
-        return $(this).closest('.md-area').removeClass('is-focused');
-      });
-    };
-
-    return GLForm;
-  })();
-}).call(this);
diff --git a/app/assets/javascripts/gl_form.js.es6 b/app/assets/javascripts/gl_form.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..0b446ff364a4fba41ca403a2ef509d3c2d0d27bd
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js.es6
@@ -0,0 +1,92 @@
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
+/* global GitLab */
+/* global DropzoneInput */
+/* global autosize */
+
+(() => {
+  const global = window.gl || (window.gl = {});
+
+  function GLForm(form) {
+    this.form = form;
+    this.textarea = this.form.find('textarea.js-gfm-input');
+    // Before we start, we should clean up any previous data for this form
+    this.destroy();
+    // Setup the form
+    this.setupForm();
+    this.form.data('gl-form', this);
+  }
+
+  GLForm.prototype.destroy = function() {
+    // Clean form listeners
+    this.clearEventListeners();
+    return this.form.data('gl-form', null);
+  };
+
+  GLForm.prototype.setupForm = function() {
+    var isNewForm;
+    isNewForm = this.form.is(':not(.gfm-form)');
+    this.form.removeClass('js-new-note-form');
+    if (isNewForm) {
+      this.form.find('.div-dropzone').remove();
+      this.form.addClass('gfm-form');
+      // remove notify commit author checkbox for non-commit notes
+      gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+      gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
+      new DropzoneInput(this.form);
+      autosize(this.textarea);
+      // form and textarea event listeners
+      this.addEventListeners();
+    }
+    gl.text.init(this.form);
+    // hide discard button
+    this.form.find('.js-note-discard').hide();
+    this.form.show();
+    if (this.isAutosizeable) this.setupAutosize();
+  };
+
+  GLForm.prototype.setupAutosize = function () {
+    this.textarea.off('autosize:resized')
+      .on('autosize:resized', this.setHeightData.bind(this));
+
+    this.textarea.off('mouseup.autosize')
+      .on('mouseup.autosize', this.destroyAutosize.bind(this));
+
+    setTimeout(() => {
+      autosize(this.textarea);
+      this.textarea.css('resize', 'vertical');
+    }, 0);
+  };
+
+  GLForm.prototype.setHeightData = function () {
+    this.textarea.data('height', this.textarea.outerHeight());
+  };
+
+  GLForm.prototype.destroyAutosize = function () {
+    const outerHeight = this.textarea.outerHeight();
+
+    if (this.textarea.data('height') === outerHeight) return;
+
+    autosize.destroy(this.textarea);
+
+    this.textarea.data('height', outerHeight);
+    this.textarea.outerHeight(outerHeight);
+    this.textarea.css('max-height', window.outerHeight);
+  };
+
+  GLForm.prototype.clearEventListeners = function() {
+    this.textarea.off('focus');
+    this.textarea.off('blur');
+    return gl.text.removeListeners(this.form);
+  };
+
+  GLForm.prototype.addEventListeners = function() {
+    this.textarea.on('focus', function() {
+      return $(this).closest('.md-area').addClass('is-focused');
+    });
+    return this.textarea.on('blur', function() {
+      return $(this).closest('.md-area').removeClass('is-focused');
+    });
+  };
+
+  global.GLForm = GLForm;
+})();
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index 5247b2a08f7d7ea27e7f1c02faba913789abd77d..10dfd05fe3c4e85ba455e279d48fc9fe3d7cd085 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -2,12 +2,12 @@
 (function() {
   this.GroupAvatar = (function() {
     function GroupAvatar() {
-      $('.js-choose-group-avatar-button').bind("click", function() {
+      $('.js-choose-group-avatar-button').on("click", function() {
         var form;
         form = $(this).closest("form");
         return form.find(".js-group-avatar-input").click();
       });
-      $('.js-group-avatar-input').bind("change", function() {
+      $('.js-group-avatar-input').on("change", function() {
         var filename, form;
         form = $(this).closest("form");
         filename = $(this).val().replace(/^.*[\\\/]/, '');
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index a50bc4a905762592126631f9b6c565dc1cb68adf..bc88dc2d092f986d6e75bb392f460177973fce32 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -59,11 +59,11 @@
       } else {
         avatar = gon.default_avatar_url;
       }
-      return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
+      return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
     };
 
     GroupsSelect.prototype.formatSelection = function(group) {
-      return group.name;
+      return group.full_name;
     };
 
     return GroupsSelect;
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 9c53cdee58e5b9e842cbb8a1c61144f59f20a709..c77fbb6a1c7834783741d73a07043de45f804045 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
 /* global UsersSelect */
+/* global Cookies */
+/* global bp */
 
 (function() {
   this.IssuableContext = (function() {
@@ -37,6 +39,13 @@
           }, 0);
         }
       });
+      window.addEventListener('beforeunload', function() {
+        // collapsed_gutter cookie hides the sidebar
+        var bpBreakpoint = bp.getBreakpointSize();
+        if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
+          Cookies.set('collapsed_gutter', true);
+        }
+      });
       $(".right-sidebar").niceScroll();
     }
 
diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6
index c260ad03d473a4013153afc03436bea30583e1d2..e0ebd36a65ce807c434c5dab0d4c62584be79156 100644
--- a/app/assets/javascripts/issues_bulk_assignment.js.es6
+++ b/app/assets/javascripts/issues_bulk_assignment.js.es6
@@ -61,7 +61,6 @@
       return labels;
     }
 
-
     /**
      * Will return only labels that were marked previously and the user has unmarked
      * @return {Array} Label IDs
@@ -80,7 +79,6 @@
       return result;
     }
 
-
     /**
      * Simple form serialization, it will return just what we need
      * Returns key/value pairs from form data
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index 8f48b1f57cefce0f440a83a07629d0d041e09b17..2a50b72c8aae4df97e58957d081f4d65b18c2a8a 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
@@ -8,6 +8,7 @@
       this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
       this.otherLabels = otherLabels || $('.js-other-labels');
       this.errorMessage = 'Unable to update label prioritization at this time';
+      this.emptyState = document.querySelector('#js-priority-labels-empty-state');
       this.prioritizedLabels.sortable({
         items: 'li',
         placeholder: 'list-placeholder',
@@ -29,7 +30,12 @@
       const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
       const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
       $tooltip.tooltip('destroy');
-      return _this.toggleLabelPriority($label, action);
+      _this.toggleLabelPriority($label, action);
+      _this.toggleEmptyState($label, $btn, action);
+    }
+
+    toggleEmptyState($label, $btn, action) {
+      this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
     }
 
     toggleLabelPriority($label, action, persistState) {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index fd1e229e30a1dbd4d51b6a305fbe33b91d7b569b..70dc0d06b7b0311729086436d07782ae20124f9d 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -336,7 +336,11 @@
                 .removeClass('is-active');
             }
 
-            if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
+            if ($dropdown.hasClass('js-issuable-form-dropdown')) {
+              return;
+            }
+
+            if ($dropdown.hasClass('js-filter-bulk-update')) {
               _this.enableBulkLabelDropdown();
               _this.setDropdownData($dropdown, isMarking, this.id(label));
               return;
diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js
index 4cdf99cae72578e74b28e564563a5db555039fa8..9cdc03095030d556d00440c7f7fc9da66a377de7 100644
--- a/app/assets/javascripts/lib/ace.js
+++ b/app/assets/javascripts/lib/ace.js
@@ -1,2 +1,3 @@
-/*= require ace-rails-ap */
+/*= require ace/ace */
 /*= require ace/ext-searchbox */
+/*= require ./ace/ace_config_paths */
diff --git a/app/assets/javascripts/lib/ace/ace_config_paths.js.erb b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..25e623f0fdc98ff2d47e768c8179a0e4fed2b414
--- /dev/null
+++ b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb
@@ -0,0 +1,25 @@
+<%
+ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path
+ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file|
+  File.basename(file, '.js').sub(/^worker-/, '')
+end
+ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file|
+  File.basename(file, '.js').sub(/^mode-/, '')
+end
+%>
+
+(function() {
+  window.gon = window.gon || {};
+  var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
+  ace.config.set('basePath', basePath);
+
+  // configure paths for all worker modules
+<% ace_workers.each do |worker| %>
+  ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js');
+<% end %>
+
+  // configure paths for all mode modules
+<% ace_modes.each do |mode| %>
+  ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js');
+<% end %>
+})();
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 6d57d31f380b42f719e631ab1b7008b85e24db5f..e3bff2559fd0a81b13bd84c8c0d4d36121025450 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -159,5 +159,76 @@
       if (!results[2]) return '';
       return decodeURIComponent(results[2].replace(/\+/g, ' '));
     };
+
+    w.gl.utils.getSelectedFragment = () => {
+      const selection = window.getSelection();
+      if (selection.rangeCount === 0) return null;
+      const documentFragment = selection.getRangeAt(0).cloneContents();
+      if (documentFragment.textContent.length === 0) return null;
+
+      return documentFragment;
+    };
+
+    w.gl.utils.insertText = (target, text) => {
+      // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
+
+      const selectionStart = target.selectionStart;
+      const selectionEnd = target.selectionEnd;
+      const value = target.value;
+
+      const textBefore = value.substring(0, selectionStart);
+      const textAfter = value.substring(selectionEnd, value.length);
+      const newText = textBefore + text + textAfter;
+
+      target.value = newText;
+      target.selectionStart = target.selectionEnd = selectionStart + text.length;
+
+      // Trigger autosave
+      $(target).trigger('input');
+
+      // Trigger autosize
+      var event = document.createEvent('Event');
+      event.initEvent('autosize:update', true, false);
+      target.dispatchEvent(event);
+    };
+
+    w.gl.utils.nodeMatchesSelector = (node, selector) => {
+      const matches = Element.prototype.matches ||
+        Element.prototype.matchesSelector ||
+        Element.prototype.mozMatchesSelector ||
+        Element.prototype.msMatchesSelector ||
+        Element.prototype.oMatchesSelector ||
+        Element.prototype.webkitMatchesSelector;
+
+      if (matches) {
+        return matches.call(node, selector);
+      }
+
+      // IE11 doesn't support `node.matches(selector)`
+
+      let parentNode = node.parentNode;
+      if (!parentNode) {
+        parentNode = document.createElement('div');
+        node = node.cloneNode(true);
+        parentNode.appendChild(node);
+      }
+
+      const matchingNodes = parentNode.querySelectorAll(selector);
+      return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
+    };
+
+    /**
+      this will take in the headers from an API response and normalize them
+      this way we don't run into production issues when nginx gives us lowercased header keys
+    */
+    w.gl.utils.normalizeHeaders = (headers) => {
+      const upperCaseHeaders = {};
+
+      Object.keys(headers).forEach((e) => {
+        upperCaseHeaders[e.toUpperCase()] = headers[e];
+      });
+
+      return upperCaseHeaders;
+    };
   })(window);
 }).call(this);
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 4620715a5210d69d94c6840217274e5f2a18ae07..2f147704c222f9bd877ec82d96555804549094ec 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -74,8 +74,9 @@
       // If not done this way, the line number anchor will sometimes keep its
       // active state even when the event is cancelled, resulting in an ugly border
       // around the link and/or a persisted underline text decoration.
-      return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
-        return event.preventDefault();
+      $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
+        event.preventDefault();
+        event.stopPropagation();
       });
     };
 
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 09ee8dbe9d7aa1a33f31cc11f8d0614113a22b82..37af422a09e512b9334c17b9e52c9746964b55b5 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -110,9 +110,8 @@
     };
 
     MergeRequest.prototype.initCommitMessageListeners = function() {
-      var textarea = $('textarea.js-commit-message');
-
-      $('a.js-with-description-link').on('click', function(e) {
+      $(document).on('click', 'a.js-with-description-link', function(e) {
+        var textarea = $('textarea.js-commit-message');
         e.preventDefault();
 
         textarea.val(textarea.data('messageWithDescription'));
@@ -120,7 +119,8 @@
         $('p.js-without-description-hint').show();
       });
 
-      $('a.js-without-description-link').on('click', function(e) {
+      $(document).on('click', 'a.js-without-description-link', function(e) {
+        var textarea = $('textarea.js-commit-message');
         e.preventDefault();
 
         textarea.val(textarea.data('messageWithoutDescription'));
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 7a315e43667a08a55cde84065902981a9abb4493..fa782ebbedf16480c52276c127dbf644e3a4545b 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -126,7 +126,9 @@
 
     MergeRequestWidget.prototype.getMergeStatus = function() {
       return $.get(this.opts.merge_check_url, function(data) {
-        return $('.mr-state-widget').replaceWith(data);
+        var $html = $(data);
+        $('.mr-widget-body').replaceWith($html.find('.mr-widget-body'));
+        $('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer'));
       });
     };
 
@@ -152,12 +154,22 @@
             return;
           }
           if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
-          if (data.status !== _this.opts.ci_status && (data.status != null)) {
+          if (data.status !== _this.opts.ci_status ||
+              data.sha !== _this.opts.ci_sha ||
+              data.pipeline !== _this.opts.ci_pipeline) {
             _this.opts.ci_status = data.status;
             _this.showCIStatus(data.status);
             if (data.coverage) {
               _this.showCICoverage(data.coverage);
             }
+            if (data.pipeline) {
+              _this.opts.ci_pipeline = data.pipeline;
+              _this.updatePipelineUrls(data.pipeline);
+            }
+            if (data.sha) {
+              _this.opts.ci_sha = data.sha;
+              _this.updateCommitUrls(data.sha);
+            }
             if (showNotification) {
               status = _this.ciLabelForStatus(data.status);
               if (status === "preparing") {
@@ -246,6 +258,16 @@
       return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
     };
 
+    MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
+      const pipelineUrl = this.opts.pipeline_path;
+      $('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/'));
+    };
+
+    MergeRequestWidget.prototype.updateCommitUrls = function(id) {
+      const commitsUrl = this.opts.commits_path;
+      $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/'));
+    };
+
     return MergeRequestWidget;
   })();
 })(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
index 2b074994b4ab9029f6e201d7e5981aa3f5556158..5969d2ba56b8fa87d20ce5f869cd4388cd990117 100644
--- a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
+++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
@@ -8,31 +8,42 @@
      * temporarily.
      * */
 
-    if ($('.accept-mr-form').length) {
-      $('.accept-mr-form').on('ajax:send', () => {
-        $('.accept-mr-form :input').disable();
-      });
+    $(document)
+    .off('ajax:send', '.accept-mr-form')
+    .on('ajax:send', '.accept-mr-form', () => {
+      $('.accept-mr-form :input').disable();
+    });
 
-      $('.accept_merge_request').on('click', () => {
-        $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
-      });
+    $(document)
+    .off('click', '.accept_merge_request')
+    .on('click', '.accept_merge_request', () => {
+      $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
+    });
 
-      $('.merge_when_build_succeeds').on('click', () => {
-        $('#merge_when_build_succeeds').val('1');
-      });
+    $(document)
+    .off('click', '.merge_when_build_succeeds')
+    .on('click', '.merge_when_build_succeeds', () => {
+      $('#merge_when_build_succeeds').val('1');
+    });
 
-      $('.js-merge-dropdown a').on('click', (e) => {
-        e.preventDefault();
-        $(this).closest('form').submit();
-      });
-    } else if ($('.rebase-in-progress').length) {
+    $(document)
+    .off('click', '.js-merge-dropdown a')
+    .on('click', '.js-merge-dropdown a', (e) => {
+      e.preventDefault();
+      $(e.target).closest('form').submit();
+    });
+    if ($('.rebase-in-progress').length) {
       merge_request_widget.rebaseInProgress();
     } else if ($('.rebase-mr-form').length) {
-      $('.rebase-mr-form').on('ajax:send', () => {
+      $(document)
+      .off('ajax:send', '.rebase-mr-form')
+      .on('ajax:send', '.rebase-mr-form', () => {
         $('.rebase-mr-form :input').disable();
       });
 
-      $('.js-rebase-button').on('click', () => {
+      $(document)
+      .off('click', '.js-rebase-button')
+      .on('click', '.js-rebase-button', () => {
         $('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress");
       });
     } else {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 06a72efa21dd68aad78b5850e431302115cd8f39..c4722be362558efe74c7c47ae96e54794d31327a 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,6 +1,5 @@
 /* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, 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, 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, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
 /* global Flash */
-/* global GLForm */
 /* global Autosave */
 /* global ResolveService */
 /* global mrRefreshWidgetUrl */
@@ -220,7 +219,6 @@
       })(this));
     };
 
-
     /*
     Increase @pollingInterval up to 120 seconds on every function call,
     if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
@@ -244,7 +242,6 @@
       return this.initRefresh();
     };
 
-
     Notes.prototype.handleCreateChanges = function(note) {
       if (typeof note === 'undefined') {
         return;
@@ -294,7 +291,6 @@
       }
     };
 
-
     /*
     Check if note does not exists on page
      */
@@ -307,7 +303,6 @@
       return this.view === 'parallel';
     };
 
-
     /*
     Render note in discussion area.
 
@@ -358,7 +353,6 @@
       return this.updateNotesCount(1);
     };
 
-
     /*
     Called in response the main target form has been successfully submitted.
 
@@ -390,7 +384,6 @@
       return form.find(".js-note-text").trigger("input");
     };
 
-
     /*
     Shows the main form and does some setup on it.
 
@@ -415,7 +408,6 @@
       return this.parentTimeline = form.parents('.timeline');
     };
 
-
     /*
     General note form setup.
 
@@ -427,12 +419,11 @@
 
     Notes.prototype.setupNoteForm = function(form) {
       var textarea;
-      new GLForm(form);
+      new gl.GLForm(form);
       textarea = form.find(".js-note-text");
       return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
     };
 
-
     /*
     Called in response to the new note form being submitted
 
@@ -448,7 +439,6 @@
       return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
     };
 
-
     /*
     Called in response to the new note form being submitted
 
@@ -473,7 +463,6 @@
       this.removeDiscussionNoteForm($form);
     };
 
-
     /*
     Called in response to the edit note form being submitted
 
@@ -498,7 +487,6 @@
       }
     };
 
-
     Notes.prototype.checkContentToAllowEditing = function($el) {
       var initialContent = $el.find('.original-note-content').text().trim();
       var currentContent = $el.find('.note-textarea').val();
@@ -522,7 +510,6 @@
       return isAllowed;
     };
 
-
     /*
     Called in response to clicking the edit note link
 
@@ -551,7 +538,6 @@
       this.putEditFormInPlace($target);
     };
 
-
     /*
     Called in response to clicking the edit note link
 
@@ -596,7 +582,6 @@
       return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
     };
 
-
     /*
     Called in response to deleting a note of any kind.
 
@@ -636,7 +621,6 @@
       return this.updateNotesCount(-1);
     };
 
-
     /*
     Called in response to clicking the delete attachment link
 
@@ -653,7 +637,6 @@
       return note.find(".current-note-edit-form").remove();
     };
 
-
     /*
     Called when clicking on the "reply" button for a diff line.
 
@@ -673,7 +656,6 @@
       return this.setupDiscussionNoteForm(replyLink, form);
     };
 
-
     /*
     Shows the diff or discussion form and does some setup on it.
 
@@ -715,7 +697,6 @@
         .addClass("discussion-form js-discussion-note-form");
     };
 
-
     /*
     Called when clicking on the "add a comment" button on the side of a diff line.
 
@@ -772,7 +753,6 @@
       }
     };
 
-
     /*
     Called in response to "cancel" on a diff note form.
 
@@ -806,7 +786,6 @@
       return this.removeDiscussionNoteForm(form);
     };
 
-
     /*
     Called after an attachment file has been selected.
 
@@ -821,7 +800,6 @@
       return form.find(".js-attachment-filename").text(filename);
     };
 
-
     /*
     Called when the tab visibility changes
      */
@@ -905,7 +883,7 @@
       var targetId = $originalContentEl.data('target-id');
       var targetType = $originalContentEl.data('target-type');
 
-      new GLForm($editForm.find('form'));
+      new gl.GLForm($editForm.find('form'));
 
       $editForm.find('form')
         .attr('action', postUrl)
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6
index 6dbaae25f2aada79845dab042ab14e29249d98ac..5aec9c813fe3710c70f70a17a5c334a106beabcc 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js.es6
@@ -36,6 +36,7 @@
     }
 
     onSubmitForm(e) {
+      e.preventDefault();
       return this.saveForm();
     }
 
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 7cf630a1d76e77286c6d3eda0595910d46d3ed6f..67f8804666d6ce498f65596ad189a858d382c7d6 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -58,6 +58,11 @@
     };
 
     Project.prototype.initRefSwitcher = function() {
+      var refListItem = document.createElement('li'),
+          refLink = document.createElement('a');
+
+      refLink.href = '#';
+
       return $('.js-project-refs-dropdown').each(function() {
         var $dropdown, selected;
         $dropdown = $(this);
@@ -67,7 +72,8 @@
             return $.ajax({
               url: $dropdown.data('refs-url'),
               data: {
-                ref: $dropdown.data('ref')
+                ref: $dropdown.data('ref'),
+                search: term
               },
               dataType: "json"
             }).done(function(refs) {
@@ -76,16 +82,29 @@
           },
           selectable: true,
           filterable: true,
+          filterRemote: true,
           filterByText: true,
           fieldName: $dropdown.data('field-name'),
           renderRow: function(ref) {
-            var link;
+            var li = refListItem.cloneNode(false);
+
             if (ref.header != null) {
-              return $('<li />').addClass('dropdown-header').text(ref.header);
+              li.className = 'dropdown-header';
+              li.textContent = ref.header;
             } else {
-              link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref);
-              return $('<li />').append(link);
+              var link = refLink.cloneNode(false);
+
+              if (ref === selected) {
+                link.className = 'is-active';
+              }
+
+              link.textContent = ref;
+              link.dataset.ref = ref;
+
+              li.appendChild(link);
             }
+
+            return li;
           },
           id: function(obj, $el) {
             return $el.attr('data-ref');
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
index 03f4531abf5212bc2475dab219c16bbadcf0a5b7..5cf28aa7a734bd068e139fbbd1f53aa6a1364487 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
@@ -49,7 +49,7 @@ class ProtectedBranchDropdown {
   onClickCreateWildcard() {
     // Refresh the dropdown's data, which ends up calling `getProtectedBranches`
     this.$dropdown.data('glDropdown').remote.execute();
-    this.$dropdown.data('glDropdown').selectRowAtIndex(0);
+    this.$dropdown.data('glDropdown').selectRowAtIndex();
   }
 
   getProtectedBranches(term, callback) {
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 489e567259cea0decc69d1008a94c8898fdce865..b1c0dc37b4d1159237c9c8459bfb27e95507cfaa 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -13,12 +13,12 @@
         filterable: true,
         fieldName: 'group_id',
         search: {
-          fields: ['name']
+          fields: ['full_name']
         },
         data: function(term, callback) {
           return Api.groups(term, {}, function(data) {
             data.unshift({
-              name: 'Any'
+              full_name: 'Any'
             });
             data.splice(1, 0, 'divider');
             return callback(data);
@@ -28,10 +28,10 @@
           return obj.id;
         },
         text: function(obj) {
-          return obj.name;
+          return obj.full_name;
         },
         toggleLabel: function(obj) {
-          return ($groupDropdown.data('default-label')) + " " + obj.name;
+          return ($groupDropdown.data('default-label')) + " " + obj.full_name;
         },
         clicked: (function(_this) {
           return function() {
diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6
index 480755899fb73d6094094cd245ffcdadf461fdd3..6250e75d407e76aac8e9f8d13f611524195c06f7 100644
--- a/app/assets/javascripts/search_autocomplete.js.es6
+++ b/app/assets/javascripts/search_autocomplete.js.es6
@@ -69,12 +69,17 @@
         search: {
           fields: ['text']
         },
+        id: this.getSearchText,
         data: this.getData.bind(this),
         selectable: true,
         clicked: this.onClick.bind(this)
       });
     }
 
+    getSearchText(selectedObject, el) {
+      return selectedObject.id ? selectedObject.text : '';
+    }
+
     getData(term, callback) {
       var _this, contents, jqXHR;
       _this = this;
@@ -364,7 +369,7 @@
 
     onClick(item, $el, e) {
       if (location.pathname.indexOf(item.url) !== -1) {
-        e.preventDefault();
+        if (!e.metaKey) e.preventDefault();
         if (!this.badgePresent) {
           if (item.category === 'Projects') {
             this.projectInputEl.val(item.id);
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 6f919de3da7fd55bb6aaa8fbfe80251cf1eb54df..4dcc5ebe28f5615711701a1e7a382ef4b6224513 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -39,29 +39,42 @@
     }
 
     ShortcutsIssuable.prototype.replyWithSelectedText = function() {
-      var quote, replyField, selected, separator;
-      if (window.getSelection) {
-        selected = window.getSelection().toString();
-        replyField = $('.js-main-target-form #note_note');
-        if (selected.trim() === "") {
-          return;
-        }
-        // Put a '>' character before each non-empty line in the selection
-        quote = _.map(selected.split("\n"), function(val) {
-          if (val.trim() !== '') {
-            return "> " + val + "\n";
-          }
-        });
-        // If replyField already has some content, add a newline before our quote
-        separator = replyField.val().trim() !== "" && "\n" || '';
-        replyField.val(function(_, current) {
-          return current + separator + quote.join('') + "\n";
-        });
-        // Trigger autosave for the added text
-        replyField.trigger('input');
-        // Focus the input field
-        return replyField.focus();
+      var quote, documentFragment, selected, separator;
+      var replyField = $('.js-main-target-form #note_note');
+
+      documentFragment = window.gl.utils.getSelectedFragment();
+      if (!documentFragment) {
+        replyField.focus();
+        return;
+      }
+
+      // If the documentFragment contains more than just Markdown, don't copy as GFM.
+      if (documentFragment.querySelector('.md, .wiki')) return;
+
+      selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
+
+      if (selected.trim() === "") {
+        return;
       }
+      quote = _.map(selected.split("\n"), function(val) {
+        return ("> " + val).trim() + "\n";
+      });
+      // If replyField already has some content, add a newline before our quote
+      separator = replyField.val().trim() !== "" && "\n\n" || '';
+      replyField.val(function(_, current) {
+        return current + separator + quote.join('') + "\n";
+      });
+
+      // Trigger autosave
+      replyField.trigger('input');
+
+      // Trigger autosize
+      var event = document.createEvent('Event');
+      event.initEvent('autosize:update', true, false);
+      replyField.get(0).dispatchEvent(event);
+
+      // Focus the input field
+      return replyField.focus();
     };
 
     ShortcutsIssuable.prototype.editIssue = function() {
diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6
index ef9c0a885fba000bea056371822f9596d80a83ba..05622916ff8728491c674cf845cc6be01a687a68 100644
--- a/app/assets/javascripts/todos.js.es6
+++ b/app/assets/javascripts/todos.js.es6
@@ -85,7 +85,7 @@
         },
         success: (data) => {
           $target.remove();
-          $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
+          $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
           return this.updateBadges(data);
         }
       });
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 7ffc546ffc1d832236498e77d6ccf66526e4cee2..6e40dfdf3d88f620870550cbb479983ffa292fd6 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -1,6 +1,5 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
 /* global d3 */
-/* global dateFormat */
 
 (function() {
   var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -33,7 +32,7 @@
         date.setDate(date.getDate() + i);
 
         var day = date.getDay();
-        var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
+        var count = timestamps[date.format('yyyy-mm-dd')];
 
         // Create a new group array if this is the first day of the week
         // or if is first object
@@ -122,7 +121,7 @@
           if (stamp.count > 0) {
             contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
           }
-          dateText = dateFormat(date, 'mmm d, yyyy');
+          dateText = date.format('mmm d, yyyy');
           return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
         };
       })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
@@ -158,7 +157,7 @@
     };
 
     Calendar.prototype.renderMonths = function() {
-      return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
+      return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
         return date.x;
       }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
         return function(date) {
diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..1fa2b5ac3995ab1def54eda8755d167cf762aca1
--- /dev/null
+++ b/app/assets/javascripts/version_check_image.js.es6
@@ -0,0 +1,10 @@
+(() => {
+  class VersionCheckImage {
+    static bindErrorEvent(imageElement) {
+      imageElement.off('error').on('error', () => imageElement.hide());
+    }
+  }
+
+  window.gl = window.gl || {};
+  gl.VersionCheckImage = VersionCheckImage;
+})();
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
index ad5cb30cc42691d8d002281ff50b697a8ffcb55b..01f8b6519a410b7c91de9a3bdaa2d618d9da7e38 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -22,47 +22,49 @@
         <div class="controls pull-right">
           <div class="btn-group inline">
             <div class="btn-group">
-              <a
+              <button
                 v-if='actions'
-                class="dropdown-toggle btn btn-default js-pipeline-dropdown-manual-actions"
+                class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
                 data-toggle="dropdown"
-                title="Manual build"
-                alt="Manual Build"
+                title="Manual job"
+                data-placement="top"
+                aria-label="Manual job"
               >
-                <span v-html='svgs.iconPlay'></span>
-                <i class="fa fa-caret-down"></i>
-              </a>
+                <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+                <i class="fa fa-caret-down" aria-hidden="true"></i>
+              </button>
               <ul class="dropdown-menu dropdown-menu-align-right">
                 <li v-for='action in pipeline.details.manual_actions'>
                   <a
                     rel="nofollow"
                     data-method="post"
                     :href='action.path'
-                    title="Manual build"
                   >
-                    <span v-html='svgs.iconPlay'></span>
-                    <span title="Manual build">{{action.name}}</span>
+                    <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+                    <span>{{action.name}}</span>
                   </a>
                 </li>
               </ul>
             </div>
             <div class="btn-group">
-              <a
+              <button
                 v-if='artifacts'
-                class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download"
+                class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
                 data-toggle="dropdown"
-                type="button"
+                title="Artifacts"
+                data-placement="top"
+                aria-label="Artifacts"
               >
-                <i class="fa fa-download"></i>
-                <i class="fa fa-caret-down"></i>
-              </a>
+                <i class="fa fa-download" aria-hidden="true"></i>
+                <i class="fa fa-caret-down" aria-hidden="true"></i>
+              </button>
               <ul class="dropdown-menu dropdown-menu-align-right">
                 <li v-for='artifact in pipeline.details.artifacts'>
                   <a
                     rel="nofollow"
                     :href='artifact.path'
                   >
-                    <i class="fa fa-download"></i>
+                    <i class="fa fa-download" aria-hidden="true"></i>
                     <span>{{download(artifact.name)}}</span>
                   </a>
                 </li>
@@ -76,9 +78,12 @@
               title="Retry"
               rel="nofollow"
               data-method="post"
+              data-placement="top"
+              data-toggle="dropdown"
               :href='pipeline.retry_path'
+              aria-label="Retry"
             >
-              <i class="fa fa-repeat"></i>
+              <i class="fa fa-repeat" aria-hidden="true"></i>
             </a>
             <a
               v-if='pipeline.flags.cancelable'
@@ -86,10 +91,12 @@
               title="Cancel"
               rel="nofollow"
               data-method="post"
+              data-placement="top"
+              data-toggle="dropdown"
               :href='pipeline.cancel_path'
-              data-original-title="Cancel"
+              aria-label="Cancel"
             >
-              <i class="fa fa-remove"></i>
+              <i class="fa fa-remove" aria-hidden="true"></i>
             </a>
           </div>
         </div>
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
index 329731321740e1bfbc95e9edeacce304585d5312..496df9aaced041a6c1123c1dcebbfa810f9f5d10 100644
--- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
@@ -1,5 +1,5 @@
 /* global Vue, Flash, gl */
-/* eslint-disable no-param-reassign, no-bitwise */
+/* eslint-disable no-param-reassign */
 
 ((gl) => {
   gl.VueStage = Vue.extend({
@@ -9,7 +9,20 @@
         spinner: '<span class="fa fa-spinner fa-spin"></span>',
       };
     },
-    props: ['stage', 'svgs', 'match'],
+    props: {
+      stage: {
+        type: Object,
+        required: true,
+      },
+      svgs: {
+        type: DOMStringMap,
+        required: true,
+      },
+      match: {
+        type: Function,
+        required: true,
+      },
+    },
     methods: {
       fetchBuilds(e) {
         const areaExpanded = e.currentTarget.attributes['aria-expanded'];
@@ -24,6 +37,18 @@
             return flash;
           });
       },
+      keepGraph(e) {
+        const { target } = e;
+
+        if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
+
+        if (
+          target.parentElement &&
+          (target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
+        ) return null;
+
+        return e.stopPropagation();
+      },
     },
     computed: {
       buildsOrSpinner() {
@@ -57,14 +82,15 @@
           data-placement="top"
           data-toggle="dropdown"
           type="button"
+          :aria-label='stage.title'
         >
-          <span v-html="svg"></span>
-          <i class="fa fa-caret-down "></i>
+          <span v-html="svg" aria-hidden="true"></span>
+          <i class="fa fa-caret-down" aria-hidden="true"></i>
         </button>
         <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
-          <div class="arrow-up"></div>
+          <div class="arrow-up" aria-hidden="true"></div>
           <div
-            @click=''
+            @click='keepGraph($event)'
             :class="dropdownClass"
             class="js-builds-dropdown-list scrollable-menu"
             v-html="buildsOrSpinner"
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6
index 1982142853a6b2dbcd0c9d3e483886d9719f80c0..9e19b1564dc47e8175ca91b767871e396de1c1e7 100644
--- a/app/assets/javascripts/vue_pipelines_index/store.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6
@@ -4,19 +4,15 @@
 
 ((gl) => {
   const pageValues = (headers) => {
-    const normalizedHeaders = {};
-
-    Object.keys(headers).forEach((e) => {
-      normalizedHeaders[e.toUpperCase()] = headers[e];
-    });
+    const normalized = gl.utils.normalizeHeaders(headers);
 
     const paginationInfo = {
-      perPage: +normalizedHeaders['X-PER-PAGE'],
-      page: +normalizedHeaders['X-PAGE'],
-      total: +normalizedHeaders['X-TOTAL'],
-      totalPages: +normalizedHeaders['X-TOTAL-PAGES'],
-      nextPage: +normalizedHeaders['X-NEXT-PAGE'],
-      previousPage: +normalizedHeaders['X-PREV-PAGE'],
+      perPage: +normalized['X-PER-PAGE'],
+      page: +normalized['X-PAGE'],
+      total: +normalized['X-TOTAL'],
+      totalPages: +normalized['X-TOTAL-PAGES'],
+      nextPage: +normalized['X-NEXT-PAGE'],
+      previousPage: +normalized['X-PREV-PAGE'],
     };
 
     return paginationInfo;
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 8392b98f0a7cf44ba29e69adeeb45e922cb16897..1d59700543c2f928bba46225c05d6008c19ba8b0 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -37,6 +37,8 @@
     display: inline-block;
     margin-left: 4px;
     margin-bottom: 2px;
+    flex-shrink: 0;
+    -webkit-flex-shrink: 0;
 
     &.s16 { margin-right: 4px; }
     &.s24 { margin-right: 4px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 407c800feb7f1306ec9653ca6f2caa8acc3504b1..0f9213b98e35bd1dd53de1a6ab8e057146d1f3f1 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -82,7 +82,12 @@
   }
 
   .block-controls {
-    float: right;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: flex-end;
+    justify-content: flex-end;
+    -webkit-flex: 1;
+    flex: 1;
 
     .control {
       float: left;
@@ -273,6 +278,10 @@
     display: inline-block;
   }
 
+  .btn {
+    margin: $btn-side-margin $btn-side-margin 0 0;
+  }
+
   @media(max-width: $screen-xs-max) {
     margin-top: 50px;
     text-align: center;
@@ -281,4 +290,15 @@
       width: 100%;
     }
   }
+
+  @media(min-width: $screen-xs-max) {
+    &.labels .text-content {
+      margin-top: 70px;
+    }
+  }
+}
+
+.flex-container-block {
+  display: -webkit-flex;
+  display: flex;
 }
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index bb6129158d968c24b7afc3f774e7a6b5dde66789..cda46223492adfa7125515d756d4fe5948ec12f0 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -330,10 +330,6 @@
   }
 }
 
-.btn-file-option {
-  background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
-}
-
 .btn-build {
   margin-left: 10px;
 
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index ef921a8c6a94953c5b01eb65d323b56993bf6443..1d2d1bfc0d794c18a3c679fd32a44b04889a21e3 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,6 +1,7 @@
 .calender-block {
   padding-left: 0;
   padding-right: 0;
+  direction: rtl;
 
   @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
     overflow-x: scroll;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 755eddefa42ceee2b2b6eea091da85074aac84ef..6bfb9a6d1cb1e8fbf45b62d2a81235978846a569 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -125,7 +125,8 @@
   top: 100%;
   left: 0;
   z-index: 9;
-  width: 240px;
+  max-width: 280px;
+  min-width: 240px;
   margin-top: 2px;
   margin-bottom: 0;
   font-size: 14px;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index d957ec64654dba4a4cdfe79142693bd275a3eadd..e3da467a27c92325772c6f7fc52795ff9e96a02b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -79,6 +79,16 @@
   overflow: auto;
 }
 
+%filter-dropdown-item-btn-hover {
+  background-color: $dropdown-hover-color;
+  color: $white-light;
+  text-decoration: none;
+
+  .avatar {
+    border-color: $white-light;
+  }
+}
+
 .filter-dropdown-item {
   .btn {
     border: none;
@@ -103,13 +113,7 @@
 
     &:hover,
     &:focus {
-      background-color: $dropdown-hover-color;
-      color: $white-light;
-      text-decoration: none;
-
-      .avatar {
-        border-color: $white-light;
-      }
+      @extend %filter-dropdown-item-btn-hover;
     }
   }
 
@@ -128,11 +132,18 @@
     display: flex;
     -webkit-flex-direction: column;
     flex-direction: column;
+
+    &> span {
+      white-space: normal;
+      word-break: break-all;
+    }
   }
 }
 
-.hint-dropdown {
-  width: 250px;
+.filter-dropdown-item.dropdown-active {
+  .btn {
+    @extend %filter-dropdown-item-btn-hover;
+  }
 }
 
 .filter-dropdown-loading {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 24a1ce2b84d7471ede7cec9b85f9c42233c0fe6d..2a01bc4d44df8c9db0a07cdc690b62ac88dd14c0 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -71,7 +71,7 @@ header {
         &:focus,
         &:active {
           background-color: $gray-light;
-          color: darken($gl-text-color-secondary, 30%);
+          color: $gl-text-color;
 
           .todos-pending-count {
             background: darken($todo-alert-blue, 10%);
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index dccf5177e351338b5c6a5ca9714710878305ecbf..db8d231a82a9b2b6758e4a2a757ce6e530b1f951 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -15,6 +15,7 @@
 }
 
 .ci-status-icon-pending,
+.ci-status-icon-failed_with_warnings,
 .ci-status-icon-success_with_warnings {
   color: $gl-warning;
 
@@ -57,3 +58,9 @@
     fill: $gl-text-color;
   }
 }
+
+.icon-link {
+  &:hover {
+    text-decoration: none;
+  }
+}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 1c6698ad0c67c2e6e45abd559d8ff4bde80ed56b..426596027defb21255f70b97828ce8eb8faa6b85 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -155,7 +155,8 @@ ul.content-list {
       }
 
       > .btn,
-      > .btn-group {
+      > .btn-group,
+      > .dropdown.inline {
         margin-right: $gl-padding-top;
         display: inline-block;
         margin-top: 3px;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 401c2d0f6ee6f1b2259cd144e76eaff9d03455ba..fd081c2d7e16aee21268478ccfb53b2a1415701d 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -294,16 +294,18 @@
 
   .container-fluid {
     position: relative;
+
+    .nav-control {
+      @media (max-width: $screen-sm-max) {
+        margin-right: 75px;
+      }
+    }
   }
 
   .controls {
     float: right;
     padding: 7px 0 0;
 
-    @media (max-width: $screen-sm-max) {
-      display: none;
-    }
-
     i {
       color: $layout-link-gray;
     }
@@ -361,6 +363,7 @@
   .fade-left {
     @include fade(right, $gray-light);
     left: -5px;
+    text-align: center;
 
     .fa {
       left: -7px;
diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss
index 4decee2c525c5a0fc660d7f450adb3703d73d144..5f4211147f30fdf05edaecf7cf40443c6474febb 100644
--- a/app/assets/stylesheets/framework/page-header.scss
+++ b/app/assets/stylesheets/framework/page-header.scss
@@ -46,10 +46,6 @@
     font-weight: bold;
   }
 
-  .fa-clipboard {
-    color: $dropdown-title-btn-color;
-  }
-
   .commit-info {
     &.branches {
       margin-left: 8px;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 12d56359d7d4d8a8b6379d9a8049276c6616d41d..ea2d26dd5a00b968fde2755651b415bd59a67d5c 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -162,6 +162,10 @@
       }
     }
   }
+
+  &.panel-without-border {
+    border: 0;
+  }
 }
 
 .panel-succes .panel-heading,
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 07cb669a46e16b9ac04c2ec034abf5469ab82b9b..7809d4866f14ab6592db2f0b5819e412a64acca9 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5;
 $save-project-loader-color: #555;
 $divergence-graph-bar-bg: #ccc;
 $divergence-graph-separator-bg: #ccc;
-$general-hover-transition-duration: 150ms;
+$general-hover-transition-duration: 100ms;
 $general-hover-transition-curve: linear;
 
 
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index cb923166b25756057b917fd5b077b2ead5d110bf..6f2e746d4b01bb77084418de41f1a70072cced5f 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21;
 $dark-main-color: #1d1f21;
 $dark-line-color: #c5c8c6;
 $dark-line-num-color: rgba(255, 255, 255, 0.3);
+$dark-line-num-color-new: #627165;
+$dark-line-num-color-old: #806565;
 $dark-diff-not-empty-bg: #557;
 $dark-highlight-bg: #ffe792;
 $dark-highlight-color: $black;
@@ -89,7 +91,6 @@ $dark-il: #de935f;
 
   .diff-line-num,
   .diff-line-num a {
-    color: $dark-main-color;
     color: $dark-line-num-color;
   }
 
@@ -121,11 +122,21 @@ $dark-il: #de935f;
     .diff-line-num.new,
     .line_content.new {
       @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
+
+      &::before,
+      a {
+        color: $dark-line-num-color-new;
+      }
     }
 
     .diff-line-num.old,
     .line_content.old {
       @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
+
+      &::before,
+      a {
+        color: $dark-line-num-color-old;
+      }
     }
 
     .line_content.match {
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index d8510baad8a5fa9d8953b103dd97db97c6ff96d9..2144a5f746605bfa70a218776e0f35fa0ce29b05 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -7,6 +7,8 @@ $monokai-bg: #272822;
 $monokai-border: #555;
 $monokai-text-color: #f8f8f2;
 $monokai-line-num-color: rgba(255, 255, 255, 0.3);
+$monokai-line-num-color-new: #707565;
+$monokai-line-num-color-old: #7e736f;
 $monokai-line-empty-bg: #49483e;
 $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
 $monokai-diff-border: #808080;
@@ -120,11 +122,21 @@ $monokai-gi: #a6e22e;
     .diff-line-num.new,
     .line_content.new {
       @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
+
+      &::before,
+      a {
+        color: $monokai-line-num-color-new;
+      }
     }
 
     .diff-line-num.old,
     .line_content.old {
       @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
+
+      &::before,
+      a {
+        color: $monokai-line-num-color-old;
+      }
     }
 
     .line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 874aecb5e16945e4c257d2e384344f59657f6f82..2cb1d18f12f55f384783ced977f8e333273ce0be 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -13,6 +13,8 @@ $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-line-color-new: #5a766c;
+$solarized-dark-line-color-old: #7a6c71;
 $solarized-dark-highlight: #094554;
 $solarized-dark-hll-bg: #174652;
 $solarized-dark-c: #586e75;
@@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198;
     .diff-line-num.new,
     .line_content.new {
       @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
+
+      &::before,
+      a {
+        color: $solarized-dark-line-color-new;
+      }
     }
 
     .diff-line-num.old,
     .line_content.old {
       @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
+
+      &::before,
+      a {
+        color: $solarized-dark-line-color-old;
+      }
     }
 
     .line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 499a1c108b829da6ab186dfcd0329b7aadfe7ae5..b72c432673003cdb0fc4d04fdbbc57db1b051184 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36;
 $solarized-light-pre-bg: #fdf6e3;
 $solarized-light-pre-color: #586e75;
 $solarized-light-line-bg: #fdf6e3;
+$solarized-light-line-color: rgba(0, 0, 0, 0.3);
+$solarized-light-line-color-new: #a1a080;
+$solarized-light-line-color-old: #ad9186;
 $solarized-light-highlight: #eee8d5;
 $solarized-light-hll-bg: #ddd8c5;
 $solarized-light-c: #93a1a1;
@@ -98,7 +101,7 @@ $solarized-light-il: #2aa198;
 
   .diff-line-num,
   .diff-line-num a {
-    color: $black-transparent;
+    color: $solarized-light-line-color;
   }
 
   // Code itself
@@ -130,11 +133,21 @@ $solarized-light-il: #2aa198;
     .line_content.new {
       @include diff_background($solarized-light-new-bg,
       $solarized-light-new-idiff, $solarized-light-border);
+
+      &::before,
+      a {
+        color: $solarized-light-line-color-new;
+      }
     }
 
     .diff-line-num.old,
     .line_content.old {
       @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
+
+      &::before,
+      a {
+        color: $solarized-light-line-color-old;
+      }
     }
 
     .line_content.match {
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index b425c78e0d56e01fddcf084b06bd7296881d93e6..398fbfd3b18bad37ee78bd53329180705002e8ca 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5;
       &.old {
         background-color: $line-number-old;
         border-color: $line-removed-dark;
+
+        a {
+          color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+        }
       }
 
       &.new {
         background-color: $line-number-new;
         border-color: $line-added-dark;
+
+        a {
+          color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+        }
       }
 
       &.hll:not(.empty-cell) {
@@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5;
       &.old {
         background-color: $line-removed;
 
+        &::before {
+          color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+        }
+
         span.idiff {
           background-color: $line-removed-dark;
         }
@@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5;
       &.new {
         background-color: $line-added;
 
+        &::before {
+          color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+        }
+
         span.idiff {
           background-color: $line-added-dark;
         }
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 324c6cec96aa2866f64a6426c065f9af92532d1e..4ef95d27f4ffe850634e0ad2b69049eb5611c0d7 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -26,10 +26,6 @@
           border: 0;
         }
       }
-
-      .container-fluid {
-        @extend .fixed-width-container;
-      }
     }
   }
 
@@ -377,6 +373,10 @@
   display: inline-block;
   padding: 5px;
 
+  &:nth-of-type(7n) {
+    padding-right: 0;
+  }
+
   .author_link {
     display: block;
   }
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 45ff9f7ff5f34343bff8f0625270953273a680a0..0c013915a63bba160dc794780aeb78e90daa4f98 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -56,15 +56,24 @@
       &.right {
         float: right;
         padding-right: 0;
+      }
 
-        a {
-          color: $gl-text-color;
-        }
+      .modify-merge-commit-link {
+        color: $gl-text-color;
       }
 
-      .remove_source_checkbox {
+      .merge-param-checkbox {
         margin: 0;
       }
+
+      a .fa-question-circle {
+        color: $gl-text-color-secondary;
+
+        &:hover,
+        &:focus {
+          color: $link-hover-color;
+        }
+      }
     }
   }
 
@@ -420,10 +429,6 @@
 .merge-request-tabs-holder {
   background-color: $white-light;
 
-  .container-limited {
-    max-width: $limited-layout-width;
-  }
-
   &.affix {
     top: 100px;
     left: 0;
@@ -433,10 +438,26 @@
     @media (max-width: $screen-xs-max) {
       right: 0;
     }
+
+    .merge-request-tabs-container {
+      padding-left: $gl-padding;
+      padding-right: $gl-padding;
+    }
   }
+}
 
-  &:not(.affix) .container-fluid {
-    padding-left: 0;
-    padding-right: 0;
+.limit-container-width {
+  .merge-request-tabs-container {
+    max-width: $limited-layout-width;
+    margin-left: auto;
+    margin-right: auto;
+  }
+}
+
+.limit-container-width:not(.container-limited) {
+  .merge-request-tabs-holder:not(.affix) {
+    .merge-request-tabs-container {
+      max-width: $limited-layout-width - ($gl-padding * 2);
+    }
   }
 }
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index cbe38b60d60b42945153be153443af3e3e33af15..f310cc72da0e8c9d87e5b6322c97a68cef76697e 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -195,10 +195,10 @@ ul.notes {
     }
 
     .note-body {
-      overflow: auto;
+      overflow-x: auto;
+      overflow-y: hidden;
 
       .note-text {
-        overflow: auto;
         word-wrap: break-word;
         @include md-typography;
         // Reset ul style types since we're nested inside a ul already
@@ -467,7 +467,7 @@ ul.notes {
   }
 
   .add-diff-note {
-    margin-top: -4px;
+    margin-top: -8px;
     border-radius: 40px;
     background: $white-light;
     padding: 4px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 8dff22e32bda1b8d76a3b009302b08eb43c6c5c7..367a468e1bafe1ec88962fd4aa0b7fd2faf6b826 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -201,7 +201,12 @@
     .stage-container {
       display: inline-block;
       position: relative;
-      margin-right: 6px;
+      height: 22px;
+      margin: 3px 6px 3px 0;
+
+      .tooltip {
+        white-space: nowrap;
+      }
 
       .tooltip-inner {
         padding: 3px 4px;
@@ -210,9 +215,9 @@
       &:not(:last-child) {
         &::after {
           content: '';
-          width: 8px;
+          width: 7px;
           position: absolute;
-          right: -8px;
+          right: -7px;
           top: 10px;
           border-bottom: 2px solid $border-color;
         }
@@ -288,6 +293,10 @@
         }
       }
     }
+
+    .tooltip {
+      white-space: nowrap;
+    }
   }
 
   .build-link {
@@ -486,31 +495,27 @@
 
     // Action Icons in big pipeline-graph nodes
     > .ci-action-icon-container .ci-action-icon-wrapper {
-      i {
-        color: $border-color;
-        border-radius: 100%;
-        border: 1px solid $border-color;
-        padding: 5px 6px;
-        font-size: 13px;
-        background: $white-light;
-        height: 30px;
-        width: 30px;
-
-        &::before {
-          position: relative;
-          top: 3px;
-          left: 3px;
-        }
+      height: 30px;
+      width: 30px;
+      background: $white-light;
+      border: 1px solid $border-color;
+      border-radius: 100%;
+      display: block;
 
-        &:hover {
-          color: $gl-text-color;
-          background-color: $stage-hover-bg;
-          border: 1px solid $stage-hover-bg;
-        }
+      &:hover {
+        background-color: $stage-hover-bg;
+        border: 1px solid $stage-hover-bg;
       }
 
-      .ci-play-icon {
-        padding: 5px 5px 5px 7px;
+      svg {
+        fill: $border-color;
+        position: relative;
+        left: -1px;
+        top: -1px;
+      }
+
+      &:hover svg {
+        fill: $gl-text-color;
       }
     }
 
@@ -649,7 +654,7 @@
   font-weight: 100;
   font-size: 15px;
   position: absolute;
-  right: 5px;
+  right: 13px;
   top: 8px;
 }
 
@@ -817,11 +822,23 @@
 
     &:hover,
     &:focus {
-      text-decoration: none;
-      color: $gl-text-color;
       background-color: $stage-hover-bg;
       border: 1px solid transparent;
     }
+
+    svg {
+      width: 22px;
+      height: 22px;
+      left: -6px;
+      position: relative;
+      top: -3px;
+      fill: $action-icon-color;
+    }
+
+    &:hover svg,
+    &:focus svg {
+      fill: $gl-text-color;
+    }
   }
 
   // link to the build
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index cd0839e58ea6aba615a842d9bdddea702eb0c524..8b59c20cb65c41e742e342b1e2061e5d3bb24546 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -198,7 +198,7 @@
     margin: 15px 5px 0 0;
 
     input {
-      height: 27px;
+      height: 28px;
     }
   }
 
@@ -523,7 +523,7 @@ a.deploy-project-label {
 
     &:hover,
     &:focus {
-      color: darken($notes-light-color, 15%);
+      color: $gl-text-color;
     }
   }
 
@@ -929,8 +929,32 @@ pre.light-well {
 .variables-table {
   table-layout: fixed;
 
+  &.table-responsive {
+    border: none;
+  }
+
   .variable-key {
-    width: 30%;
+    width: 300px;
+    max-width: 300px;
+    overflow: hidden;
+    word-wrap: break-word;
+
+    // override bootstrap
+    white-space: normal!important;
+
+    @media (max-width: $screen-sm-max) {
+      width: 150px;
+      max-width: 150px;
+    }
+  }
+
+  .variable-value {
+    @media(max-width: $screen-xs-max) {
+      width: 150px;
+      max-width: 150px;
+      overflow: hidden;
+      word-wrap: break-word;
+    }
   }
 }
 
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 12bff32bbf38643e3c85d78662ee98cb519adcb0..88ea92c5afb1e7e7d8184d984426cc3cb5a1cdfd 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -18,7 +18,6 @@
 .file-finder-input:hover,
 .issuable-search-form:hover,
 .search-text-input:hover,
-textarea:hover,
 .form-control:hover {
   border-color: lighten($dropdown-input-focus-border, 20%);
   box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index f19275770be9dfcc65d5e1c34bdb3448b064be29..6f31d4ed78977dba58a05528ac620aa289c6e98d 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -19,7 +19,8 @@
       overflow: visible;
     }
 
-    &.ci-failed {
+    &.ci-failed,
+    &.ci-failed_with_warnings {
       color: $gl-danger;
       border-color: $gl-danger;
 
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 01675acc62e9d5fdc8c544928c3f191813c6bee6..0d5604aae6939706ef3f947497464746d8b0d132 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -76,6 +76,10 @@
     font-size: 14px;
   }
 
+  .action-name {
+    font-weight: normal;
+  }
+
   .todo-body {
     .todo-note {
       word-wrap: break-word;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 4cce1c363ebfaa9e522052401925fcc1c62b9046..948921efc0ba07a4edb1b902981b6adfda9da953 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -32,6 +32,10 @@
       .last-commit {
         @include str-truncated(506px);
 
+        .fa-angle-right {
+          margin-left: 5px;
+        }
+
         @media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
           @include str-truncated(450px);
         }
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 1b4987dd7382a4de6fe3016ab5025f5b9c566424..543d5eac504cc65d04cdaba7d171851fb18e1194 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -5,7 +5,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
   end
 
   def update
-    if @application_setting.update_attributes(application_setting_params)
+    successful = ApplicationSettings::UpdateService
+      .new(@application_setting, current_user, application_setting_params)
+      .execute
+
+    if successful
       redirect_to admin_application_settings_path,
         notice: 'Application settings saved successfully'
     else
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index b09ae4230969720c14698b5e21316162d880ae1f..39c8c6d8a0c0c216fbc2bf776dd03f1debdd4642 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -45,7 +45,7 @@ class Admin::ProjectsController < Admin::ApplicationController
   protected
 
   def project
-    @project = Project.find_with_namespace(
+    @project = Project.find_by_full_path(
       [params[:namespace_id], '/', params[:id]].join('')
     )
     @project || render_404
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index bc65dcc33d3c582a097f4d17d69a6cbf4e45f5c6..70ac6a754342cf135d63dc8a9284a3267eee85a9 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -24,7 +24,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
   private
 
   def project
-    @project = Project.find_with_namespace(
+    @project = Project.find_by_full_path(
       [params[:namespace_id], '/', params[:project_id]].join('')
     )
     @project || render_404
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 6db4e1dc1bcb8e27850989ef92e3f3e9e3a338ee..d7a45bacd3573b1bf86c5397e7cbf7c8cf340312 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController
     if params[:search].blank?
       # Include current user if available to filter by "Me"
       if params[:current_user].present? && current_user
+        @users = @users.where.not(id: current_user.id)
         @users = [current_user, *@users]
       end
 
       if params[:author_id].present?
         author = User.find_by_id(params[:author_id])
-        @users = [author, *@users] if author
+        @users = [author, *@users].uniq if author
       end
-
-      @users.uniq!
     end
 
     render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 6f43ce5226d8e375570a54c3ca110c4250306eb1..6286d67d30cc3b0518d5993866e47bd35e1d1730 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -4,13 +4,15 @@ module CreatesCommit
   def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
     set_commit_variables
 
+    start_branch = @mr_target_branch unless initial_commit?
     commit_params = @commit_params.merge(
-      source_project: @project,
-      source_branch: @ref,
-      target_branch: @target_branch
+      start_project: @mr_target_project,
+      start_branch: start_branch,
+      target_branch: @mr_source_branch
     )
 
-    result = service.new(@tree_edit_project, current_user, commit_params).execute
+    result = service.new(
+      @mr_source_project, current_user, commit_params).execute
 
     if result[:status] == :success
       update_flash_notice(success_notice)
@@ -89,20 +91,18 @@ module CreatesCommit
     @mr_source_project != @mr_target_project
   end
 
-  def different_branch?
-    @mr_source_branch != @mr_target_branch || different_project?
-  end
-
   def create_merge_request?
-    params[:create_merge_request].present? && different_branch?
+    # XXX: Even if the field is set, if we're checking the same branch
+    # as the target branch in the same project,
+    # we don't want to create a merge request.
+    params[:create_merge_request].present? &&
+      (different_project? || @ref != @target_branch)
   end
 
+  # TODO: We should really clean this up
   def set_commit_variables
-    @mr_source_branch ||= @target_branch
-
     if can?(current_user, :push_code, @project)
       # Edit file in this project
-      @tree_edit_project = @project
       @mr_source_project = @project
 
       if @project.forked?
@@ -112,15 +112,34 @@ module CreatesCommit
       else
         # Merge request to this project
         @mr_target_project = @project
-        @mr_target_branch ||= @ref
+        @mr_target_branch = @ref || @target_branch
       end
     else
-      # Edit file in fork
-      @tree_edit_project = current_user.fork_of(@project)
       # Merge request from fork to this project
-      @mr_source_project = @tree_edit_project
+      @mr_source_project = current_user.fork_of(@project)
       @mr_target_project = @project
-      @mr_target_branch ||= @ref
+      @mr_target_branch = @ref || @target_branch
     end
+
+    @mr_source_branch = guess_mr_source_branch
+  end
+
+  def initial_commit?
+    @mr_target_branch.nil? ||
+      !@mr_target_project.repository.branch_exists?(@mr_target_branch)
+  end
+
+  def guess_mr_source_branch
+    # XXX: Happens when viewing a commit without a branch. In this case,
+    # @target_branch would be the default branch for @mr_source_project,
+    # however we want a generated new branch here. Thus we can't use
+    # @target_branch, but should pass nil to indicate that we want a new
+    # branch instead of @target_branch.
+    return if
+      create_merge_request? &&
+          # XXX: Don't understand why rubocop prefers this indention
+          @mr_source_project.repository.branch_exists?(@target_branch)
+
+    @target_branch
   end
 end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index 99acd98ae1361f01b08bdcc11acde6db5ccfba33..562f92bd83c39ebeef37c8fa374d9500ef3b3886 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -7,7 +7,7 @@ module SpammableActions
 
   def mark_as_spam
     if SpamService.new(spammable).mark_as_spam!
-      redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
+      redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
     else
       redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
     end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 3da44b9b8880780814422288583d3df51c499596..306afb65f10df40ff56eabc2b802a66dd48bd740 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController
     if signed_in?(resource_name)
       after_sign_in_path_for(resource)
     else
-      sign_in(resource)
-      if signed_in?(resource_name)
-        after_sign_in_path_for(resource)
-      else
-        new_session_path(resource_name)
-      end
+      flash[:notice] += " Please sign in."
+      new_session_path(resource_name)
     end
   end
 end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 4dda4e51f6a5a2440e48a6e779d50e9817906cd3..79d420a32d3d81c71e9ffed27a378bcfe294ee4f 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController
 
   before_action :event_filter, only: :activity
   before_action :projects, only: [:issues, :merge_requests]
+  before_action :set_show_full_reference, only: [:issues, :merge_requests]
 
   respond_to :html
 
@@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController
     @events = @event_filter.apply_filter(@events).with_associations
     @events = @events.limit(20).offset(params[:offset] || 0)
   end
+
+  def set_show_full_reference
+    @show_full_reference = true
+  end
 end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index a62c62113721c3d7ec49d129400f6b1367c3f4c3..26e17a7553ef616d1fb4aa06ddfac1ad45803758 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
 
   def trending
     @projects = filter_projects(Project.trending)
+    @projects = @projects.sort(@sort = params[:sort])
     @projects = @projects.page(params[:page])
 
     respond_to do |format|
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index f81237db9910ef5fcd799b64548e148516b42ece..264b14713fb85a759d291288b04e7a6d78f4a2eb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
     if Groups::UpdateService.new(@group, current_user, group_params).execute
       redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
     else
-      @group.reset_path!
+      @group.restore_path!
 
       render action: "edit"
     end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index b2ff36f65380395359a788b04c66d0f468ee4e1b..ba523b190bf5c4c886fb04eaccf661362e664006 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -24,7 +24,7 @@ class Projects::ApplicationController < ApplicationController
       end
 
       project_path = "#{namespace}/#{id}"
-      @project = Project.find_with_namespace(project_path)
+      @project = Project.find_by_full_path(project_path)
 
       if can?(current_user, :read_project, @project) && !@project.pending_delete?
         if @project.path_with_namespace != project_path
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 9b45ed6b6af9ae45602520cc97890e30081b152d..886934a3f677e884ade8a5bf903d79f4a4ae4110 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController
   private
 
   def build
-    @build ||= project.builds.find_by!(id: params[:id]).present(user: current_user)
+    @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user)
   end
 
   def build_path(build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index bfc59bcc8629d89257d13e2290b47227ef0ff5ca..b5a7078a3a19bdf7c14f9adcbe4fc778b4f91b1f 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def pipelines
+    @pipelines = @commit.pipelines.order(id: :desc)
+
+    respond_to do |format|
+      format.html
+      format.json do
+        render json: PipelineSerializer
+          .new(project: @project, user: @current_user)
+          .with_pagination(request, response)
+          .represent(@pipelines)
+      end
+    end
   end
 
   def branches
@@ -39,7 +50,7 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def revert
-    assign_change_commit_vars(@commit.revert_branch_name)
+    assign_change_commit_vars
 
     return render_404 if @target_branch.blank?
 
@@ -48,7 +59,7 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def cherry_pick
-    assign_change_commit_vars(@commit.cherry_pick_branch_name)
+    assign_change_commit_vars
 
     return render_404 if @target_branch.blank?
 
@@ -105,11 +116,9 @@ class Projects::CommitController < Projects::ApplicationController
     }
   end
 
-  def assign_change_commit_vars(mr_source_branch)
+  def assign_change_commit_vars
     @commit = project.commit(params[:id])
     @target_branch = params[:target_branch]
-    @mr_source_branch = mr_source_branch
-    @mr_target_branch = @target_branch
     @commit_params = {
       commit: @commit,
       create_merge_request: params[:create_merge_request].present? || different_project?
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d32966645c85bbe253248e3fb53930b996106fca..321cde255c314e04ba968a0137edf0bf975a00cb 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -46,7 +46,8 @@ class Projects::CompareController < Projects::ApplicationController
   end
 
   def define_diff_vars
-    @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
+    @compare = CompareService.new(@project, @head_ref)
+      .execute(@project, @start_ref)
 
     if @compare
       @commits = @compare.commits
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 8714349e27f3ef8c98cf88421d41481b285d5fda..216c158e41e0e24b5604ad01f55004899783a822 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -79,7 +79,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     if project_id.blank?
       @project = nil
     else
-      @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
+      @project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
     end
   end
 
@@ -109,12 +109,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   end
 
   def repository
+    wiki? ? project.wiki.repository : project.repository
+  end
+
+  def wiki?
+    return @wiki if defined?(@wiki)
+
     _, suffix = project_id_with_suffix
-    if suffix == '.wiki.git'
-      project.wiki.repository
-    else
-      project.repository
-    end
+    @wiki = suffix == '.wiki.git'
   end
 
   def render_not_found
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 9184dcccac557ba79a2ce9dde12779063b6586cb..278098fcc58635ae1a67803ae311a8015b0ce890 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -84,7 +84,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def access
-    @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
+    @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities)
   end
 
   def access_check
@@ -102,4 +102,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
 
     access_check.allowed?
   end
+
+  def access_klass
+    @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+  end
 end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 2beb0df8a07fd1b259f61111ae1d4d7469488897..8472ceca3297e3adba4162b5c14414b0d6a0c250 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -33,6 +33,18 @@ class Projects::IssuesController < Projects::ApplicationController
       @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
     end
 
+    @users = []
+
+    if params[:assignee_id].present?
+      assignee = User.find_by_id(params[:assignee_id])
+      @users.push(assignee) if assignee
+    end
+
+    if params[:author_id].present?
+      author = User.find_by_id(params[:author_id])
+      @users.push(author) if author
+    end
+
     respond_to do |format|
       format.html
       format.atom { render layout: false }
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 824ed7be73e0754b991af3344041b031bb497ea7..1593b5c1afbd7b1f2a158ef2baf0d62804fa048a 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController
   include ToggleSubscriptionAction
 
   before_action :module_enabled
-  before_action :label, only: [:edit, :update, :destroy]
+  before_action :label, only: [:edit, :update, :destroy, :promote]
   before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription]
   before_action :authorize_read_label!
   before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
                                                  :generate, :destroy, :remove_priority,
                                                  :set_priorities]
+  before_action :authorize_admin_group!, only: [:promote]
 
   respond_to :js, :html
 
@@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController
     @label.destroy
     @labels = find_labels
 
-    respond_to do |format|
-      format.html do
-        redirect_to(namespace_project_labels_path(@project.namespace, @project),
-                    notice: 'Label was removed')
-      end
-      format.js
-    end
+    redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed')
   end
 
   def remove_priority
@@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController
     end
   end
 
+  def promote
+    promote_service = Labels::PromoteService.new(@project, @current_user)
+
+    begin
+      return render_404 unless promote_service.execute(@label)
+      respond_to do |format|
+        format.html do
+          redirect_to(namespace_project_labels_path(@project.namespace, @project),
+                      notice: 'Label was promoted to a Group Label')
+        end
+        format.js
+      end
+    rescue ActiveRecord::RecordInvalid => e
+      Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
+      Gitlab::AppLogger.error e
+
+      respond_to do |format|
+        format.html do
+          redirect_to(namespace_project_labels_path(@project.namespace, @project),
+                      notice: 'Failed to promote label due to internal error. Please contact administrators.')
+        end
+        format.js
+      end
+    end
+  end
+
   protected
 
   def module_enabled
@@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController
   def authorize_admin_labels!
     return render_404 unless can?(current_user, :admin_label, @project)
   end
+
+  def authorize_admin_group!
+    return render_404 unless can?(current_user, :admin_group, @project.group)
+  end
 end
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 01d99c7df3504b18a276823eda3cafa2b8872cf2..38f7e6eb5e9a4d64910524f9dfbd032009d75e0a 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController
   end
 
   def teams
-    @teams ||= @service.list_teams(current_user)
+    @teams, @teams_error_message = @service.list_teams(current_user)
   end
 
   def service
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9ac5bf4b9f8313632ce558023a8f35eab648ab3d..6eb542e4bd8fc0875e996cbac8794b569f76af10 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
         render 'show'
       end
-      format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } }
+
+      format.json do
+        render json: {
+          html: view_to_html_string('projects/merge_requests/show/_pipelines'),
+          pipelines: PipelineSerializer
+            .new(project: @project, user: @current_user)
+            .with_pagination(request, response)
+            .represent(@pipelines)
+        }
+      end
     end
   end
 
@@ -425,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       title: merge_request.title,
       sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
       status: status,
-      coverage: coverage
+      coverage: coverage,
+      pipeline: pipeline.try(:id)
     }
 
     render json: response
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 3602b3d5e58de3e07a2394e0c29ef02c9a194c2a..667f4870c7a343f63e2f50e279d4b1dda1e71140 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController
 
         redirect_to new_path
       end
-      format.js do
-        @ref = params[:ref]
-        define_tree_vars
-        tree
-        render "tree"
-      end
     end
   end
 
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 02a97c1c574ccfa73d235958b4fc87793dd13ea0..5d193f26a8e222efa51aa0e175ce114d331d3ca3 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,8 +1,9 @@
 class Projects::SnippetsController < Projects::ApplicationController
   include ToggleAwardEmoji
+  include SpammableActions
 
   before_action :module_enabled
-  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
 
   # Allow read any snippet
   before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
@@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController
   end
 
   def create
-    @snippet = CreateSnippetService.new(@project, current_user,
-                                        snippet_params).execute
+    create_params = snippet_params.merge(request: request)
+    @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
 
     if @snippet.valid?
       respond_with(@snippet,
@@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController
     @snippet ||= @project.snippets.find(params[:id])
   end
   alias_method :awardable, :snippet
+  alias_method :spammable, :snippet
 
   def authorize_read_project_snippet!
     return render_404 unless can?(current_user, :read_project_snippet, @snippet)
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index e617be8f9fba15565564b63d98ded987b8b2e65c..50ba33ed57033d2a57f3704b020d16d98865ef74 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -36,7 +36,7 @@ class Projects::UploadsController < Projects::ApplicationController
     namespace = params[:namespace_id]
     id = params[:project_id]
 
-    file_project = Project.find_with_namespace("#{namespace}/#{id}")
+    file_project = Project.find_by_full_path("#{namespace}/#{id}")
 
     if file_project.nil?
       @uploader = nil
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 444ff837bb379a872a5387aae54da20180aa8a34..acca821782c7d17205fbc7f957158972a668aebb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -231,12 +231,16 @@ class ProjectsController < Projects::ApplicationController
   end
 
   def refs
+    branches = BranchesFinder.new(@repository, params).execute.map(&:name)
+
     options = {
-      'Branches' => @repository.branch_names,
+      'Branches' => branches.take(100),
     }
 
     unless @repository.tag_count.zero?
-      options['Tags'] = VersionSorter.rsort(@repository.tag_names)
+      tags = TagsFinder.new(@repository, params).execute.map(&:name)
+
+      options['Tags'] = tags.take(100)
     end
 
     # If reference is commit id - we should add it to branch/tag selectbox
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 627be74a38fd647f31951780610d35cffd3f178e..db2817fadf6d9271cfa6b604d7ce67b945a13bdf 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -7,6 +7,7 @@
 # For users who haven't customized the setting, we simply delegate to
 # `DashboardController#show`, which is the default.
 class RootController < Dashboard::ProjectsController
+  skip_before_action :authenticate_user!, only: [:index]
   before_action :redirect_to_custom_dashboard, only: [:index]
 
   def index
@@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController
   private
 
   def redirect_to_custom_dashboard
-    return unless current_user
+    return redirect_to new_user_session_path unless current_user
 
     case current_user.dashboard
     when 'stars'
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index b666aa01d6ba84e3942a24cf49d8dab9aac02dc3..6576ebd5235a67d8bfa2d7af4dc60d5b02454c95 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -45,6 +45,8 @@ class SearchController < ApplicationController
       end
 
     @search_objects = @search_results.objects(@scope, params[:page])
+
+    check_single_commit_result
   end
 
   def autocomplete
@@ -59,4 +61,16 @@ class SearchController < ApplicationController
 
     render json: search_autocomplete_opts(term).to_json
   end
+
+  private
+
+  def check_single_commit_result
+    if @search_results.single_commit_result?
+      only_commit = @search_results.objects('commits').first
+      query = params[:search].strip.downcase
+      found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
+
+      redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
+    end
+  end
 end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index dee57e4a388fc2cb05172a914a4d9878c39aef7e..b169d9936885130e93a3b8ef62b9bef735292903 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,5 +1,6 @@
 class SnippetsController < ApplicationController
   include ToggleAwardEmoji
+  include SpammableActions
 
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
 
@@ -40,8 +41,8 @@ class SnippetsController < ApplicationController
   end
 
   def create
-    @snippet = CreateSnippetService.new(nil, current_user,
-                                        snippet_params).execute
+    create_params = snippet_params.merge(request: request)
+    @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
 
     respond_with @snippet.becomes(Snippet)
   end
@@ -96,6 +97,7 @@ class SnippetsController < ApplicationController
                  end
   end
   alias_method :awardable, :snippet
+  alias_method :spammable, :snippet
 
   def authorize_read_snippet!
     authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a112928c6dedfb94d551c464d6aa6cd1c0a6a568..bee323993a09868bb6d8903d976cf4a6f0dbf839 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -37,7 +37,7 @@ module ApplicationHelper
       if project_id.is_a?(Project)
         project_id
       else
-        Project.find_with_namespace(project_id)
+        Project.find_by_full_path(project_id)
       end
 
     if project.avatar_url
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index c3508443d8aeff693c5aa09d862819827655757f..311a70725ab07ab67cc663619a45f0fd76000ebb 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -21,7 +21,7 @@ module BlobHelper
                                      options[:link_opts])
 
     if !on_top_of_branch?(project, ref)
-      button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+      button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
     elsif can_edit_blob?(blob, project, ref)
       link_to "Edit", edit_path, class: 'btn btn-sm'
     elsif can?(current_user, :fork_project, project)
@@ -32,7 +32,7 @@ module BlobHelper
       }
       fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
 
-      link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
+      link_to "Edit", fork_path, class: 'btn', method: :post
     end
   end
 
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index e9461b9f85923041dcabe75f2bb637d1b0d523cf..6dcb624c4da5d92de7f9c44653f536f075f67208 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -198,7 +198,7 @@ module CommitsHelper
     link_to(
       namespace_project_blob_path(project.namespace, project,
                                   tree_join(commit_sha, diff_new_path)),
-      class: 'btn view-file js-view-file btn-file-option'
+      class: 'btn view-file js-view-file'
     ) do
       raw('View file @') + content_tag(:span, commit_sha[0..6],
                                        class: 'commit-short-id')
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index aa54ee07bdccaf3b8ef34f2a7ef946e044724760..2aa0449c46e761d817030a3192d0cd3782b6037f 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
     from.present? &&
       to.present? &&
       from != to &&
-      project.feature_available?(:merge_requests, current_user) &&
+      can?(current_user, :create_merge_request, project) &&
       project.repository.branch_names.include?(from) &&
       project.repository.branch_names.include?(to)
   end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 77dc9e7d538dab25c78f13c502b0c934fcee6482..926c9703628158e1027666936133faa1da3469f3 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -14,7 +14,7 @@ module GroupsHelper
   def group_title(group, name = nil, url = nil)
     full_title = ''
 
-    group.parents.each do |parent|
+    group.ancestors.each do |parent|
       full_title += link_to(simple_sanitize(parent.name), group_path(parent))
       full_title += ' / '.html_safe
     end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index e5bb8b93e7693e5d096949f66d855a6a0a5ecbd5..03354c235eb0763ce33943846fab103f0c944e6c 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -162,6 +162,10 @@ module IssuablesHelper
     ]
   end
 
+  def issuable_reference(issuable)
+    @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
+  end
+
   def issuable_filter_present?
     issuable_filter_params.any? { |k| params.key?(k) }
   end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 8c2c4e8833ba8e8cfdbd6a81067ac5b7ab98b2b9..83ff898e68a5a3eb0913c74e2b09fae150bcc049 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -143,4 +143,16 @@ module MergeRequestsHelper
   def different_base?(version1, version2)
     version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
   end
+
+  def merge_params(merge_request)
+    {
+      merge_when_build_succeeds: true,
+      should_remove_source_branch: true,
+      sha: merge_request.diff_head_sha
+    }.merge(merge_params_ee(merge_request))
+  end
+
+  def merge_params_ee(merge_request)
+    {}
+  end
 end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 6654f6997ce2c10c47a0417067f84506a1610ea6..37b69423c972695a4803e495eaea22b17d16fdad 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -89,7 +89,7 @@ module SearchHelper
       {
         category: "Groups",
         id: group.id,
-        label: "#{search_result_sanitize(group.name)}",
+        label: "#{search_result_sanitize(group.full_name)}",
         url: group_path(group)
       }
     end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 9bab140e60abc141634e4cc931916e7f5131117a..715e5893a2cd917723e377c7854362c6e93a66b8 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -1,23 +1,23 @@
 module ServicesHelper
   def service_event_description(event)
     case event
-    when "push"
+    when "push", "push_events"
       "Event will be triggered by a push to the repository"
-    when "tag_push"
+    when "tag_push", "tag_push_events"
       "Event will be triggered when a new tag is pushed to the repository"
-    when "note"
+    when "note", "note_events"
       "Event will be triggered when someone adds a comment"
-    when "issue"
+    when "issue", "issue_events"
       "Event will be triggered when an issue is created/updated/closed"
-    when "confidential_issue"
+    when "confidential_issue", "confidential_issue_events"
       "Event will be triggered when a confidential issue is created/updated/closed"
-    when "merge_request"
+    when "merge_request", "merge_request_events"
       "Event will be triggered when a merge request is created/updated/merged"
-    when "build"
+    when "build", "build_events"
       "Event will be triggered when a build status changes"
-    when "wiki_page"
+    when "wiki_page", "wiki_page_events"
       "Event will be triggered when a wiki page is created/updated"
-    when "commit"
+    when "commit", "commit_events"
       "Event will be triggered when a commit is created/updated"
     end
   end
@@ -26,4 +26,6 @@ module ServicesHelper
     event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
     "#{event}_events"
   end
+
+  extend self
 end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index a674564c4ec8c2d0364cf865b94b3781b4766da2..456598b4c28cc26721f5750209bfb70c2574a89a 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,7 +1,8 @@
 module VersionCheckHelper
   def version_status_badge
     if Rails.env.production? && current_application_settings.version_check_enabled
-      image_tag VersionCheck.new.url
+      image_url = VersionCheck.new.url
+      image_tag image_url, class: 'js-version-status-badge'
     end
   end
 end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 3a83ae15dd8627951a718f8f182147406e64b4d9..fc93acfe63e09870ba45cf2dbd540c9fbe45b81a 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -93,10 +93,6 @@ module VisibilityLevelHelper
     current_application_settings.default_project_visibility
   end
 
-  def default_snippet_visibility
-    current_application_settings.default_snippet_visibility
-  end
-
   def default_group_visibility
     current_application_settings.default_group_visibility
   end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 0d20c9092c40ca85ffdfbb72364fcc71f055c206..46fa6fd9f6deb88369e76349f7e88ba071fedb9e 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -38,6 +38,14 @@ module Emails
       mail_answer_thread(@snippet, note_thread_options(recipient_id))
     end
 
+    def note_personal_snippet_email(recipient_id, note_id)
+      setup_note_mail(note_id, recipient_id)
+
+      @snippet = @note.noteable
+      @target_url = snippet_url(@note.noteable)
+      mail_answer_thread(@snippet, note_thread_options(recipient_id))
+    end
+
     private
 
     def note_target_url_options
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0bc1c19e9cd3b5099ce2583853ced1f0868d95cb..0cd3456b4de90d139495760fa0b624c5f7fc460c 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -107,15 +107,11 @@ class Notify < BaseMailer
 
   def mail_thread(model, headers = {})
     add_project_headers
+    add_unsubscription_headers_and_links
+
     headers["X-GitLab-#{model.class.name}-ID"] = model.id
     headers['X-GitLab-Reply-Key'] = reply_key
 
-    if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
-      headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
-
-      @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
-    end
-
     if Gitlab::IncomingEmail.enabled?
       address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
       address.display_name = @project.name_with_namespace
@@ -171,4 +167,16 @@ class Notify < BaseMailer
     headers['X-GitLab-Project-Id'] = @project.id
     headers['X-GitLab-Project-Path'] = @project.path_with_namespace
   end
+
+  def add_unsubscription_headers_and_links
+    return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable?
+
+    list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)]
+    if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
+      list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}"
+    end
+
+    headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
+    @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
+  end
 end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index fa8f8bc3a5f8dca53e3340e53f2a90c54a51a3d6..ad6c588202e531d0ab2759b9211293d562d62e12 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -22,6 +22,17 @@ class Ability
       end
     end
 
+    # Given a list of users and a snippet this method returns the users that can
+    # read the given snippet.
+    def users_that_can_read_personal_snippet(users, snippet)
+      case snippet.visibility_level
+      when Snippet::INTERNAL, Snippet::PUBLIC
+        users
+      when Snippet::PRIVATE
+        users.include?(snippet.author) ? [snippet.author] : []
+      end
+    end
+
     # Returns an Array of Issues that can be read by the given user.
     #
     # issues - The issues to reduce down to those readable by the user.
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 8fab77cda0aa8293f846d320a7a330156bd6ff03..2df8b071e13e0165f8db82324d4a0c4b9b2cc8ca 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -156,53 +156,64 @@ class ApplicationSetting < ActiveRecord::Base
 
   def self.expire
     Rails.cache.delete(CACHE_KEY)
+  rescue
+    # Gracefully handle when Redis is not available. For example,
+    # omnibus may fail here during gitlab:assets:compile.
   end
 
   def self.cached
     Rails.cache.fetch(CACHE_KEY)
   end
 
-  def self.create_from_defaults
-    create(
-      default_projects_limit: Settings.gitlab['default_projects_limit'],
-      default_branch_protection: Settings.gitlab['default_branch_protection'],
-      signup_enabled: Settings.gitlab['signup_enabled'],
-      signin_enabled: Settings.gitlab['signin_enabled'],
-      gravatar_enabled: Settings.gravatar['enabled'],
-      sign_in_text: nil,
+  def self.defaults_ce
+    {
       after_sign_up_text: nil,
-      help_page_text: nil,
-      shared_runners_text: nil,
-      restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
-      max_attachment_size: Settings.gitlab['max_attachment_size'],
-      session_expire_delay: Settings.gitlab['session_expire_delay'],
+      akismet_enabled: false,
+      container_registry_token_expire_delay: 5,
+      default_branch_protection: Settings.gitlab['default_branch_protection'],
       default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+      default_projects_limit: Settings.gitlab['default_projects_limit'],
       default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+      disabled_oauth_sign_in_sources: [],
       domain_whitelist: Settings.gitlab['domain_whitelist'],
+      gravatar_enabled: Settings.gravatar['enabled'],
+      help_page_text: nil,
+      housekeeping_bitmaps_enabled: true,
+      housekeeping_enabled: true,
+      housekeeping_full_repack_period: 50,
+      housekeeping_gc_period: 200,
+      housekeeping_incremental_repack_period: 10,
       import_sources: Gitlab::ImportSources.values,
-      shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
-      max_artifacts_size: Settings.artifacts['max_size'],
-      require_two_factor_authentication: false,
-      two_factor_grace_period: 48,
-      recaptcha_enabled: false,
-      akismet_enabled: false,
       koding_enabled: false,
       koding_url: nil,
+      max_artifacts_size: Settings.artifacts['max_size'],
+      max_attachment_size: Settings.gitlab['max_attachment_size'],
       plantuml_enabled: false,
       plantuml_url: nil,
+      recaptcha_enabled: false,
       repository_checks_enabled: true,
-      disabled_oauth_sign_in_sources: [],
-      send_user_confirmation_email: false,
-      container_registry_token_expire_delay: 5,
       repository_storages: ['default'],
-      user_default_external: false,
+      require_two_factor_authentication: false,
+      restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
+      session_expire_delay: Settings.gitlab['session_expire_delay'],
+      send_user_confirmation_email: false,
+      shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
+      shared_runners_text: nil,
       sidekiq_throttling_enabled: false,
-      housekeeping_enabled: true,
-      housekeeping_bitmaps_enabled: true,
-      housekeeping_incremental_repack_period: 10,
-      housekeeping_full_repack_period: 50,
-      housekeeping_gc_period: 200,
-    )
+      sign_in_text: nil,
+      signin_enabled: Settings.gitlab['signin_enabled'],
+      signup_enabled: Settings.gitlab['signup_enabled'],
+      two_factor_grace_period: 48,
+      user_default_external: false
+    }
+  end
+
+  def self.defaults
+    defaults_ce
+  end
+
+  def self.create_from_defaults
+    create(defaults)
   end
 
   def home_page_url_column_exist
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5fe8ddf69d7168f8531848a6f45583e39107c29d..b1f77bf242cc8e17a484f07f44003ed79b762bce 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -275,29 +275,23 @@ module Ci
     end
 
     def update_coverage
-      return unless project
-      coverage_regex = project.build_coverage_regex
-      return unless coverage_regex
       coverage = extract_coverage(trace, coverage_regex)
-
-      if coverage.is_a? Numeric
-        update_attributes(coverage: coverage)
-      end
+      update_attributes(coverage: coverage) if coverage.present?
     end
 
     def extract_coverage(text, regex)
-      begin
-        matches = text.scan(Regexp.new(regex)).last
-        matches = matches.last if matches.kind_of?(Array)
-        coverage = matches.gsub(/\d+(\.\d+)?/).first
+      return unless regex
 
-        if coverage.present?
-          coverage.to_f
-        end
-      rescue
-        # if bad regex or something goes wrong we dont want to interrupt transition
-        # so we just silentrly ignore error for now
+      matches = text.scan(Regexp.new(regex)).last
+      matches = matches.last if matches.kind_of?(Array)
+      coverage = matches.gsub(/\d+(\.\d+)?/).first
+
+      if coverage.present?
+        coverage.to_f
       end
+    rescue
+      # if bad regex or something goes wrong we dont want to interrupt transition
+      # so we just silentrly ignore error for now
     end
 
     def has_trace_file?
@@ -522,6 +516,10 @@ module Ci
       self.update(artifacts_expire_at: nil)
     end
 
+    def coverage_regex
+      super || project.try(:build_coverage_regex)
+    end
+
     def when
       read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
     end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2a97e8bae4a8ea9785fd5402c2945ae9fee3d711..fab8497ec7db820fb7c09a7d86330d76c7e81604 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -128,16 +128,21 @@ module Ci
     end
 
     def stages
+      # TODO, this needs refactoring, see gitlab-ce#26481.
+
+      stages_query = statuses
+        .group('stage').select(:stage).order('max(stage_idx)')
+
       status_sql = statuses.latest.where('stage=sg.stage').status_sql
 
-      stages_query = statuses.group('stage').select(:stage)
-                       .order('max(stage_idx)')
+      warnings_sql = statuses.latest.select('COUNT(*) > 0')
+        .where('stage=sg.stage').failed_but_allowed.to_sql
 
-      stages_with_statuses = CommitStatus.from(stages_query, :sg).
-        pluck('sg.stage', status_sql)
+      stages_with_statuses = CommitStatus.from(stages_query, :sg)
+        .pluck('sg.stage', status_sql, "(#{warnings_sql})")
 
       stages_with_statuses.map do |stage|
-        Ci::Stage.new(self, name: stage.first, status: stage.last)
+        Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
       end
     end
 
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index f30253eefe3e0bfb657be8fb686bca9bd5600ae8..07a086b0acae00b1a48cfdcad69cdf03572bb764 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -126,9 +126,11 @@ module Ci
     end
 
     def tick_runner_queue
-      new_update = SecureRandom.hex
-      Gitlab::Redis.with { |redis| redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME) }
-      new_update
+      SecureRandom.hex.tap do |new_update|
+        Gitlab::Redis.with do |redis|
+          redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME)
+        end
+      end
     end
 
     def ensure_runner_queue_value
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index d035eda6df56084f45a97a673e0b1e95f0464314..ca74c91b0627b5861e8052b0c94c31a081c22ee1 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -8,10 +8,11 @@ module Ci
 
     delegate :project, to: :pipeline
 
-    def initialize(pipeline, name:, status: nil)
+    def initialize(pipeline, name:, status: nil, warnings: nil)
       @pipeline = pipeline
       @name = name
       @status = status
+      @warnings = warnings
     end
 
     def to_param
@@ -39,5 +40,17 @@ module Ci
     def builds
       @builds ||= pipeline.builds.where(stage: name)
     end
+
+    def success?
+      status.to_s == 'success'
+    end
+
+    def has_warnings?
+      if @warnings.nil?
+        statuses.latest.failed_but_allowed.any?
+      else
+        @warnings
+      end
+    end
   end
 end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 5d942cb0422ba5ced7386e0f0c851b9d40d1b12a..46f06733da198111f9a31f2f8dd4ff7fbdb25941 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -21,6 +21,9 @@ class Commit
   DIFF_HARD_LIMIT_FILES = 1000
   DIFF_HARD_LIMIT_LINES = 50000
 
+  # The SHA can be between 7 and 40 hex characters.
+  COMMIT_SHA_PATTERN = '\h{7,40}'
+
   class << self
     def decorate(commits, project)
       commits.map do |commit|
@@ -52,6 +55,10 @@ class Commit
     def from_hash(hash, project)
       new(Gitlab::Git::Commit.new(hash), project)
     end
+
+    def valid_hash?(key)
+      !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
+    end
   end
 
   attr_accessor :raw
@@ -77,8 +84,6 @@ class Commit
 
   # Pattern used to extract commit references from text
   #
-  # The SHA can be between 7 and 40 hex characters.
-  #
   # This pattern supports cross-project references.
   def self.reference_pattern
     @reference_pattern ||= %r{
@@ -88,15 +93,15 @@ class Commit
   end
 
   def self.link_reference_pattern
-    @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
+    @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
   end
 
   def to_reference(from_project = nil, full: false)
     commit_reference(from_project, id, full: full)
   end
 
-  def reference_link_text(from_project = nil)
-    commit_reference(from_project, short_id)
+  def reference_link_text(from_project = nil, full: false)
+    commit_reference(from_project, short_id, full: full)
   end
 
   def diff_line_count
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 90bd6490a02e901b2fc4816756217b81ea328d98..a600f9c14c5a5dd17ad4f029b2eb04ac4da8c9fd 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -51,6 +51,10 @@ module CacheMarkdownField
     CACHING_CLASSES.map(&:constantize)
   end
 
+  def skip_project_check?
+    false
+  end
+
   extend ActiveSupport::Concern
 
   included do
@@ -112,7 +116,8 @@ module CacheMarkdownField
       invalidation_method = "#{html_field}_invalidated?".to_sym
 
       define_method(cache_method) do
-        html = Banzai::Renderer.cacheless_render_field(self, markdown_field)
+        options = { skip_project_check: skip_project_check? }
+        html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
         __send__("#{html_field}=", html)
         true
       end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 90432fc4050ac029830daa658bd41f3dc67eed90..431c035496917aef14665c8d925bd487d0d8f3e5 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,6 +1,7 @@
 module HasStatus
   extend ActiveSupport::Concern
 
+  DEFAULT_STATUS = 'created'
   AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
   STARTED_STATUSES = %w[running success failed skipped]
   ACTIVE_STATUSES = %w[pending running]
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 8ab0401d28811e8e5b47f6c3f672d963b47c5b0b..ef2c1e5d41430f4badef5ff0804005d8f0af4d1a 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -49,7 +49,11 @@ module Mentionable
 
     self.class.mentionable_attrs.each do |attr, options|
       text    = __send__(attr)
-      options = options.merge(cache_key: [self, attr], author: author)
+      options = options.merge(
+        cache_key: [self, attr],
+        author: author,
+        skip_project_check: skip_project_check?
+      )
 
       extractor.analyze(text, options)
     end
@@ -121,4 +125,8 @@ module Mentionable
   def cross_reference_exists?(target)
     SystemNoteService.cross_reference_exists?(target, local_reference)
   end
+
+  def skip_project_check?
+    false
+  end
 end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 70740c76e43ecc01ffc3234c065ae9b0c7f543be..4865c0a14b1721577702a65c2cbedabed4e4d272 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -96,6 +96,11 @@ module Participable
 
     participants.merge(ext.users)
 
-    Ability.users_that_can_read_project(participants.to_a, project)
+    case self
+    when PersonalSnippet
+      Ability.users_that_can_read_personal_snippet(participants.to_a, self)
+    else
+      Ability.users_that_can_read_project(participants.to_a, project)
+    end
   end
 end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 1108a64c59e7e06e2c9984939a4c290941e78d15..2b93aa30c0f2ef5a645cba06d6cbba142d9dab14 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -60,6 +60,21 @@ module Routable
         joins(:route).where(wheres.join(' OR '))
       end
     end
+
+    # Builds a relation to find multiple objects that are nested under user membership
+    #
+    # Usage:
+    #
+    #     Klass.member_descendants(1)
+    #
+    # Returns an ActiveRecord::Relation.
+    def member_descendants(user_id)
+      joins(:route).
+        joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
+               INNER JOIN members ON members.source_id = r2.source_id
+               AND members.source_type = r2.source_type").
+        where('members.user_id = ?', user_id)
+    end
   end
 
   private
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 1aa97debe426fcfe3c893603833e6119da366191..1acff093aa1ac0ca213ea4c02ce654fd581c9041 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -34,7 +34,13 @@ module Spammable
   end
 
   def check_for_spam
-    self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
+    if spam?
+      self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
+    end
+  end
+
+  def spammable_entity_type
+    self.class.name.underscore
   end
 
   def spam_title
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index ebc75100a546c8e298fb8c206cceca7c9390b562..25e2d8ea24e10ca3857b73f90ddc1f399a114b9e 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -11,10 +11,10 @@ module Taskable
   INCOMPLETE   = 'incomplete'.freeze
   ITEM_PATTERN = /
     ^
-    (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
-    \s*                     # optional whitespace prefix
-    (\[\s\]|\[[xX]\])       # checkbox
-    (\s.+)                  # followed by whitespace and some text.
+    \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
+    \s+                       # whitespace prefix has to be always presented for a list item
+    (\[\s\]|\[[xX]\])         # checkbox
+    (\s.+)                    # followed by whitespace and some text.
   /x
 
   def self.get_tasks(content)
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 652abf18a8a145da9be6d5433283986e452dd47f..577367f1eedd8d48921eaf00a6faa95a14dfe275 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -1,7 +1,8 @@
 class Environment < ActiveRecord::Base
   # Used to generate random suffixes for the slug
+  LETTERS = 'a'..'z'
   NUMBERS = '0'..'9'
-  SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
+  SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
 
   belongs_to :project, required: true, validate: true
 
@@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base
     slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
 
     # Must start with a letter
-    slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
+    slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
+
+    # Repeated dashes are invalid (OpenShift limitation)
+    slugified.gsub!(/\-+/, '-')
 
     # Maximum length: 24 characters (OpenShift limitation)
     slugified = slugified[0..23]
 
-    # Cannot end with a "-" character (Kubernetes label limitation)
-    slugified = slugified[0..-2] if slugified[-1] == "-"
+    # Cannot end with a dash (Kubernetes label limitation)
+    slugified.chop! if slugified.end_with?('-')
 
     # Add a random suffix, shortening the current string if necessary, if it
     # has been slugified. This ensures uniqueness.
-    slugified = slugified[0..16] + "-" + random_suffix if slugified != name
+    if slugified != name
+      slugified = slugified[0..16]
+      slugified << '-' unless slugified.end_with?('-')
+      slugified << random_suffix
+    end
 
     self.slug = slugified
   end
diff --git a/app/models/group.rb b/app/models/group.rb
index 99675ddb3661ff5c9f6ca515f689d0b5d3cd263d..4cdfd022094290857a4a272bad8bc1ebdb02e172 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -201,7 +201,7 @@ class Group < Namespace
   end
 
   def members_with_parents
-    GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id))
+    GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id))
   end
 
   def users_with_parents
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 65638d9a29957e9ac0433cff9e4604701da901e3..d8826b65fcc2edd7ca9ef99bc82ac01009fd4392 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base
     end
   end
 
-  def to_reference(from_project = nil, full: false)
+  # `from` argument can be a Namespace or Project.
+  def to_reference(from = nil, full: false)
     reference = "#{self.class.reference_prefix}#{iid}"
 
-    "#{project.to_reference(from_project, full: full)}#{reference}"
+    "#{project.to_reference(from, full: full)}#{reference}"
   end
 
   def referenced_merge_requests(current_user = nil)
diff --git a/app/models/key.rb b/app/models/key.rb
index 8be29c697f1bc62ea8cf4f64bf43143d6c98ff90..9c74ca84753288552608f463d76aa64d93c9a53a 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -4,6 +4,8 @@ class Key < ActiveRecord::Base
   include AfterCommitQueue
   include Sortable
 
+  LAST_USED_AT_REFRESH_TIME = 1.day.to_i
+
   belongs_to :user
 
   before_validation :generate_fingerprint
@@ -50,7 +52,10 @@ class Key < ActiveRecord::Base
   end
 
   def update_last_used_at
-    UseKeyWorker.perform_async(self.id)
+    lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME)
+    return unless lease.try_obtain
+
+    UseKeyWorker.perform_async(id)
   end
 
   def add_to_shell
diff --git a/app/models/member.rb b/app/models/member.rb
index c585e0b450e97d8e22b29631b828eddaa19cfd39..26a6054e00db2537d6b55c894c3067ea251b734a 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -68,9 +68,9 @@ class Member < ActiveRecord::Base
   after_create :send_request, if: :request?, unless: :importing?
   after_create :create_notification_setting, unless: [:pending?, :importing?]
   after_create :post_create_hook, unless: [:pending?, :importing?]
-  after_create :refresh_member_authorized_projects, if: :importing?
   after_update :post_update_hook, unless: [:pending?, :importing?]
   after_destroy :post_destroy_hook, unless: :pending?
+  after_commit :refresh_member_authorized_projects
 
   delegate :name, :username, :email, to: :user, prefix: true
 
@@ -147,8 +147,6 @@ class Member < ActiveRecord::Base
         member.save
       end
 
-      UserProjectAccessChangedService.new(user.id).execute if user.is_a?(User)
-
       member
     end
 
@@ -275,23 +273,27 @@ class Member < ActiveRecord::Base
   end
 
   def post_create_hook
-    UserProjectAccessChangedService.new(user.id).execute
     system_hook_service.execute_hooks_for(self, :create)
   end
 
   def post_update_hook
-    UserProjectAccessChangedService.new(user.id).execute if access_level_changed?
+    # override in sub class
   end
 
   def post_destroy_hook
-    refresh_member_authorized_projects
     system_hook_service.execute_hooks_for(self, :destroy)
   end
 
+  # Refreshes authorizations of the current member.
+  #
+  # This method schedules a job using Sidekiq and as such **must not** be called
+  # in a transaction. Doing so can lead to the job running before the
+  # transaction has been committed, resulting in the job either throwing an
+  # error or not doing any meaningful work.
   def refresh_member_authorized_projects
-    # If user/source is being destroyed, project access are gonna be destroyed eventually
-    # because of DB foreign keys, so we shouldn't bother with refreshing after each
-    # member is destroyed through association
+    # If user/source is being destroyed, project access are going to be
+    # destroyed eventually because of DB foreign keys, so we shouldn't bother
+    # with refreshing after each member is destroyed through association
     return if destroyed_by_association.present?
 
     UserProjectAccessChangedService.new(user_id).execute
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index cd5b345bae5021bce4121de6e044406f420bd92a..082adcafcc80d8bd14e0273daa0f23d3c81e09d9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base
     work_in_progress?(title) ? title : "WIP: #{title}"
   end
 
-  def to_reference(from_project = nil, full: false)
+  # `from` argument can be a Namespace or Project.
+  def to_reference(from = nil, full: false)
     reference = "#{self.class.reference_prefix}#{iid}"
 
-    "#{project.to_reference(from_project, full: full)}#{reference}"
+    "#{project.to_reference(from, full: full)}#{reference}"
   end
 
   def first_commit
@@ -865,9 +866,11 @@ class MergeRequest < ActiveRecord::Base
       paths: paths
     )
 
-    active_diff_notes.each do |note|
-      service.execute(note)
-      Gitlab::Timeless.timeless(note, &:save)
+    transaction do
+      active_diff_notes.each do |note|
+        service.execute(note)
+        Gitlab::Timeless.timeless(note, &:save)
+      end
     end
   end
 
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index dadb81f9b6e30804cf984d98e63e9acdcc3dd5ec..70bad2a4396aecf039fd42e5ca68393d4e3ba228 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -169,7 +169,8 @@ class MergeRequestDiff < ActiveRecord::Base
     # When compare merge request versions we want diff A..B instead of A...B
     # so we handle cases when user does squash and rebase of the commits between versions.
     # For this reason we set straight to true by default.
-    CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
+    CompareService.new(project, head_commit_sha)
+      .execute(project, sha, straight: straight)
   end
 
   def commits_count
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index d41833de66f2cd0325092fc5f51227baf703a77f..67d8c1c2e4cb29f5661a46d0147916565e5157c5 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base
   include CacheMarkdownField
   include Sortable
   include Gitlab::ShellAdapter
+  include Gitlab::CurrentSettings
   include Routable
 
   cache_markdown_field :description, pipeline: :description
@@ -130,6 +131,8 @@ class Namespace < ActiveRecord::Base
 
     Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
 
+    remove_exports!
+
     # If repositories moved successfully we need to
     # send update instructions to users.
     # However we cannot allow rollback since we moved namespace dir
@@ -174,6 +177,10 @@ class Namespace < ActiveRecord::Base
     end
   end
 
+  def shared_runners_enabled?
+    projects.with_shared_runners.any?
+  end
+
   def full_name
     @full_name ||=
       if parent
@@ -183,8 +190,26 @@ class Namespace < ActiveRecord::Base
       end
   end
 
-  def parents
-    @parents ||= parent ? parent.parents + [parent] : []
+  # Scopes the model on ancestors of the record
+  def ancestors
+    if parent_id
+      path = route.path
+      paths = []
+
+      until path.blank?
+        path = path.rpartition('/').first
+        paths << path
+      end
+
+      self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC')
+    else
+      self.class.none
+    end
+  end
+
+  # Scopes the model on direct and indirect children of the record
+  def descendants
+    self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC')
   end
 
   private
@@ -214,6 +239,8 @@ class Namespace < ActiveRecord::Base
         GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
       end
     end
+
+    remove_exports!
   end
 
   def refresh_access_of_projects_invited_groups
@@ -226,4 +253,20 @@ class Namespace < ActiveRecord::Base
   def full_path_changed?
     path_changed? || parent_id_changed?
   end
+
+  def remove_exports!
+    Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
+  end
+
+  def export_path
+    File.join(Gitlab::ImportExport.storage_path, full_path_was)
+  end
+
+  def full_path_was
+    if parent
+      parent.full_path + '/' + path_was
+    else
+      path_was
+    end
+  end
 end
diff --git a/app/models/note.rb b/app/models/note.rb
index 0c1b05dabf28909b5aaedec606ebd1308b515a1d..bf090a0438c4768940f797bcd2946269286be962 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -43,7 +43,8 @@ class Note < ActiveRecord::Base
   delegate :name, :email, to: :author, prefix: true
   delegate :title, to: :noteable, allow_nil: true
 
-  validates :note, :project, presence: true
+  validates :note, presence: true
+  validates :project, presence: true, unless: :for_personal_snippet?
 
   # Attachments are deprecated and are handled by Markdown uploader
   validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -53,7 +54,7 @@ class Note < ActiveRecord::Base
   validates :commit_id, presence: true, if: :for_commit?
   validates :author, presence: true
 
-  validate unless: [:for_commit?, :importing?] do |note|
+  validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
     unless note.noteable.try(:project) == note.project
       errors.add(:invalid_project, 'Note and noteable project mismatch')
     end
@@ -83,7 +84,7 @@ class Note < ActiveRecord::Base
   after_initialize :ensure_discussion_id
   before_validation :nullify_blank_type, :nullify_blank_line_code
   before_validation :set_discussion_id
-  after_save :keep_around_commit
+  after_save :keep_around_commit, unless: :for_personal_snippet?
 
   class << self
     def model_name
@@ -165,6 +166,14 @@ class Note < ActiveRecord::Base
     noteable_type == "Snippet"
   end
 
+  def for_personal_snippet?
+    noteable.is_a?(PersonalSnippet)
+  end
+
+  def skip_project_check?
+    for_personal_snippet?
+  end
+
   # override to return commits, which are not active record
   def noteable
     if for_commit?
@@ -220,6 +229,10 @@ class Note < ActiveRecord::Base
     note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
   end
 
+  def to_ability_name
+    for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
+  end
+
   private
 
   def keep_around_commit
diff --git a/app/models/project.rb b/app/models/project.rb
index 1630975b0d3fd23189a76435aa816c12d3149753..0d286bfbaa80a9dcdf730a52503002e5fa5bb348 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -121,8 +121,6 @@ class Project < ActiveRecord::Base
 
   # Merge Requests for target project should be removed with it
   has_many :merge_requests,     dependent: :destroy, foreign_key: 'target_project_id'
-  # Merge requests from source project should be kept when source project was removed
-  has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
   has_many :issues,             dependent: :destroy
   has_many :labels,             dependent: :destroy, class_name: 'ProjectLabel'
   has_many :services,           dependent: :destroy
@@ -226,6 +224,8 @@ class Project < ActiveRecord::Base
 
   scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
   scope :with_statistics, -> { includes(:statistics) }
+  scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
+  scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
 
   # "enabled" here means "not disabled". It includes private features!
   scope :with_feature_enabled, ->(feature) {
@@ -370,10 +370,6 @@ class Project < ActiveRecord::Base
     def group_ids
       joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
     end
-
-    # Add alias for Routable method for compatibility with old code.
-    # In future all calls `find_with_namespace` should be replaced with `find_by_full_path`
-    alias_method :find_with_namespace, :find_by_full_path
   end
 
   def lfs_enabled?
@@ -592,10 +588,11 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def to_reference(from_project = nil, full: false)
-    if full || cross_namespace_reference?(from_project)
+  # `from` argument can be a Namespace or Project.
+  def to_reference(from = nil, full: false)
+    if full || cross_namespace_reference?(from)
       path_with_namespace
-    elsif cross_project_reference?(from_project)
+    elsif cross_project_reference?(from)
       path
     end
   end
@@ -1098,12 +1095,20 @@ class Project < ActiveRecord::Base
     project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
   end
 
+  def shared_runners_available?
+    shared_runners_enabled?
+  end
+
+  def shared_runners
+    shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
+  end
+
   def any_runners?(&block)
     if runners.active.any?(&block)
       return true
     end
 
-    shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
+    shared_runners.active.any?(&block)
   end
 
   def valid_runners_token?(token)
@@ -1284,21 +1289,26 @@ class Project < ActiveRecord::Base
 
   private
 
+  def cross_namespace_reference?(from)
+    case from
+    when Project
+      namespace != from.namespace
+    when Namespace
+      namespace != from
+    end
+  end
+
   # Check if a reference is being done cross-project
-  #
-  # from_project - Refering Project object
-  def cross_project_reference?(from_project)
-    from_project && self != from_project
+  def cross_project_reference?(from)
+    return true if from.is_a?(Namespace)
+
+    from && self != from
   end
 
   def pushes_since_gc_redis_key
     "projects/#{id}/pushes_since_gc"
   end
 
-  def cross_namespace_reference?(from_project)
-    from_project && namespace != from_project.namespace
-  end
-
   def default_branch_protected?
     current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
       current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
@@ -1338,6 +1348,6 @@ class Project < ActiveRecord::Base
   def pending_delete_twin
     return false unless path
 
-    Project.unscoped.where(pending_delete: true).find_with_namespace(path_with_namespace)
+    Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace)
   end
 end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 6149c35cc6106509828ac5d27bcef5fd9535537d..5cb6b0c527d1505b1e7dd7cab47f8f463e92ffcd 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -16,8 +16,7 @@ class ProjectGroupLink < ActiveRecord::Base
   validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
   validate :different_group
 
-  after_create :refresh_group_members_authorized_projects
-  after_destroy :refresh_group_members_authorized_projects
+  after_commit :refresh_group_members_authorized_projects
 
   def self.access_options
     Gitlab::Access.options
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 7c23b76676374c80af5853b58ddebc234aae87d1..3728f5642e432253a1276d1f289580718a08bc61 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -25,7 +25,7 @@ You can create a Personal Access Token here:
 http://app.asana.com/-/account_api'
   end
 
-  def to_param
+  def self.to_param
     'asana'
   end
 
@@ -44,7 +44,7 @@ http://app.asana.com/-/account_api'
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index d839221d315e04dc6e53f80a64e47e2bf3b88ef7..aeeff8917bf58e2b58fa74db3b54b06432ad6bb0 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -12,7 +12,7 @@ class AssemblaService < Service
     'Project Management Software (Source Commits Endpoint)'
   end
 
-  def to_param
+  def self.to_param
     'assembla'
   end
 
@@ -23,7 +23,7 @@ class AssemblaService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 4819bdbef8cae042fa8f2e1a6e35e830f23f5d8d..400020ee04aa8ebd87a50159e84b433a106faf94 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -40,7 +40,7 @@ class BambooService < CiService
     'You must set up automatic revision labeling and a repository trigger in Bamboo.'
   end
 
-  def to_param
+  def self.to_param
     'bamboo'
   end
 
@@ -56,10 +56,6 @@ class BambooService < CiService
     ]
   end
 
-  def supported_events
-    %w(push)
-  end
-
   def build_page(sha, ref)
     with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
   end
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index 338e685339a03c1536fcdb9fa973ce52482238c7..046e2809f454ffacf99ec63fd9b8e794a6147758 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService
     end
   end
 
-  def to_param
+  def self.to_param
     'bugzilla'
   end
 end
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index e77942d8f3cff099a5be9f3f93d51e94469655a1..0956c4a4ede143a41930732633962ee2505941c6 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -24,10 +24,6 @@ class BuildkiteService < CiService
     hook.save
   end
 
-  def supported_events
-    %w(push)
-  end
-
   def execute(data)
     return unless supported_events.include?(data[:object_kind])
 
@@ -54,7 +50,7 @@ class BuildkiteService < CiService
     'Continuous integration and deployments'
   end
 
-  def to_param
+  def self.to_param
     'buildkite'
   end
 
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 201b94b065ba899d607c4d9ce98782c413e72e30..ebd21e3718910bcb25076fbff710d7b97e93c4d3 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -19,11 +19,11 @@ class BuildsEmailService < Service
     'Email the builds status to a list of recipients.'
   end
 
-  def to_param
+  def self.to_param
     'builds_email'
   end
 
-  def supported_events
+  def self.supported_events
     %w(build)
   end
 
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 5af93860d09df945945a21ed4621a6cbb7b97a71..0de59af5652de6997f04aa189081107ca0e91e1f 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -12,7 +12,7 @@ class CampfireService < Service
     'Simple web-based real-time group chat'
   end
 
-  def to_param
+  def self.to_param
     'campfire'
   end
 
@@ -24,7 +24,7 @@ class CampfireService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index b7ef44c3054523edc10666a5f59e1da85387ab3a..8468934425fc113a0ee40147528820d9165c13a0 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -25,7 +25,7 @@ class ChatNotificationService < Service
     valid?
   end
 
-  def supported_events
+  def self.supported_events
     %w[push issue confidential_issue merge_request note tag_push
        build pipeline wiki_page]
   end
@@ -82,19 +82,19 @@ class ChatNotificationService < Service
   def get_message(object_kind, data)
     case object_kind
     when "push", "tag_push"
-      PushMessage.new(data)
+      ChatMessage::PushMessage.new(data)
     when "issue"
-      IssueMessage.new(data) unless is_update?(data)
+      ChatMessage::IssueMessage.new(data) unless is_update?(data)
     when "merge_request"
-      MergeMessage.new(data) unless is_update?(data)
+      ChatMessage::MergeMessage.new(data) unless is_update?(data)
     when "note"
-      NoteMessage.new(data)
+      ChatMessage::NoteMessage.new(data)
     when "build"
-      BuildMessage.new(data) if should_build_be_notified?(data)
+      ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
     when "pipeline"
-      PipelineMessage.new(data) if should_pipeline_be_notified?(data)
+      ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
     when "wiki_page"
-      WikiPageMessage.new(data)
+      ChatMessage::WikiPageMessage.new(data)
     end
   end
 
diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb
index 0bc160af6042638afcd815222bc074e24854c570..5eb1bd86e9db5380ee1090a48a7ae19ca8ef4658 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/chat_slash_commands_service.rb
@@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service
       ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
   end
 
-  def supported_events
-    []
+  def self.supported_events
+    %w()
   end
 
   def can_test?
@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
     return unless valid_token?(params[:token])
 
     user = find_chat_user(params)
-    unless user
+
+    if user
+      Gitlab::ChatCommands::Command.new(project, user, params).execute
+    else
       url = authorize_chat_name_url(params)
-      return presenter.authorize_chat_name(url)
+      Gitlab::ChatCommands::Presenters::Access.new(url).authorize
     end
-
-    Gitlab::ChatCommands::Command.new(project, user,
-      params).execute
   end
 
   private
@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
   def authorize_chat_name_url(params)
     ChatNames::AuthorizeUserService.new(self, params).execute
   end
-
-  def presenter
-    Gitlab::ChatCommands::Presenter.new
-  end
 end
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 4de0106707e644f8446a1968f11a778081d38824..82979c8bd34ed76a60563b04113f24f8b5db0d46 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -8,7 +8,7 @@ class CiService < Service
     self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index b2f426dc2acc4e6bb42fd965491a18bcdbd6a7cb..dea915a4d056c11dab5fa06ab0010094465ced7e 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService
     end
   end
 
-  def to_param
+  def self.to_param
     'custom_issue_tracker'
   end
 
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
index ab353a1abe61c26e4aa720fda95f9eaf07e6e9e7..91a55514a9a64d5d57e2425379ccf4895ac9d625 100644
--- a/app/models/project_services/deployment_service.rb
+++ b/app/models/project_services/deployment_service.rb
@@ -5,8 +5,8 @@
 class DeploymentService < Service
   default_value_for :category, 'deployment'
 
-  def supported_events
-    []
+  def self.supported_events
+    %w()
   end
 
   def predefined_variables
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 4bbbebf54cba17bb64405cb1e89807b3bdc2fd6b..0a217d8cabab8379aeedda02e2f655b80ccf8712 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -32,7 +32,7 @@ class DroneCiService < CiService
     true
   end
 
-  def supported_events
+  def self.supported_events
     %w(push merge_request tag_push)
   end
 
@@ -87,7 +87,7 @@ class DroneCiService < CiService
     'Drone is a Continuous Integration platform built on Docker, written in Go'
   end
 
-  def to_param
+  def self.to_param
     'drone_ci'
   end
 
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 79285cbd26de1182ea5cb23d25becc3ccdd623dc..f4f913ee0b60518488ee735d2cad40cfefe0b6d2 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -12,11 +12,11 @@ class EmailsOnPushService < Service
     'Email the commits and diff of each push to a list of recipients.'
   end
 
-  def to_param
+  def self.to_param
     'emails_on_push'
   end
 
-  def supported_events
+  def self.supported_events
     %w(push tag_push)
   end
 
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index d7b6e505191b8c82bda2d5fbf741e9c1ca1f94e8..bdf6fa6a5860eba9b017a39d57cdae8745deb7e2 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -13,7 +13,7 @@ class ExternalWikiService < Service
     'Replaces the link to the internal wiki with a link to an external wiki.'
   end
 
-  def to_param
+  def self.to_param
     'external_wiki'
   end
 
@@ -29,4 +29,8 @@ class ExternalWikiService < Service
       nil
     end
   end
+
+  def self.supported_events
+    %w()
+  end
 end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index dd00275187f63533e21249d810294ce9b4ae9ee3..10a13c3fbdcb57962dd6ebe0f71bddefe819b5ed 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -12,7 +12,7 @@ class FlowdockService < Service
     'Flowdock is a collaboration web app for technical teams.'
   end
 
-  def to_param
+  def self.to_param
     'flowdock'
   end
 
@@ -22,7 +22,7 @@ class FlowdockService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 598aca5e06d28bf517fbfbdb935efe287a7b0a6e..f271e1f1739c9186b4e8a946553dde56f14da48b 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -12,7 +12,7 @@ class GemnasiumService < Service
     'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
   end
 
-  def to_param
+  def self.to_param
     'gemnasium'
   end
 
@@ -23,7 +23,7 @@ class GemnasiumService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 6bd8d4ec5682fd9395989fd1cf76b338a4fe854e..ad4eb9536e1157ba3a0141e07b057a71e7dcca44 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService
 
   default_value_for :default, true
 
-  def to_param
+  def self.to_param
     'gitlab'
   end
 
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 915f6fed74c01cf357c81512a7f0341ee63b7bcc..72da219df28bf2ca8aea5569f02c5617f96dd029 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -27,7 +27,7 @@ class HipchatService < Service
     'Private group chat and IM'
   end
 
-  def to_param
+  def self.to_param
     'hipchat'
   end
 
@@ -45,7 +45,7 @@ class HipchatService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push issue confidential_issue merge_request note tag_push build)
   end
 
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 7355918feab407b6c52ad74f54d97e0e1e9b8570..5d93064f9b311af129db78d44cce4b4efa250114 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -17,11 +17,11 @@ class IrkerService < Service
     'gateway.'
   end
 
-  def to_param
+  def self.to_param
     'irker'
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index bce2cdd55165ee3060e1eda1bbeaa930ae0cde22..9e65fdbf9d66185e4dd918cd37f0255312694f71 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -57,7 +57,7 @@ class IssueTrackerService < Service
     end
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2d969d2fcb66bf7539df1bab914cf349b5f7af67..80d002f9c320281f08b6829f8e71842fa9d86c59 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -12,7 +12,7 @@ class JiraService < IssueTrackerService
   # 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
+  def self.supported_events
     %w(commit merge_request)
   end
 
@@ -60,9 +60,9 @@ class JiraService < IssueTrackerService
   end
 
   def help
-    'You need to configure JIRA before enabling this service. For more details
+    "You need to configure JIRA before enabling this service. For more details
     read the
-    [JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).'
+    [JIRA service documentation](#{help_page_url('project_services/jira')})."
   end
 
   def title
@@ -81,7 +81,7 @@ class JiraService < IssueTrackerService
     end
   end
 
-  def to_param
+  def self.to_param
     'jira'
   end
 
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 085125ca9dce64662005f3ca0a963699c5ddefd2..fa3cedc4354bbdfcaab1e9f4a43683b97079d6e2 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -52,7 +52,7 @@ class KubernetesService < DeploymentService
     'deployments with `app=$CI_ENVIRONMENT_SLUG`'
   end
 
-  def to_param
+  def self.to_param
     'kubernetes'
   end
 
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index ee8a0b5527561b2f66793ba3ded8b7ec47da7d26..4ebc5318da1f36c14a4bdedf1b566cfa2530d608 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService
     'Receive event notifications in Mattermost'
   end
 
-  def to_param
+  def self.to_param
     'mattermost'
   end
 
@@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService
   end
 
   def default_channel_placeholder
-    "#town-square"
+    "town-square"
   end
 end
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 2cb481182d7073fb7461481661a3416e94f0ffa5..b0f7a42f9a3abe5932c0530028405281a8d37e06 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
     "Perform common operations on GitLab in Mattermost"
   end
 
-  def to_param
+  def self.to_param
     'mattermost_slash_commands'
   end
 
@@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
     [false, e.message]
   end
 
-  def list_teams(user)
-    Mattermost::Team.new(user).all
+  def list_teams(current_user)
+    [Mattermost::Team.new(current_user).all, nil]
   rescue Mattermost::Error => e
     [[], e.message]
   end
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 745f9bd1b43f492f9d1c3fb999e901ba7dc73d84..ac617f409d9417bbf4cbdce6e37c27d1ada590ef 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -15,11 +15,11 @@ class PipelinesEmailService < Service
     'Email the pipelines status to a list of recipients.'
   end
 
-  def to_param
+  def self.to_param
     'pipelines_email'
   end
 
-  def supported_events
+  def self.supported_events
     %w[pipeline]
   end
 
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 5301f9fa0ff600aa49ef7c5b7d68a1ea89ee2687..9cc642591f4364104ad77e5ac253fe5dbaf10761 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -14,7 +14,7 @@ class PivotaltrackerService < Service
     'Project Management Software (Source Commits Endpoint)'
   end
 
-  def to_param
+  def self.to_param
     'pivotaltracker'
   end
 
@@ -34,7 +34,7 @@ class PivotaltrackerService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 3dd878e4c7d77356dcc67c684d5968a6fc1f1718..a963d27a37652e491cd712115364babbc52f942d 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -13,7 +13,7 @@ class PushoverService < Service
     'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
   end
 
-  def to_param
+  def self.to_param
     'pushover'
   end
 
@@ -61,7 +61,7 @@ class PushoverService < Service
     ]
   end
 
-  def supported_events
+  def self.supported_events
     %w(push)
   end
 
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index f9da273cf0849ea24bb423080bf861cdb4e8f27e..6acf611eba581c9a9e4322b3232db46553f8639c 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService
     end
   end
 
-  def to_param
+  def self.to_param
     'redmine'
   end
 end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 76d233a3cca24dd2930f53b65c5b3e93196eb888..f77d2d7c60ba925c6eaba1ce41e777b85dc9f03d 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -7,7 +7,7 @@ class SlackService < ChatNotificationService
     'Receive event notifications in Slack'
   end
 
-  def to_param
+  def self.to_param
     'slack'
   end
 
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 5a7cc0fb329799c90d25c92f6551f2b61cf7aa91..c34991e426287da2b74ae9ae296d5f2c5dfe2e2c 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService
     "Perform common operations on GitLab in Slack"
   end
 
-  def to_param
+  def self.to_param
     'slack_slash_commands'
   end
 
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 6726082048fa579298792a824fbb87ee819410b0..cbaffb8ce48558370314be5056c0c94b9ec6ceb2 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -43,14 +43,10 @@ class TeamcityService < CiService
     'requests build, that setting is in the vsc root advanced settings.'
   end
 
-  def to_param
+  def self.to_param
     'teamcity'
   end
 
-  def supported_events
-    %w(push)
-  end
-
   def fields
     [
       { type: 'text', name: 'teamcity_url',
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 25b5d7776411273af6aabdc6f7766113cb2b9441..9bb456eee24755149b1e6025c2955a5525d3b4d3 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet
 
   participant :author
   participant :notes_with_associations
+
+  def check_for_spam?
+    super && project.public?
+  end
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 43dba86e5ed4afa212b4c4c741a60df0ea235b0a..7cf09c52bf4a032ea5d09c377f303474d3ea5a8f 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -5,7 +5,7 @@ class Repository
 
   attr_accessor :path_with_namespace, :project
 
-  class CommitError < StandardError; end
+  CommitError = Class.new(StandardError)
 
   # Methods that cache data from the Git repository.
   #
@@ -64,10 +64,6 @@ class Repository
     @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
   end
 
-  def update_autocrlf_option
-    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
-  end
-
   # Return absolute path to repository
   def path_to_repo
     @path_to_repo ||= File.expand_path(
@@ -168,63 +164,46 @@ class Repository
     tags.find { |tag| tag.name == name }
   end
 
-  def add_branch(user, branch_name, target)
-    oldrev = Gitlab::Git::BLANK_SHA
-    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
-    target = commit(target).try(:id)
+  def add_branch(user, branch_name, ref)
+    newrev = commit(ref).try(:sha)
 
-    return false unless target
+    return false unless newrev
 
-    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
-      update_ref!(ref, target, oldrev)
-    end
+    GitOperationService.new(user, self).add_branch(branch_name, newrev)
 
     after_create_branch
     find_branch(branch_name)
   end
 
   def add_tag(user, tag_name, target, message = nil)
-    oldrev = Gitlab::Git::BLANK_SHA
-    ref    = Gitlab::Git::TAG_REF_PREFIX + tag_name
-    target = commit(target).try(:id)
-
-    return false unless target
-
+    newrev = commit(target).try(:id)
     options = { message: message, tagger: user_to_committer(user) } if message
 
-    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
+    return false unless newrev
+
+    GitOperationService.new(user, self).add_tag(tag_name, newrev, options)
 
     find_tag(tag_name)
   end
 
   def rm_branch(user, branch_name)
     before_remove_branch
-
     branch = find_branch(branch_name)
-    oldrev = branch.try(:dereferenced_target).try(:id)
-    newrev = Gitlab::Git::BLANK_SHA
-    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
 
-    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
-      update_ref!(ref, newrev, oldrev)
-    end
+    GitOperationService.new(user, self).rm_branch(branch)
 
     after_remove_branch
     true
   end
 
-  def rm_tag(tag_name)
+  def rm_tag(user, tag_name)
     before_remove_tag
+    tag = find_tag(tag_name)
 
-    begin
-      rugged.tags.delete(tag_name)
-      true
-    rescue Rugged::ReferenceError
-      false
-    end
+    GitOperationService.new(user, self).rm_tag(tag)
+
+    after_remove_tag
+    true
   end
 
   def ref_names
@@ -241,21 +220,6 @@ class Repository
     false
   end
 
-  def update_ref!(name, newrev, oldrev)
-    # We use 'git update-ref' because libgit2/rugged currently does not
-    # offer 'compare and swap' ref updates. Without compare-and-swap we can
-    # (and have!) accidentally reset the ref to an earlier state, clobbering
-    # commits. See also https://github.com/libgit2/libgit2/issues/1534.
-    command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
-    _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
-      stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
-    end
-
-    return if status.zero?
-
-    raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.")
-  end
-
   # Makes sure a commit is kept around when Git garbage collection runs.
   # Git GC will delete commits from the repository that are no longer in any
   # branches or tags, but we want to keep some of these commits around, for
@@ -435,6 +399,11 @@ class Repository
     repository_event(:remove_tag)
   end
 
+  # Runs code after removing a tag.
+  def after_remove_tag
+    expire_tags_cache
+  end
+
   def before_import
     expire_content_cache
   end
@@ -779,121 +748,132 @@ class Repository
     @tags ||= raw_repository.tags
   end
 
-  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
-    update_branch_with_hooks(user, branch) do |ref|
-      options = {
-        commit: {
-          branch: ref,
-          message: message,
-          update_ref: false
-        }
-      }
+  # rubocop:disable Metrics/ParameterLists
+  def commit_dir(
+    user, path,
+    message:, branch_name:,
+    author_email: nil, author_name: nil,
+    start_branch_name: nil, start_project: project)
+    check_tree_entry_for_dir(branch_name, path)
 
-      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
-
-      raw_repository.mkdir(path, options)
+    if start_branch_name
+      start_project.repository.
+        check_tree_entry_for_dir(start_branch_name, path)
     end
-  end
 
-  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
-    update_branch_with_hooks(user, branch) do |ref|
-      options = {
-        commit: {
-          branch: ref,
-          message: message,
-          update_ref: false
-        },
-        file: {
-          content: content,
-          path: path,
-          update: update
-        }
-      }
-
-      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
-
-      Gitlab::Git::Blob.commit(raw_repository, options)
-    end
-  end
-
-  def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
-    update_branch_with_hooks(user, branch) do |ref|
-      options = {
-        commit: {
-          branch: ref,
-          message: message,
-          update_ref: false
-        },
-        file: {
-          content: content,
-          path: path,
-          update: true
-        }
-      }
-
-      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
-
-      if previous_path && previous_path != path
-        options[:file][:previous_path] = previous_path
-        Gitlab::Git::Blob.rename(raw_repository, options)
-      else
-        Gitlab::Git::Blob.commit(raw_repository, options)
+    commit_file(
+      user,
+      "#{path}/.gitkeep",
+      '',
+      message: message,
+      branch_name: branch_name,
+      update: false,
+      author_email: author_email,
+      author_name: author_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project)
+  end
+  # rubocop:enable Metrics/ParameterLists
+
+  # rubocop:disable Metrics/ParameterLists
+  def commit_file(
+    user, path, content,
+    message:, branch_name:, update: true,
+    author_email: nil, author_name: nil,
+    start_branch_name: nil, start_project: project)
+    unless update
+      error_message = "Filename already exists; update not allowed"
+
+      if tree_entry_at(branch_name, path)
+        raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
       end
-    end
-  end
-
-  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
-    update_branch_with_hooks(user, branch) do |ref|
-      options = {
-        commit: {
-          branch: ref,
-          message: message,
-          update_ref: false
-        },
-        file: {
-          path: path
-        }
-      }
 
-      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
-
-      Gitlab::Git::Blob.remove(raw_repository, options)
+      if start_branch_name &&
+          start_project.repository.tree_entry_at(start_branch_name, path)
+        raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
+      end
     end
-  end
 
-  def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil)
-    update_branch_with_hooks(user, branch) do |ref|
+    multi_action(
+      user: user,
+      message: message,
+      branch_name: branch_name,
+      author_email: author_email,
+      author_name: author_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project,
+      actions: [{ action: :create,
+                  file_path: path,
+                  content: content }])
+  end
+  # rubocop:enable Metrics/ParameterLists
+
+  # rubocop:disable Metrics/ParameterLists
+  def update_file(
+    user, path, content,
+    message:, branch_name:, previous_path:,
+    author_email: nil, author_name: nil,
+    start_branch_name: nil, start_project: project)
+    action = if previous_path && previous_path != path
+               :move
+             else
+               :update
+             end
+
+    multi_action(
+      user: user,
+      message: message,
+      branch_name: branch_name,
+      author_email: author_email,
+      author_name: author_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project,
+      actions: [{ action: action,
+                  file_path: path,
+                  content: content,
+                  previous_path: previous_path }])
+  end
+  # rubocop:enable Metrics/ParameterLists
+
+  # rubocop:disable Metrics/ParameterLists
+  def remove_file(
+    user, path,
+    message:, branch_name:,
+    author_email: nil, author_name: nil,
+    start_branch_name: nil, start_project: project)
+    multi_action(
+      user: user,
+      message: message,
+      branch_name: branch_name,
+      author_email: author_email,
+      author_name: author_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project,
+      actions: [{ action: :delete,
+                  file_path: path }])
+  end
+  # rubocop:enable Metrics/ParameterLists
+
+  # rubocop:disable Metrics/ParameterLists
+  def multi_action(
+    user:, branch_name:, message:, actions:,
+    author_email: nil, author_name: nil,
+    start_branch_name: nil, start_project: project)
+    GitOperationService.new(user, self).with_branch(
+      branch_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project) do |start_commit|
       index = rugged.index
-      parents = []
-      branch = find_branch(ref)
 
-      if branch
-        last_commit = branch.dereferenced_target
-        index.read_tree(last_commit.raw_commit.tree)
-        parents = [last_commit.sha]
-      end
+      parents = if start_commit
+                  index.read_tree(start_commit.raw_commit.tree)
+                  [start_commit.sha]
+                else
+                  []
+                end
 
-      actions.each do |action|
-        case action[:action]
-        when :create, :update, :move
-          mode =
-            case action[:action]
-            when :update
-              index.get(action[:file_path])[:mode]
-            when :move
-              index.get(action[:previous_path])[:mode]
-            end
-          mode ||= 0o100644
-
-          index.remove(action[:previous_path]) if action[:action] == :move
-
-          content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content]
-          oid = rugged.write(content, :blob)
-
-          index.add(path: action[:file_path], oid: oid, mode: mode)
-        when :delete
-          index.remove(action[:file_path])
-        end
+      actions.each do |act|
+        git_action(index, act)
       end
 
       options = {
@@ -906,6 +886,7 @@ class Repository
       Rugged::Commit.create(rugged, options)
     end
   end
+  # rubocop:enable Metrics/ParameterLists
 
   def get_committer_and_author(user, email: nil, name: nil)
     committer = user_to_committer(user)
@@ -918,7 +899,7 @@ class Repository
   end
 
   def user_to_committer(user)
-    Gitlab::Git::committer_hash(email: user.email, name: user.name)
+    Gitlab::Git.committer_hash(email: user.email, name: user.name)
   end
 
   def can_be_merged?(source_sha, target_branch)
@@ -932,17 +913,18 @@ class Repository
     end
   end
 
-  def merge(user, merge_request, options = {})
-    our_commit = rugged.branches[merge_request.target_branch].target
-    their_commit = rugged.lookup(merge_request.diff_head_sha)
+  def merge(user, source, merge_request, options = {})
+    GitOperationService.new(user, self).with_branch(
+      merge_request.target_branch) do |start_commit|
+      our_commit = start_commit.sha
+      their_commit = source
 
-    raise "Invalid merge target" if our_commit.nil?
-    raise "Invalid merge source" if their_commit.nil?
+      raise 'Invalid merge target' unless our_commit
+      raise 'Invalid merge source' unless their_commit
 
-    merge_index = rugged.merge_commits(our_commit, their_commit)
-    return false if merge_index.conflicts?
+      merge_index = rugged.merge_commits(our_commit, their_commit)
+      break if merge_index.conflicts?
 
-    update_branch_with_hooks(user, merge_request.target_branch) do
       actual_options = options.merge(
         parents: [our_commit, their_commit],
         tree: merge_index.write_tree(rugged),
@@ -952,34 +934,48 @@ class Repository
       merge_request.update(in_progress_merge_commit_sha: commit_id)
       commit_id
     end
+  rescue Repository::CommitError # when merge_index.conflicts?
+    false
   end
 
-  def revert(user, commit, base_branch, revert_tree_id = nil)
-    source_sha = find_branch(base_branch).dereferenced_target.sha
-    revert_tree_id ||= check_revert_content(commit, base_branch)
+  def revert(
+    user, commit, branch_name, revert_tree_id = nil,
+    start_branch_name: nil, start_project: project)
+    revert_tree_id ||= check_revert_content(commit, branch_name)
 
     return false unless revert_tree_id
 
-    update_branch_with_hooks(user, base_branch) do
+    GitOperationService.new(user, self).with_branch(
+      branch_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project) do |start_commit|
+
       committer = user_to_committer(user)
-      source_sha = Rugged::Commit.create(rugged,
+
+      Rugged::Commit.create(rugged,
         message: commit.revert_message(user),
         author: committer,
         committer: committer,
         tree: revert_tree_id,
-        parents: [rugged.lookup(source_sha)])
+        parents: [start_commit.sha])
     end
   end
 
-  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
-    source_sha = find_branch(base_branch).dereferenced_target.sha
-    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
+  def cherry_pick(
+    user, commit, branch_name, cherry_pick_tree_id = nil,
+    start_branch_name: nil, start_project: project)
+    cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name)
 
     return false unless cherry_pick_tree_id
 
-    update_branch_with_hooks(user, base_branch) do
+    GitOperationService.new(user, self).with_branch(
+      branch_name,
+      start_branch_name: start_branch_name,
+      start_project: start_project) do |start_commit|
+
       committer = user_to_committer(user)
-      source_sha = Rugged::Commit.create(rugged,
+
+      Rugged::Commit.create(rugged,
         message: commit.message,
         author: {
           email: commit.author_email,
@@ -988,22 +984,22 @@ class Repository
         },
         committer: committer,
         tree: cherry_pick_tree_id,
-        parents: [rugged.lookup(source_sha)])
+        parents: [start_commit.sha])
     end
   end
 
-  def resolve_conflicts(user, branch, params)
-    update_branch_with_hooks(user, branch) do
+  def resolve_conflicts(user, branch_name, params)
+    GitOperationService.new(user, self).with_branch(branch_name) do
       committer = user_to_committer(user)
 
       Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
     end
   end
 
-  def check_revert_content(commit, base_branch)
-    source_sha = find_branch(base_branch).dereferenced_target.sha
-    args       = [commit.id, source_sha]
-    args << { mainline: 1 } if commit.merge_commit?
+  def check_revert_content(target_commit, branch_name)
+    source_sha = commit(branch_name).sha
+    args       = [target_commit.sha, source_sha]
+    args << { mainline: 1 } if target_commit.merge_commit?
 
     revert_index = rugged.revert_commit(*args)
     return false if revert_index.conflicts?
@@ -1014,10 +1010,10 @@ class Repository
     tree_id
   end
 
-  def check_cherry_pick_content(commit, base_branch)
-    source_sha = find_branch(base_branch).dereferenced_target.sha
-    args       = [commit.id, source_sha]
-    args << 1 if commit.merge_commit?
+  def check_cherry_pick_content(target_commit, branch_name)
+    source_sha = commit(branch_name).sha
+    args       = [target_commit.sha, source_sha]
+    args << 1 if target_commit.merge_commit?
 
     cherry_pick_index = rugged.cherrypick_commit(*args)
     return false if cherry_pick_index.conflicts?
@@ -1075,6 +1071,28 @@ class Repository
     Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip)
   end
 
+  def with_repo_branch_commit(start_repository, start_branch_name)
+    branch_name_or_sha =
+      if start_repository == self
+        start_branch_name
+      else
+        tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
+
+        fetch_ref(
+          start_repository.path_to_repo,
+          "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
+          tmp_ref
+        )
+
+        start_repository.commit(start_branch_name).sha
+      end
+
+    yield(commit(branch_name_or_sha))
+
+  ensure
+    rugged.references.delete(tmp_ref) if tmp_ref
+  end
+
   def fetch_ref(source_path, source_ref, target_ref)
     args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
     Gitlab::Popen.popen(args, path_to_repo)
@@ -1084,39 +1102,6 @@ class Repository
     fetch_ref(path_to_repo, ref, ref_path)
   end
 
-  def update_branch_with_hooks(current_user, branch)
-    update_autocrlf_option
-
-    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
-    target_branch = find_branch(branch)
-    was_empty = empty?
-
-    # Make commit
-    newrev = yield(ref)
-
-    unless newrev
-      raise CommitError.new('Failed to create commit')
-    end
-
-    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
-      oldrev = Gitlab::Git::BLANK_SHA
-    else
-      oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
-    end
-
-    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
-      update_ref!(ref, newrev, oldrev)
-
-      if was_empty || !target_branch
-        # If repo was empty expire cache
-        after_create if was_empty
-        after_create_branch
-      end
-    end
-
-    newrev
-  end
-
   def ls_files(ref)
     actual_ref = ref || root_ref
     raw_repository.ls_files(actual_ref)
@@ -1175,8 +1160,76 @@ class Repository
     end
   end
 
+  protected
+
+  def tree_entry_at(branch_name, path)
+    branch_exists?(branch_name) &&
+      # tree_entry is private
+      raw_repository.send(:tree_entry, commit(branch_name), path)
+  end
+
+  def check_tree_entry_for_dir(branch_name, path)
+    return unless branch_exists?(branch_name)
+
+    entry = tree_entry_at(branch_name, path)
+
+    return unless entry
+
+    if entry[:type] == :blob
+      raise Gitlab::Git::Repository::InvalidBlobName.new(
+        "Directory already exists as a file")
+    else
+      raise Gitlab::Git::Repository::InvalidBlobName.new(
+        "Directory already exists")
+    end
+  end
+
   private
 
+  def git_action(index, action)
+    path = normalize_path(action[:file_path])
+
+    if action[:action] == :move
+      previous_path = normalize_path(action[:previous_path])
+    end
+
+    case action[:action]
+    when :create, :update, :move
+      mode =
+        case action[:action]
+        when :update
+          index.get(path)[:mode]
+        when :move
+          index.get(previous_path)[:mode]
+        end
+      mode ||= 0o100644
+
+      index.remove(previous_path) if action[:action] == :move
+
+      content = if action[:encoding] == 'base64'
+                  Base64.decode64(action[:content])
+                else
+                  action[:content]
+                end
+
+      oid = rugged.write(content, :blob)
+
+      index.add(path: path, oid: oid, mode: mode)
+    when :delete
+      index.remove(path)
+    end
+  end
+
+  def normalize_path(path)
+    pathname = Gitlab::Git::PathHelper.normalize_path(path)
+
+    if pathname.each_filename.include?('..')
+      raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
+    end
+
+    pathname.to_s
+  end
+
   def refs_directory_exists?
     return false unless path_with_namespace
 
@@ -1188,7 +1241,18 @@ class Repository
   end
 
   def tags_sorted_by_committed_date
-    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
+    tags.sort_by do |tag|
+      # Annotated tags can point to any object (e.g. a blob), but generally
+      # tags point to a commit. If we don't have a commit, then just default
+      # to putting the tag at the end of the list.
+      target = tag.dereferenced_target
+
+      if target
+        target.committed_date
+      else
+        Time.now
+      end
+    end
   end
 
   def keep_around_ref_name(sha)
diff --git a/app/models/route.rb b/app/models/route.rb
index caf596efa79627bca90455150e15dca27be730e2..dd171fdb06934fd9fb5cf4b04ae61baf1322a6be 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,15 +8,16 @@ class Route < ActiveRecord::Base
     presence: true,
     uniqueness: { case_sensitive: false }
 
-  after_update :rename_children, if: :path_changed?
+  after_update :rename_descendants, if: :path_changed?
 
-  def rename_children
+  def rename_descendants
     # We update each row separately because MySQL does not have regexp_replace.
     # rubocop:disable Rails/FindEach
     Route.where('path LIKE ?', "#{path_was}/%").each do |route|
       # Note that update column skips validation and callbacks.
-      # We need this to avoid recursive call of rename_children method
+      # We need this to avoid recursive call of rename_descendants method
       route.update_column(:path, route.path.sub(path_was, path))
     end
+    # rubocop:enable Rails/FindEach
   end
 end
diff --git a/app/models/service.rb b/app/models/service.rb
index 19ef3ba9c2382a17442f7a790332cc7b42024d53..043be222f3a40155b291da53b4a575d3f325e4b3 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -76,6 +76,11 @@ class Service < ActiveRecord::Base
 
   def to_param
     # implement inside child
+    self.class.to_param
+  end
+
+  def self.to_param
+    raise NotImplementedError
   end
 
   def fields
@@ -92,7 +97,11 @@ class Service < ActiveRecord::Base
   end
 
   def event_names
-    supported_events.map { |event| "#{event}_events" }
+    self.class.event_names
+  end
+
+  def self.event_names
+    self.supported_events.map { |event| "#{event}_events" }
   end
 
   def event_field(event)
@@ -104,6 +113,10 @@ class Service < ActiveRecord::Base
   end
 
   def supported_events
+    self.class.supported_events
+  end
+
+  def self.supported_events
     %w(push tag_push issue confidential_issue merge_request wiki_page)
   end
 
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 771a73505562bdd5a359fe606360c56ee2cd1fc0..2665a7249a353b1f2a14c69d78222ad1bd47b527 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base
   include Sortable
   include Awardable
   include Mentionable
+  include Spammable
 
   cache_markdown_field :title, pipeline: :single_line
   cache_markdown_field :content
@@ -17,7 +18,7 @@ class Snippet < ActiveRecord::Base
     default_content_html_invalidator || file_name_changed?
   end
 
-  default_value_for :visibility_level, Snippet::PRIVATE
+  default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility }
 
   belongs_to :author, class_name: 'User'
   belongs_to :project
@@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base
   participant :author
   participant :notes_with_associations
 
+  attr_spammable :title, spam_title: true
+  attr_spammable :content, spam_description: true
+
   def self.reference_prefix
     '$'
   end
@@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base
     notes.includes(:author)
   end
 
+  def check_for_spam?
+    public?
+  end
+
+  def spammable_entity_type
+    'snippet'
+  end
+
   class << self
     # Searches for snippets with a matching title or file name.
     #
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 4c99aa0d3be56bfaeadaf054ecb6e47b8b730fb7..2adf494ce116ce06988cefad903c9cb689be41bc 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base
 
   def target_reference
     if for_commit?
-      target.short_id
+      target.reference_link_text(full: true)
     else
-      target.to_reference
+      target.to_reference(full: true)
     end
   end
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 06dd98a318858f85a3a845a73874efa0b99cd3fa..54f5388eb2c215ec951ce06ccd298614ced47dd7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -179,8 +179,8 @@ class User < ActiveRecord::Base
   scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
   scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
   scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
-  scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) }
-  scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) }
+  scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) }
+  scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) }
 
   def self.with_two_factor
     joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
@@ -439,6 +439,15 @@ class User < ActiveRecord::Base
     Group.where("namespaces.id IN (#{union.to_sql})")
   end
 
+  def nested_groups
+    Group.member_descendants(id)
+  end
+
+  def nested_projects
+    Project.joins(:namespace).where('namespaces.parent_id IS NOT NULL').
+      member_descendants(id)
+  end
+
   def refresh_authorized_projects
     Users::RefreshAuthorizedProjectsService.new(self).execute
   end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 7b1752df0e1a230f43ed10ce41a8360de16cb017..8b25332b73ceeeb2635ae78d1d0665b36dca8017 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,8 +1,6 @@
 module Ci
   class BuildPolicy < CommitStatusPolicy
     def rules
-      can! :read_build if @subject.project.public_builds?
-
       super
 
       # If we can't read build we should also not have that
diff --git a/app/presenters/README.md b/app/presenters/README.md
index 3edd63451e799e6ed2e1b920381d224acb283efe..a4d592b54d6e963c14dbe7c55203f275caec71f6 100644
--- a/app/presenters/README.md
+++ b/app/presenters/README.md
@@ -113,7 +113,7 @@ detects the presenter based on the presented subject's class.
 class Projects::LabelsController < Projects::ApplicationController
   def edit
     @label = Gitlab::View::Presenter::Factory
-      .new(@label, user: current_user)
+      .new(@label, current_user: current_user)
       .fabricate!
   end
 end
@@ -132,7 +132,7 @@ and then in the controller:
 ```ruby
 class Projects::LabelsController < Projects::ApplicationController
   def edit
-    @label = @label.present(user: current_user)
+    @label = @label.present(current_user: current_user)
   end
 end
 ```
@@ -147,7 +147,7 @@ end
 You can also present the model in the view:
 
 ```ruby
-- label = @label.present(current_user)
+- label = @label.present(current_user: current_user)
 
 %div{ class: label.text_color }
   = render partial: label, label: label
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
index de9a181db90f0303e3f70385aa5332dc2b1fae8b..311ee9c96be401994d4305bd3eb04ccf19ff5842 100644
--- a/app/serializers/base_serializer.rb
+++ b/app/serializers/base_serializer.rb
@@ -6,6 +6,7 @@ class BaseSerializer
   def represent(resource, opts = {})
     self.class.entity_class
       .represent(resource, opts.merge(request: @request))
+      .as_json
   end
 
   def self.entity(entity_class)
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index d04a4990cb0861990204e6f22efc356cbaf855ec..61f0f11d7d23ac06b86e2161689385b1e7bf2dae 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -40,10 +40,12 @@ class PipelineEntity < Grape::Entity
     end
 
     expose :path do |pipeline|
-      namespace_project_tree_path(
-        pipeline.project.namespace,
-        pipeline.project,
-        id: pipeline.ref)
+      if pipeline.ref
+        namespace_project_tree_path(
+          pipeline.project.namespace,
+          pipeline.project,
+          id: pipeline.ref)
+      end
     end
 
     expose :tag?, as: :tag
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index cfa86cc2553f228f816f14e66d3515ee23796d08..b2de6c5832ec398a71b9a1a291e698384ced207e 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -1,9 +1,10 @@
 class PipelineSerializer < BaseSerializer
-  entity PipelineEntity
   class InvalidResourceError < StandardError; end
   include API::Helpers::Pagination
   Struct.new('Pagination', :request, :response)
 
+  entity PipelineEntity
+
   def represent(resource, opts = {})
     if paginated?
       raise InvalidResourceError unless resource.respond_to?(:page)
diff --git a/app/services/application_settings/base_service.rb b/app/services/application_settings/base_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2bcc7d7c08b5ea994daaf366f30eeb4ef79ccfba
--- /dev/null
+++ b/app/services/application_settings/base_service.rb
@@ -0,0 +1,7 @@
+module ApplicationSettings
+  class BaseService < ::BaseService
+    def initialize(application_setting, user, params = {})
+      @application_setting, @current_user, @params = application_setting, user, params.dup
+    end
+  end
+end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..61589a07250b031b25cd680e6ecaae7f03e7c00a
--- /dev/null
+++ b/app/services/application_settings/update_service.rb
@@ -0,0 +1,7 @@
+module ApplicationSettings
+  class UpdateService < ApplicationSettings::BaseService
+    def execute
+      @application_setting.update(@params)
+    end
+  end
+end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index c00c5aebf57e776dc92e6439fc341e991dea6782..5cb7a86a5ee80c583341940794fade34b093a6cb 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -61,7 +61,7 @@ module Auth
     end
 
     def process_repository_access(type, name, actions)
-      requested_project = Project.find_with_namespace(name)
+      requested_project = Project.find_by_full_path(name)
       return unless requested_project
 
       actions = actions.select do |action|
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 74b5ebf372b10523f873c7d4d2d232f1ff0d2e62..6f03bf2be13cf5e5250f12fc1cd9871203906472 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -2,48 +2,72 @@ module Ci
   # This class responsible for assigning
   # proper pending build to runner on runner API request
   class RegisterBuildService
-    def execute(current_runner)
-      builds = Ci::Build.pending.unstarted
+    include Gitlab::CurrentSettings
 
+    attr_reader :runner
+
+    Result = Struct.new(:build, :valid?)
+
+    def initialize(runner)
+      @runner = runner
+    end
+
+    def execute
       builds =
-        if current_runner.shared?
-          builds.
-            # don't run projects which have not enabled shared runners and builds
-            joins(:project).where(projects: { shared_runners_enabled: true }).
-            joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
-
-            # this returns builds that are ordered by number of running builds
-            # we prefer projects that don't use shared runners at all
-            joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
-            where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
-            order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+        if runner.shared?
+          builds_for_shared_runner
         else
-          # do run projects which are only assigned to this runner (FIFO)
-          builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
+          builds_for_specific_runner
         end
 
       build = builds.find do |build|
-        current_runner.can_pick?(build)
+        runner.can_pick?(build)
       end
 
       if build
         # In case when 2 runners try to assign the same build, second runner will be declined
         # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
-        build.runner_id = current_runner.id
+        build.runner_id = runner.id
         build.run!
       end
 
-      build
+      Result.new(build, true)
 
     rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
-      nil
+      Result.new(build, false)
     end
 
     private
 
+    def builds_for_shared_runner
+      new_builds.
+        # don't run projects which have not enabled shared runners and builds
+        joins(:project).where(projects: { shared_runners_enabled: true }).
+        joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
+        where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
+
+        # Implement fair scheduling
+        # this returns builds that are ordered by number of running builds
+        # we prefer projects that don't use shared runners at all
+        joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
+        order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+    end
+
+    def builds_for_specific_runner
+      new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC')
+    end
+
     def running_builds_for_shared_runners
       Ci::Build.running.where(runner: Ci::Runner.shared).
         group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
     end
+
+    def new_builds
+      Ci::Build.pending.unstarted
+    end
+
+    def shared_runner_build_limits_feature_enabled?
+      ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
+    end
   end
 end
diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb
index 2e901016666ca1a76cf9a2d6833057c34a74106f..152c8ae5006cd0e700dbec94260e63d37605997d 100644
--- a/app/services/ci/update_build_queue_service.rb
+++ b/app/services/ci/update_build_queue_service.rb
@@ -6,6 +6,14 @@ module Ci
           runner.tick_runner_queue
         end
       end
+
+      return unless build.project.shared_runners_enabled?
+
+      Ci::Runner.shared.each do |runner|
+        if runner.can_pick?(build)
+          runner.tick_runner_queue
+        end
+      end
     end
   end
 end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index 4d410f66c55c7227aa767da5ac509363a39d01fe..25e22f14e60b0c3d9cb3c0827cf76a6607578d33 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -4,7 +4,8 @@ module Commits
     class ChangeError < StandardError; end
 
     def execute
-      @source_project = params[:source_project] || @project
+      @start_project = params[:start_project] || @project
+      @start_branch = params[:start_branch]
       @target_branch = params[:target_branch]
       @commit = params[:commit]
       @create_merge_request = params[:create_merge_request].present?
@@ -25,13 +26,28 @@ module Commits
     def commit_change(action)
       raise NotImplementedError unless repository.respond_to?(action)
 
-      into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
-      tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
+      if @create_merge_request
+        into = @commit.public_send("#{action}_branch_name")
+        tree_branch = @start_branch
+      else
+        into = tree_branch = @target_branch
+      end
+
+      tree_id = repository.public_send(
+        "check_#{action}_content", @commit, tree_branch)
 
       if tree_id
-        create_target_branch(into) if @create_merge_request
+        validate_target_branch(into) if @create_merge_request
+
+        repository.public_send(
+          action,
+          current_user,
+          @commit,
+          into,
+          tree_id,
+          start_project: @start_project,
+          start_branch_name: @start_branch)
 
-        repository.public_send(action, current_user, @commit, into, tree_id)
         success
       else
         error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
@@ -50,12 +66,12 @@ module Commits
       true
     end
 
-    def create_target_branch(new_branch)
+    def validate_target_branch(new_branch)
       # Temporary branch exists and contains the change commit
-      return success if repository.find_branch(new_branch)
+      return if repository.find_branch(new_branch)
 
-      result = CreateBranchService.new(@project, current_user)
-                                  .execute(new_branch, @target_branch, source_project: @source_project)
+      result = ValidateNewBranchService.new(@project, current_user)
+        .execute(new_branch)
 
       if result[:status] == :error
         raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 5e8fafca98c7b71be4de700a42bff260960f5537..ab4c02a97a0efb9c39afd54c57b6bb181d10cc63 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,23 +3,27 @@ require 'securerandom'
 # Compare 2 branches for one repo or between repositories
 # and return Gitlab::Git::Compare object that responds to commits and diffs
 class CompareService
-  def execute(source_project, source_branch, target_project, target_branch, straight: false)
-    source_commit = source_project.commit(source_branch)
-    return unless source_commit
+  attr_reader :start_project, :start_branch_name
 
-    source_sha = source_commit.sha
+  def initialize(new_start_project, new_start_branch_name)
+    @start_project = new_start_project
+    @start_branch_name = new_start_branch_name
+  end
 
+  def execute(target_project, target_branch, straight: false)
     # If compare with other project we need to fetch ref first
-    unless target_project == source_project
-      random_string = SecureRandom.hex
+    target_project.repository.with_repo_branch_commit(
+      start_project.repository,
+      start_branch_name) do |commit|
+      break unless commit
 
-      target_project.repository.fetch_ref(
-        source_project.repository.path_to_repo,
-        "refs/heads/#{source_branch}",
-        "refs/tmp/#{random_string}/head"
-      )
+      compare(commit.sha, target_project, target_branch, straight)
     end
+  end
+
+  private
 
+  def compare(source_sha, target_project, target_branch, straight)
     raw_compare = Gitlab::Git::Compare.new(
       target_project.repository.raw_repository,
       target_branch,
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index e004a3034960764785f0697f8f31ea531ea1fa7f..77459d8779ddf9ad3188e35de30bee21b830deaa 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,31 +1,11 @@
 class CreateBranchService < BaseService
-  def execute(branch_name, ref, source_project: @project)
-    valid_branch = Gitlab::GitRefValidator.validate(branch_name)
+  def execute(branch_name, ref)
+    result = ValidateNewBranchService.new(project, current_user)
+      .execute(branch_name)
 
-    unless valid_branch
-      return error('Branch name is invalid')
-    end
-
-    repository = project.repository
-    existing_branch = repository.find_branch(branch_name)
-
-    if existing_branch
-      return error('Branch already exists')
-    end
-
-    new_branch = if source_project != @project
-                   repository.fetch_ref(
-                     source_project.repository.path_to_repo,
-                     "refs/heads/#{ref}",
-                     "refs/heads/#{branch_name}"
-                   )
-
-                   repository.after_create_branch
+    return result if result[:status] == :error
 
-                   repository.find_branch(branch_name)
-                 else
-                   repository.add_branch(current_user, branch_name, ref)
-                 end
+    new_branch = repository.add_branch(current_user, branch_name, ref)
 
     if new_branch
       success(new_branch)
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 95cc9baf406c2fa99ea7e8105d88d0ad0af8e841..14f5ba064ffdab0835c3851c7b85561b9c2d9568 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -1,5 +1,8 @@
 class CreateSnippetService < BaseService
   def execute
+    request = params.delete(:request)
+    api = params.delete(:api)
+
     snippet = if project
                 project.snippets.build(params)
               else
@@ -12,8 +15,12 @@ class CreateSnippetService < BaseService
     end
 
     snippet.author = current_user
+    snippet.spam = SpamService.new(snippet, request).check(api)
+
+    if snippet.save
+      UserAgentDetailService.new(snippet, request).create
+    end
 
-    snippet.save
     snippet
   end
 end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
index a44dee14a0f7da03051a6e86a6ff9592a2d5418e..9d4bffb93e9e182d69e2e1c1cf423ce9f1563835 100644
--- a/app/services/delete_tag_service.rb
+++ b/app/services/delete_tag_service.rb
@@ -7,7 +7,7 @@ class DeleteTagService < BaseService
       return error('No such tag', 404)
     end
 
-    if repository.rm_tag(tag_name)
+    if repository.rm_tag(current_user, tag_name)
       release = project.releases.find_by(tag: tag_name)
       release.destroy if release
 
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 9bd4bd464f7940253aeb7fd48014fdae3ae22cb6..0a25f56d24cb1bd7eb1a0188667e80a7c5dd3ab9 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -3,9 +3,9 @@ module Files
     class ValidationError < StandardError; end
 
     def execute
-      @source_project = params[:source_project] || @project
-      @source_branch = params[:source_branch]
-      @target_branch  = params[:target_branch]
+      @start_project = params[:start_project] || @project
+      @start_branch = params[:start_branch]
+      @target_branch = params[:target_branch]
 
       @commit_message = params[:commit_message]
       @file_path      = params[:file_path]
@@ -22,10 +22,8 @@ module Files
       # Validate parameters
       validate
 
-      # Create new branch if it different from source_branch
-      if different_branch?
-        create_target_branch
-      end
+      # Create new branch if it different from start_branch
+      validate_target_branch if different_branch?
 
       result = commit
       if result
@@ -40,7 +38,7 @@ module Files
     private
 
     def different_branch?
-      @source_branch != @target_branch || @source_project != @project
+      @start_branch != @target_branch || @start_project != @project
     end
 
     def file_has_changed?
@@ -61,22 +59,23 @@ module Files
       end
 
       unless project.empty_repo?
-        unless @source_project.repository.branch_names.include?(@source_branch)
+        unless @start_project.repository.branch_exists?(@start_branch)
           raise_error('You can only create or edit files when you are on a branch')
         end
 
         if different_branch?
-          if repository.branch_names.include?(@target_branch)
+          if repository.branch_exists?(@target_branch)
             raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes')
           end
         end
       end
     end
 
-    def create_target_branch
-      result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
+    def validate_target_branch
+      result = ValidateNewBranchService.new(project, current_user).
+        execute(@target_branch)
 
-      unless result[:status] == :success
+      if result[:status] == :error
         raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
       end
     end
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
index e5b4d60e46795f63ca960b6a96c64752e97c1b3d..858de5f0538abbd71f479f7a9298a5a8b5ee6556 100644
--- a/app/services/files/create_dir_service.rb
+++ b/app/services/files/create_dir_service.rb
@@ -1,7 +1,15 @@
 module Files
   class CreateDirService < Files::BaseService
     def commit
-      repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
+      repository.commit_dir(
+        current_user,
+        @file_path,
+        message: @commit_message,
+        branch_name: @target_branch,
+        author_email: @author_email,
+        author_name: @author_name,
+        start_project: @start_project,
+        start_branch_name: @start_branch)
     end
 
     def validate
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index b23576b9a284235fae658ebf54f7217ef7352060..88dd7bbaedbfe4ab261f1dd9918cfdf7e6391c5d 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,7 +1,17 @@
 module Files
   class CreateService < Files::BaseService
     def commit
-      repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name)
+      repository.commit_file(
+        current_user,
+        @file_path,
+        @file_content,
+        message: @commit_message,
+        branch_name: @target_branch,
+        update: false,
+        author_email: @author_email,
+        author_name: @author_name,
+        start_project: @start_project,
+        start_branch_name: @start_branch)
     end
 
     def validate
@@ -24,7 +34,7 @@ module Files
       unless project.empty_repo?
         @file_path.slice!(0) if @file_path.start_with?('/')
 
-        blob = repository.blob_at_branch(@source_branch, @file_path)
+        blob = repository.blob_at_branch(@start_branch, @file_path)
 
         if blob
           raise_error('Your changes could not be committed because a file with the same name already exists')
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 4f7e7a5baaac259d827981abe15b9e4333f492e8..50f0ffcac9fda47bb8fbfdcbd079b4d93a1b3be5 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,7 +1,15 @@
 module Files
   class DeleteService < Files::BaseService
     def commit
-      repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
+      repository.remove_file(
+        current_user,
+        @file_path,
+        message: @commit_message,
+        branch_name: @target_branch,
+        author_email: @author_email,
+        author_name: @author_name,
+        start_project: @start_project,
+        start_branch_name: @start_branch)
     end
   end
 end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index 54446e90007c2057c7b87476272aaed98f83abba..6ba868df04da17b66ce587338c950111e8106c1f 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -5,11 +5,13 @@ module Files
     def commit
       repository.multi_action(
         user: current_user,
-        branch: @target_branch,
         message: @commit_message,
+        branch_name: @target_branch,
         actions: params[:actions],
         author_email: @author_email,
-        author_name: @author_name
+        author_name: @author_name,
+        start_project: @start_project,
+        start_branch_name: @start_branch
       )
     end
 
@@ -61,7 +63,7 @@ module Files
     end
 
     def last_commit
-      Gitlab::Git::Commit.last_for_path(repository, @source_branch, @file_path)
+      Gitlab::Git::Commit.last_for_path(repository, @start_branch, @file_path)
     end
 
     def regex_check(file)
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 47a18e3e132e4608f6487abb8e0cd556bd2f897b..a71fe61a4b6a8c20e13fce5576399acf9dbdab01 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -4,11 +4,13 @@ module Files
 
     def commit
       repository.update_file(current_user, @file_path, @file_content,
-                             branch: @target_branch,
-                             previous_path: @previous_path,
                              message: @commit_message,
+                             branch_name: @target_branch,
+                             previous_path: @previous_path,
                              author_email: @author_email,
-                             author_name: @author_name)
+                             author_name: @author_name,
+                             start_project: @start_project,
+                             start_branch_name: @start_branch)
     end
 
     private
@@ -23,7 +25,7 @@ module Files
 
     def last_commit
       @last_commit ||= Gitlab::Git::Commit.
-        last_for_path(@source_project.repository, @source_branch, @file_path)
+        last_for_path(@start_project.repository, @start_branch, @file_path)
     end
   end
 end
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index 6cd3908d43ad38d9fc3f43763e6f8763cb9d4cfe..d222d1e63aa766113422ab087abfd02d0300cf9a 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -18,9 +18,9 @@ class GitHooksService
       end
     end
 
-    yield self
-
-    run_hook('post-receive')
+    yield(self).tap do
+      run_hook('post-receive')
+    end
   end
 
   private
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27bcc0476018b216c0a8b281f39015dc2dabe6c1
--- /dev/null
+++ b/app/services/git_operation_service.rb
@@ -0,0 +1,179 @@
+class GitOperationService
+  attr_reader :user, :repository
+
+  def initialize(new_user, new_repository)
+    @user = new_user
+    @repository = new_repository
+  end
+
+  def add_branch(branch_name, newrev)
+    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+    oldrev = Gitlab::Git::BLANK_SHA
+
+    update_ref_in_hooks(ref, newrev, oldrev)
+  end
+
+  def rm_branch(branch)
+    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
+    oldrev = branch.target
+    newrev = Gitlab::Git::BLANK_SHA
+
+    update_ref_in_hooks(ref, newrev, oldrev)
+  end
+
+  def add_tag(tag_name, newrev, options = {})
+    ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+    oldrev = Gitlab::Git::BLANK_SHA
+
+    with_hooks(ref, newrev, oldrev) do |service|
+      # We want to pass the OID of the tag object to the hooks. For an
+      # annotated tag we don't know that OID until after the tag object
+      # (raw_tag) is created in the repository. That is why we have to
+      # update the value after creating the tag object. Only the
+      # "post-receive" hook will receive the correct value in this case.
+      raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
+      service.newrev = raw_tag.target_id
+    end
+  end
+
+  def rm_tag(tag)
+    ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
+    oldrev = tag.target
+    newrev = Gitlab::Git::BLANK_SHA
+
+    update_ref_in_hooks(ref, newrev, oldrev) do
+      repository.rugged.tags.delete(tag_name)
+    end
+  end
+
+  # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
+  # it would be created from `start_branch_name`.
+  # If `start_project` is passed, and the branch doesn't exist,
+  # it would try to find the commits from it instead of current repository.
+  def with_branch(
+    branch_name,
+    start_branch_name: nil,
+    start_project: repository.project,
+    &block)
+
+    check_with_branch_arguments!(
+      branch_name, start_branch_name, start_project)
+
+    update_branch_with_hooks(branch_name) do
+      repository.with_repo_branch_commit(
+        start_project.repository,
+        start_branch_name || branch_name,
+        &block)
+    end
+  end
+
+  private
+
+  def update_branch_with_hooks(branch_name)
+    update_autocrlf_option
+
+    was_empty = repository.empty?
+
+    # Make commit
+    newrev = yield
+
+    unless newrev
+      raise Repository::CommitError.new('Failed to create commit')
+    end
+
+    branch = repository.find_branch(branch_name)
+    oldrev = find_oldrev_from_branch(newrev, branch)
+
+    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+    update_ref_in_hooks(ref, newrev, oldrev)
+
+    # If repo was empty expire cache
+    repository.after_create if was_empty
+    repository.after_create_branch if
+      was_empty || Gitlab::Git.blank_ref?(oldrev)
+
+    newrev
+  end
+
+  def find_oldrev_from_branch(newrev, branch)
+    return Gitlab::Git::BLANK_SHA unless branch
+
+    oldrev = branch.target
+
+    if oldrev == repository.rugged.merge_base(newrev, branch.target)
+      oldrev
+    else
+      raise Repository::CommitError.new('Branch diverged')
+    end
+  end
+
+  def update_ref_in_hooks(ref, newrev, oldrev)
+    with_hooks(ref, newrev, oldrev) do
+      update_ref(ref, newrev, oldrev)
+    end
+  end
+
+  def with_hooks(ref, newrev, oldrev)
+    GitHooksService.new.execute(
+      user,
+      repository.path_to_repo,
+      oldrev,
+      newrev,
+      ref) do |service|
+
+      yield(service)
+    end
+  end
+
+  def update_ref(ref, newrev, oldrev)
+    # We use 'git update-ref' because libgit2/rugged currently does not
+    # offer 'compare and swap' ref updates. Without compare-and-swap we can
+    # (and have!) accidentally reset the ref to an earlier state, clobbering
+    # commits. See also https://github.com/libgit2/libgit2/issues/1534.
+    command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
+    _, status = Gitlab::Popen.popen(
+      command,
+      repository.path_to_repo) do |stdin|
+      stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
+    end
+
+    unless status.zero?
+      raise Repository::CommitError.new(
+        "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
+        " Please refresh and try again.")
+    end
+  end
+
+  def update_autocrlf_option
+    if repository.raw_repository.autocrlf != :input
+      repository.raw_repository.autocrlf = :input
+    end
+  end
+
+  def check_with_branch_arguments!(
+    branch_name, start_branch_name, start_project)
+    return if repository.branch_exists?(branch_name)
+
+    if repository.project != start_project
+      unless start_branch_name
+        raise ArgumentError,
+          'Should also pass :start_branch_name if' +
+          ' :start_project is different from current project'
+      end
+
+      unless start_project.repository.branch_exists?(start_branch_name)
+        raise ArgumentError,
+          "Cannot find branch #{branch_name} nor" \
+          " #{start_branch_name} from" \
+          " #{start_project.path_with_namespace}"
+      end
+    elsif start_branch_name
+      unless repository.branch_exists?(start_branch_name)
+        raise ArgumentError,
+          "Cannot find branch #{branch_name} nor" \
+          " #{start_branch_name} from" \
+          " #{repository.project.path_with_namespace}"
+      end
+    end
+  end
+end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..76d0ba67b07af6616831f8e64f9369ed210c565f
--- /dev/null
+++ b/app/services/labels/promote_service.rb
@@ -0,0 +1,71 @@
+module Labels
+  class PromoteService < BaseService
+    BATCH_SIZE = 1000
+
+    def execute(label)
+      return unless project.group &&
+          label.is_a?(ProjectLabel)
+
+      Label.transaction do
+        new_label = clone_label_to_group_label(label)
+
+        label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
+          update_issuables(new_label, batched_ids)
+          update_issue_board_lists(new_label, batched_ids)
+          update_priorities(new_label, batched_ids)
+          # Order is important, project labels need to be last
+          update_project_labels(batched_ids)
+        end
+
+        # We skipped validations during creation. Let's run them now, after deleting conflicting labels
+        raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid?
+        new_label
+      end
+    end
+
+    private
+
+    def label_ids_for_merge(new_label)
+      LabelsFinder.
+        new(current_user, title: new_label.title, group_id: project.group.id).
+        execute(skip_authorization: true).
+        where.not(id: new_label).
+        select(:id)  # Can't use pluck() to avoid object-creation because of the batching
+    end
+
+    def update_issuables(new_label, label_ids)
+      LabelLink.
+        where(label: label_ids).
+        update_all(label_id: new_label)
+    end
+
+    def update_issue_board_lists(new_label, label_ids)
+      List.
+        where(label: label_ids).
+        update_all(label_id: new_label)
+    end
+
+    def update_priorities(new_label, label_ids)
+      LabelPriority.
+        where(label: label_ids).
+        update_all(label_id: new_label)
+    end
+
+    def update_project_labels(label_ids)
+      Label.where(id: label_ids).delete_all
+    end
+
+    def clone_label_to_group_label(label)
+      params = label.attributes.slice('title', 'description', 'color')
+      # Since the title of the new label has to be the same as the previous labels
+      # and we're merging old labels in batches we'll skip validation to omit 2-step
+      # merge process and do it in one batch
+      # We'll be forcing validation at the end of the transaction to ensure everything
+      # was merged correctly
+      new_label = GroupLabel.new(params.merge(group: project.group))
+      new_label.save(validate: false)
+
+      new_label
+    end
+  end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 70e25956dc7140518d0b3aaaf235004d9bbd0c15..5a53b9730592ffe4c64331fc11a2c60ef7d74da3 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -38,15 +38,13 @@ module MergeRequests
 
     private
 
-    def merge_requests_for(branch)
-      origin_merge_requests = @project.origin_merge_requests
-        .opened.where(source_branch: branch).to_a
-
-      fork_merge_requests = @project.fork_merge_requests
-        .opened.where(source_branch: branch).to_a
-
-      (origin_merge_requests + fork_merge_requests)
-        .uniq.select(&:source_project)
+    # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
+    def merge_requests_for(source_branch, mr_states: [:opened])
+      MergeRequest
+        .with_state(mr_states)
+        .where(source_branch: source_branch, source_project_id: @project.id)
+        .preload(:source_project) # we don't need a #includes since we're just preloading for the #select
+        .select(&:source_project)
     end
 
     def pipeline_merge_requests(pipeline)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 6a7393a9921728a1dcc04e72a1c9c9bf5da915ee..f4d52e3ebbddaf316af181f5590c13ffec7fb378 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -1,62 +1,89 @@
 module MergeRequests
   class BuildService < MergeRequests::BaseService
     def execute
-      merge_request = MergeRequest.new(params)
-
-      # Set MR attributes
-      merge_request.can_be_created = true
+      self.merge_request = MergeRequest.new(params)
+      merge_request.can_be_created  = true
       merge_request.compare_commits = []
-      merge_request.source_project = project unless merge_request.source_project
+      merge_request.source_project  = find_source_project
+      merge_request.target_project  = find_target_project
+      merge_request.target_branch   = find_target_branch
 
-      merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+      if branches_specified? && branches_valid?
+        compare_branches
+        assign_title_and_description
+      else
+        merge_request.can_be_created = false
+      end
 
-      merge_request.target_project ||= (project.forked_from_project || project)
-      merge_request.target_branch ||= merge_request.target_project.default_branch
+      merge_request
+    end
 
-      messages = validate_branches(merge_request)
-      return build_failed(merge_request, messages) unless messages.empty?
+    private
 
-      compare = CompareService.new.execute(
-        merge_request.source_project,
-        merge_request.source_branch,
-        merge_request.target_project,
-        merge_request.target_branch,
-      )
+    attr_accessor :merge_request
 
-      merge_request.compare_commits = compare.commits
-      merge_request.compare = compare
+    delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
 
-      set_title_and_description(merge_request)
+    def find_source_project
+      source_project || project
     end
 
-    private
+    def find_target_project
+      return target_project if target_project.present? && can?(current_user, :read_project, target_project)
+      project.forked_from_project || project
+    end
 
-    def validate_branches(merge_request)
-      messages = []
+    def find_target_branch
+      target_branch || target_project.default_branch
+    end
 
-      if merge_request.target_branch.blank? || merge_request.source_branch.blank?
-        messages <<
-          if params[:source_branch] || params[:target_branch]
-            "You must select source and target branch"
-          end
-      end
+    def branches_specified?
+      params[:source_branch] && params[:target_branch]
+    end
 
-      if merge_request.source_project == merge_request.target_project &&
-          merge_request.target_branch == merge_request.source_branch
+    def branches_valid?
+      validate_branches
+      errors.blank?
+    end
 
-        messages << 'You must select different branches'
-      end
+    def compare_branches
+      compare = CompareService.new(
+        source_project,
+        source_branch
+      ).execute(
+        target_project,
+        target_branch
+      )
 
-      # See if source and target branches exist
-      if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch)
-        messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
-      end
+      merge_request.compare_commits = compare.commits
+      merge_request.compare = compare
+    end
 
-      if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch)
-        messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
-      end
+    def validate_branches
+      add_error('You must select source and target branch') unless branches_present?
+      add_error('You must select different branches') if same_source_and_target?
+      add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
+      add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists?
+    end
+
+    def add_error(message)
+      errors.add(:base, message)
+    end
+
+    def branches_present?
+      target_branch.present? && source_branch.present?
+    end
+
+    def same_source_and_target?
+      source_project == target_project && target_branch == source_branch
+    end
 
-      messages
+    def source_branch_exists?
+      source_branch.blank? || source_project.commit(source_branch)
+    end
+
+    def target_branch_exists?
+      target_branch.blank? || target_project.commit(target_branch)
     end
 
     # When your branch name starts with an iid followed by a dash this pattern will be
@@ -71,17 +98,17 @@ module MergeRequests
     # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
     #   more than one commit in the MR
     #
-    def set_title_and_description(merge_request)
-      if match = merge_request.source_branch.match(/\A(\d+)-/)
+    def assign_title_and_description
+      if match = source_branch.match(/\A(\d+)-/)
         iid = match[1]
       end
 
-      commits = merge_request.compare_commits
+      commits = compare_commits
       if commits && commits.count == 1
         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, current_user)
+      elsif iid && issue = target_project.get_issue(iid, current_user)
         case issue
         when Issue
           merge_request.title = "Resolve \"#{issue.title}\""
@@ -89,31 +116,20 @@ module MergeRequests
           merge_request.title = "Resolve #{issue.title}"
         end
       else
-        merge_request.title = merge_request.source_branch.titleize.humanize
+        merge_request.title = source_branch.titleize.humanize
       end
 
       if iid
         closes_issue = "Closes ##{iid}"
 
-        if merge_request.description.present?
+        if description.present?
           merge_request.description += closes_issue.prepend("\n\n")
         else
           merge_request.description = closes_issue
         end
       end
 
-      merge_request.title = merge_request.wip_title if commits.empty?
-
-      merge_request
-    end
-
-    def build_failed(merge_request, messages)
-      messages.compact.each do |message|
-        merge_request.errors.add(:base, message)
-      end
-      merge_request.compare_commits = []
-      merge_request.can_be_created = false
-      merge_request
+      merge_request.title = wip_title if commits.empty?
     end
   end
 end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index ab9056a32508b54335b2d8632f0f242438d07a4b..5ca6fec962dcfe7673e81541e9bcf5202ef2894c 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,13 +6,17 @@ module MergeRequests
   # Executed when you do merge via GitLab UI
   #
   class MergeService < MergeRequests::BaseService
-    attr_reader :merge_request
+    attr_reader :merge_request, :source
 
     def execute(merge_request)
       @merge_request = merge_request
 
       return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
 
+      @source = find_merge_source
+
+      return log_merge_error('No source for merge', true) unless @source
+
       merge_request.in_locked_state do
         if commit
           after_merge
@@ -34,7 +38,7 @@ module MergeRequests
         committer: committer
       }
 
-      commit_id = repository.merge(current_user, merge_request, options)
+      commit_id = repository.merge(current_user, source, merge_request, options)
 
       if commit_id
         merge_request.update(merge_commit_sha: commit_id)
@@ -73,9 +77,11 @@ module MergeRequests
     end
 
     def merge_request_info
-      project = merge_request.project
+      merge_request.to_reference(full: true)
+    end
 
-      "#{project.to_reference}#{merge_request.to_reference}"
+    def find_merge_source
+      merge_request.diff_head_sha
     end
   end
 end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 51d5d7563fc7e28806e02fcb94b334eee54ade83..b4bfb0e5e8c79ad5e3ce21c355f1b5284cd37e47 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -42,7 +42,7 @@ module MergeRequests
         commit_ids.include?(merge_request.diff_head_sha)
       end
 
-      merge_requests.uniq.select(&:source_project).each do |merge_request|
+      filter_merge_requests(merge_requests).each do |merge_request|
         MergeRequests::PostMergeService.
           new(merge_request.target_project, @current_user).
           execute(merge_request)
@@ -58,10 +58,13 @@ module MergeRequests
     def reload_merge_requests
       merge_requests = @project.merge_requests.opened.
         by_source_or_target_branch(@branch_name).to_a
-      merge_requests += fork_merge_requests
-      merge_requests = filter_merge_requests(merge_requests)
 
-      merge_requests.each do |merge_request|
+      # Fork merge requests
+      merge_requests += MergeRequest.opened
+        .where(source_branch: @branch_name, source_project: @project)
+        .where.not(target_project: @project).to_a
+
+      filter_merge_requests(merge_requests).each do |merge_request|
         if merge_request.source_branch == @branch_name || force_push?
           merge_request.reload_diff
         else
@@ -175,16 +178,7 @@ module MergeRequests
     end
 
     def merge_requests_for_source_branch
-      @source_merge_requests ||= begin
-        merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
-        merge_requests += fork_merge_requests
-        filter_merge_requests(merge_requests)
-      end
-    end
-
-    def fork_merge_requests
-      @fork_merge_requests ||= @project.fork_merge_requests.opened.
-        where(source_branch: @branch_name).to_a
+      @source_merge_requests ||= merge_requests_for(@branch_name)
     end
 
     def branch_added?
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index cdd765c85eb66b0470b8bb555384a9b59d3dbe88..b4f8b33d564b40e3ae3c6b4813c73407b367713b 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -3,9 +3,10 @@ module Notes
     def execute
       merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
 
-      note = project.notes.new(params)
-      note.author = current_user
-      note.system = false
+      note = Note.new(params)
+      note.project = project
+      note.author  = current_user
+      note.system  = false
 
       if note.award_emoji?
         noteable = note.noteable
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index e4cd3fc7833867763cff187fac320dbc37c55f8d..6a10e17248333fab9e6b3805cc38061af735874c 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -10,6 +10,9 @@ module Notes
       # Skip system notes, like status changes and cross-references and awards
       unless @note.system?
         EventCreateService.new.leave_note(@note, @note.author)
+
+        return if @note.for_personal_snippet?
+
         @note.create_cross_references!
         execute_note_hooks
       end
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index aaea9717fc478d8e3f25283ca67e9f032e6f6f5a..56913568cae70e250caa8eb5269fd101ca46f21c 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -12,7 +12,7 @@ module Notes
     def self.supported?(note, current_user)
       noteable_update_service(note) &&
         current_user &&
-        current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable)
+        current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
     end
 
     def supported?(note)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index c3b61e68eab6da4163aad842b00e0832a8193e1f..b2cc39763f3470fec383dd474793a349038b9557 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -178,8 +178,15 @@ class NotificationService
     recipients = []
 
     mentioned_users = note.mentioned_users
+
+    ability, subject = if note.for_personal_snippet?
+                         [:read_personal_snippet, note.noteable]
+                       else
+                         [:read_project, note.project]
+                       end
+
     mentioned_users.select! do |user|
-      user.can?(:read_project, note.project)
+      user.can?(ability, subject)
     end
 
     # Add all users participating in the thread (author, assignee, comment authors)
@@ -192,11 +199,13 @@ class NotificationService
 
     recipients = recipients.concat(participants)
 
-    # Merge project watchers
-    recipients = add_project_watchers(recipients, note.project)
+    unless note.for_personal_snippet?
+      # Merge project watchers
+      recipients = add_project_watchers(recipients, note.project)
 
-    # Merge project with custom notification
-    recipients = add_custom_notifications(recipients, note.project, :new_note)
+      # Merge project with custom notification
+      recipients = add_custom_notifications(recipients, note.project, :new_note)
+    end
 
     # Reject users with Mention notification level, except those mentioned in _this_ note.
     recipients = reject_mention_users(recipients - mentioned_users, note.project)
@@ -211,8 +220,7 @@ class NotificationService
     recipients.delete(note.author)
     recipients = recipients.uniq
 
-    # build notify method like 'note_commit_email'
-    notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
+    notify_method = "note_#{note.to_ability_name}_email".to_sym
 
     recipients.each do |recipient|
       mailer.send(notify_method, recipient.id, note.id).deliver_later
@@ -357,7 +365,7 @@ class NotificationService
     users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
 
     users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
-    users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users)
+    users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users)
 
     User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
   end
@@ -407,8 +415,8 @@ class NotificationService
   end
 
   # Build a list of users based on group notification settings
-  def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
-    uids = notification_settings_for(project, :watch)
+  def select_group_member_setting(group, project_members, global_setting, users_global_level_watch)
+    uids = notification_settings_for(group, :watch)
 
     # Group setting is watch, add to users list if user is not project member
     users = []
@@ -465,7 +473,7 @@ class NotificationService
 
       setting = user.notification_settings_for(project)
 
-      if !setting && project.group
+      if project.group && (setting.nil? || setting.global?)
         setting = user.notification_settings_for(project.group)
       end
 
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 159f46cd465c4b84c18fea0858e9a263f2b33f7c..c7cce0c55b9dea73b4f2040aa08e3b67833a8ec2 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -22,17 +22,7 @@ module Projects
         return @project
       end
 
-      # Set project name from path
-      if @project.name.present? && @project.path.present?
-        # if both name and path set - everything is ok
-      elsif @project.path.present?
-        # Set project name from path
-        @project.name = @project.path.dup
-      elsif @project.name.present?
-        # For compatibility - set path from name
-        # TODO: remove this in 8.0
-        @project.path = @project.name.dup.parameterize
-      end
+      set_project_name_from_path
 
       # get namespace id
       namespace_id = params[:namespace_id]
@@ -144,5 +134,19 @@ module Projects
         service.save!
       end
     end
+
+    def set_project_name_from_path
+      # Set project name from path
+      if @project.name.present? && @project.path.present?
+        # if both name and path set - everything is ok
+      elsif @project.path.present?
+        # Set project name from path
+        @project.name = @project.path.dup
+      elsif @project.name.present?
+        # For compatibility - set path from name
+        # TODO: remove this in 8.0
+        @project.path = @project.name.dup.parameterize
+      end
+    end
   end
 end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 842e23eb6b60653e47171348844a3fce4068db04..55d9cb13ae44aa271cf3d6d8b72029bed75da9b8 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -22,6 +22,8 @@ module Projects
       if project.update_attributes(params.except(:default_branch))
         if project.previous_changes.include?('path')
           project.rename_repo
+        else
+          system_hook_service.execute_hooks_for(project, :update)
         end
 
         success
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index aa9837038a6b4a9ba3b5732bec7d7eeba2a975bb..781cd13b44bfeebb3c6d4823ec2ff286b7db06ae 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -9,7 +9,10 @@ module Search
     def execute
       group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
       projects = ProjectsFinder.new.execute(current_user)
-      projects = projects.in_namespace(group.id) if group
+
+      if group
+        projects = projects.inside_path(group.full_path)
+      end
 
       Gitlab::SearchResults.new(current_user, projects, params[:search])
     end
diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb
index 2469b4f0d7c7d37f91fb08667672da1839728573..d7a6804ee88b3d784aa89418ffb436a1ece4946b 100644
--- a/app/services/user_project_access_changed_service.rb
+++ b/app/services/user_project_access_changed_service.rb
@@ -4,6 +4,6 @@ class UserProjectAccessChangedService
   end
 
   def execute
-    AuthorizedProjectsWorker.bulk_perform_async(@user_ids.map { |id| [id] })
+    AuthorizedProjectsWorker.bulk_perform_and_wait(@user_ids.map { |id| [id] })
   end
 end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 2d211d5ebbe9460872c754b575536e7048f6103e..fad741531eab983e2cebac8915c5a40ccb725708 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -118,7 +118,8 @@ module Users
         user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
         user.groups_projects.select_for_project_authorization,
         user.projects.select_for_project_authorization,
-        user.groups.joins(:shared_projects).select_for_project_authorization
+        user.groups.joins(:shared_projects).select_for_project_authorization,
+        user.nested_projects.select_for_project_authorization
       ]
 
       Gitlab::SQL::Union.new(relations)
diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f61be184ce27da09eb72011e48be6c8e1a0de90
--- /dev/null
+++ b/app/services/validate_new_branch_service.rb
@@ -0,0 +1,22 @@
+require_relative 'base_service'
+
+class ValidateNewBranchService < BaseService
+  def execute(branch_name)
+    valid_branch = Gitlab::GitRefValidator.validate(branch_name)
+
+    unless valid_branch
+      return error('Branch name is invalid')
+    end
+
+    repository = project.repository
+    existing_branch = repository.find_branch(branch_name)
+
+    if existing_branch
+      return error('Branch already exists')
+    end
+
+    success
+  rescue GitHooksService::PreReceiveError => ex
+    error(ex.message)
+  end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 558bbe07b1603f52478f9f9ab444a1852e156d3b..e7701d75a6eec843176d11e9223a4d8ffd13c070 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -204,7 +204,7 @@
       .col-sm-10
         = f.number_field :max_artifacts_size, class: 'form-control'
         .help-block
-          Set the maximum file size each build's artifacts can have
+          Set the maximum file size each jobs's artifacts can have
           = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
 
   - if Gitlab.config.registry.enabled
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 3132d157f29bb8697702fd878471bdcc2b84b5b1..2269fb1fd8c8c2eec344d9c2c4f10de68f6ef9d0 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -4,7 +4,7 @@
     - if @broadcast_message.message.present?
       = render_broadcast_message(@broadcast_message)
     - else
-      = "Your message here"
+      Your message here
 
 = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
   = form_errors(@broadcast_message)
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 5e3f105d41f6b52f267806f893c7f6ad61068d41..66d633119c241eee9d613ad1df5e6e0a6fbc5839 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -12,7 +12,7 @@
         = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
 
   .row-content-block.second-block
-    #{(@scope || 'all').capitalize} builds
+    #{(@scope || 'all').capitalize} jobs
 
   %ul.content-list.builds-content-list.admin-builds-table
     = render "projects/builds/table", builds: @builds, admin: true
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index b5f96363230b5729ffcbd40b04321115491a9523..7893c1dee975757b13261175f73b86954b7a0b6a 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -20,9 +20,9 @@
             %span
               Groups
         = nav_link path: 'builds#index' do
-          = link_to admin_builds_path, title: 'Builds' do
+          = link_to admin_builds_path, title: 'Jobs' do
             %span
-              Builds
+              Jobs
         = nav_link path: ['runners#index', 'runners#show'] do
           = link_to admin_runners_path, title: 'Runners' do
             %span
diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml
index 7362d904b94dd34c28432635d1189a086e41696e..8c658905bd68639f7b69b9cb3bea74ac05074223 100644
--- a/app/views/admin/identities/_identity.html.haml
+++ b/app/views/admin/identities/_identity.html.haml
@@ -1,6 +1,6 @@
 %tr
   %td
-    = "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})"
+    #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
   %td
     = identity.extern_uid
   %td
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 37bb6a3b0e088eb7caeb1d2291a72abae93cd457..721bc77cc2fa0f39bd364343a8bae1c67cf493cc 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -11,7 +11,7 @@
       that for future communication.
       %br
       Registration token is
-      %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
+      %code#runners-token= current_application_settings.runners_registration_token
 
   .bs-callout.clearfix
     .pull-left
@@ -26,7 +26,7 @@
 
   .bs-callout
     %p
-      A 'Runner' is a process which runs a build.
+      A 'Runner' is a process which runs a job.
       You can setup as many Runners as you need.
       %br
       Runners can be placed on separate users, servers, even on your local machine.
@@ -37,16 +37,16 @@
       %ul
         %li
           %span.label.label-success shared
-          \- Runner runs builds from all unassigned projects
+          \- Runner runs jobs from all unassigned projects
         %li
           %span.label.label-info specific
-          \- Runner runs builds from assigned projects
+          \- Runner runs jobs from assigned projects
         %li
           %span.label.label-warning locked
           \- Runner cannot be assigned to other projects
         %li
           %span.label.label-danger paused
-          \- Runner will not receive any new builds
+          \- Runner will not receive any new jobs
 
   .append-bottom-20.clearfix
     .pull-left
@@ -68,7 +68,7 @@
           %th Runner token
           %th Description
           %th Projects
-          %th Builds
+          %th Jobs
           %th Tags
           %th Last contact
           %th
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index ca503e35623a5f24650160d3c31300cc9854159d..dc4116e1ce0163bd14d12bae18fd42d3489dabd4 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -11,13 +11,13 @@
 
 - if @runner.shared?
   .bs-callout.bs-callout-success
-    %h4 This Runner will process builds from ALL UNASSIGNED projects
+    %h4 This Runner will process jobs from ALL UNASSIGNED projects
     %p
       If you want Runners to build only specific projects, enable them in the table below.
       Keep in mind that this is a one way transition.
 - else
   .bs-callout.bs-callout-info
-    %h4 This Runner will process builds only from ASSIGNED projects
+    %h4 This Runner will process jobs only from ASSIGNED projects
     %p You can't make this a shared Runner.
 %hr
 
@@ -70,11 +70,11 @@
     = paginate @projects, theme: "gitlab"
 
   .col-md-6
-    %h4 Recent builds served by this Runner
+    %h4 Recent jobs served by this Runner
     %table.table.ci-table.runner-builds
       %thead
         %tr
-          %th Build
+          %th Job
           %th Status
           %th Project
           %th Commit
@@ -100,7 +100,7 @@
           %td.build-link
             - if project
               = link_to ci_status_path(build.pipeline) do
-                %strong #{build.pipeline.short_sha}
+                %strong= build.pipeline.short_sha
 
           %td.timestamp
             - if build.finished_at
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index bfc6142067a53bf82b91a5256e782ec999997a95..2e5f120c4e4ca500ee2f54070bef95b799e64886 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -10,7 +10,7 @@
         %h4 CPU
         .data
           - if @cpus
-            %h1= "#{@cpus.length} cores"
+            %h1 #{@cpus.length} cores
           - else
             = icon('warning', class: 'text-warning')
             Unable to collect CPU info
@@ -19,7 +19,7 @@
         %h4 Memory
         .data
           - if @memory
-            %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}"
+            %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
           - else
             = icon('warning', class: 'text-warning')
             Unable to collect memory info
@@ -28,6 +28,6 @@
         %h4 Disks
         .data
           - @disks.each do |disk|
-            %h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}"
-            %p= "#{disk[:disk_name]}"
-            %p= "#{disk[:mount_path]}"
+            %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
+            %p= disk[:disk_name]
+            %p= disk[:mount_path]
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a71240986c96662fea32ef5948ee87a13b06c197..76b1291fe10b1856b6dedd6087397f0239c359bd 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -186,6 +186,6 @@
             - if @user.solo_owned_groups.present?
               %p
                 This user is currently an owner in these groups:
-                %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+                %strong= @user.solo_owned_groups.map(&:name).join(', ')
               %p
                 You must transfer ownership or delete these groups before you can delete this user.
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 95eb9a5715218a10c728649e3dc8814afa829f1c..b0bee1c620421f22a02a01ae0ed6b585dd2d2ba5 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -13,7 +13,7 @@
           .file-holder
             .file-title.clearfix
               Content of .gitlab-ci.yml
-            #ci-editor.ci-editor #{@content}
+            #ci-editor.ci-editor= @content
           = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
       .col-sm-12
         .pull-left.prepend-top-10
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
index 601fb7f0f3f892a44d7bba0b5974db30ba4a9a79..c00c7f7407e5c0174bd799a13ab36674aea263c2 100644
--- a/app/views/ci/status/_badge.html.haml
+++ b/app/views/ci/status/_badge.html.haml
@@ -1,7 +1,8 @@
 - status = local_assigns.fetch(:status)
+- link = local_assigns.fetch(:link, true)
 - css_classes = "ci-status ci-#{status.group}"
 
-- if status.has_details?
+- if link && status.has_details?
   = link_to status.details_path, class: css_classes do
     = custom_icon(status.icon)
     = status.text
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index 8dea3479f823106645062264f700a2a470fa459c..8ed23ac49193069d6753cf2e4471abe4c343e05d 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -16,4 +16,4 @@
 
 - if status.has_action?
   = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title }  do
-    = icon(status.action_icon, class: status.action_class)
+    = custom_icon(status.action_icon)
diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml
index dd2f649de9a3ec25549c87ffd2d7d77e66abed9b..0530d21a7e2022318f666e5991b198cb5cef1902 100644
--- a/app/views/ci/status/_graph_badge.html.haml
+++ b/app/views/ci/status/_graph_badge.html.haml
@@ -2,7 +2,7 @@
 
 - subject = local_assigns.fetch(:subject)
 - status = subject.detailed_status(current_user)
-- klass = "ci-status-icon ci-status-icon-#{status.group}"
+- klass = "ci-status-icon ci-status-icon-#{status.group} js-ci-status-icon-#{status.group}"
 - tooltip = "#{subject.name} - #{status.label}"
 
 - if status.has_details?
@@ -16,5 +16,5 @@
 
 - if status.has_action?
   = link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title }  do
-    %i.ci-action-icon-wrapper
-      = icon(status.action_icon, class: status.action_class)
+    %i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" }
+      = custom_icon(status.action_icon)
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 3caaf827ff5ad82559e7ea13c2aeb714fad24760..653052f7c546924d1b2a67b1639ec0296ce6a4a2 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -15,6 +15,4 @@
     = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
 
 = render 'shared/issuable/filter', type: :issues
-
-.prepend-top-default
-  = render 'shared/issues'
+= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index fb016599fef06e5c236f8bf22ebdca07d750fb64..e64c78c4cb833afbc66a61bfaf15c777fe285a1b 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -7,6 +7,4 @@
     = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
 
 = render 'shared/issuable/filter', type: :merge_requests
-
-.prepend-top-default
-  = render 'shared/merge_requests'
+= render 'shared/merge_requests'
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 9d7bcdb9d166afa2cb8589323d4c07a53acd1131..605bfd0cf8d02055e8e6ccac67476573f18fbf54 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -11,8 +11,11 @@
             = link_to_author(todo)
           - else
             (removed)
-      %span.todo-label
+
+      %span.action-name
         = todo_action_name(todo)
+
+      %span.todo-label
         - if todo.target
           = todo_target_link(todo)
         - else
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f4efcfb27b2d35479dc8a2c2a33eb05252ce4549..c4bf2c90cc2a0b2a1bdb6a51a0285cb80e9d9fe6 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -67,21 +67,17 @@
                   = sort_title_oldest_created
 
 
-.prepend-top-default
+.js-todos-all
   - if @todos.any?
     .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
-    - @todos.group_by(&:project).each do |group|
-      .panel.panel-default.panel-small
-        - project = group[0]
-        .panel-heading
-          = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
-
+      .panel.panel-default.panel-small.panel-without-border
         %ul.content-list.todos-list
-          = render group[1]
+          = render @todos
     = paginate @todos, theme: "gitlab"
+
   - elsif current_user.todos.any?
     .todos-all-done
-      = render "shared/empty_states/todos_all_done.svg"
+      = render "shared/empty_states/icons/todos_all_done.svg"
       - if todos_filter_empty?
         %h4.text-center
           = Gitlab.config.gitlab.no_todos_messages.sample
@@ -98,7 +94,7 @@
   - else
     .todos-empty
       .todos-empty-hero
-        = render "shared/empty_states/todos_empty.svg"
+        = render "shared/empty_states/icons/todos_empty.svg"
       .todos-empty-content
         %h4
           Todos let you see what you should do next.
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 2bce27804849fe3c9c5649a5db0c22d280824daf..6f5d4bf2a2f5f0193c63c1198d57e9174e1c97d7 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -1,6 +1,9 @@
 - expanded = discussion.expanded?
 %li.note.note-discussion.timeline-entry
   .timeline-entry-inner
+    .timeline-icon
+      = link_to user_path(discussion.author) do
+        = image_tag avatar_icon(discussion.author), class: "avatar s40"
     .timeline-content
       .discussion.js-toggle-container{ class: discussion.id, data: { discussion_id: discussion.id } }
         .discussion-header
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
index ef16b516e2c3f823e16f41d37617e188272cd33d..3a19e021643b4653b493c16c546cdde878b04836 100644
--- a/app/views/discussions/_parallel_diff_discussion.html.haml
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -6,7 +6,7 @@
       .content{ class: ('hide' unless discussion_left.expanded?) }
         = render "discussions/notes", discussion: discussion_left, line_type: 'old'
   - else
-    %td.notes_line.old= ""
+    %td.notes_line.old= ("")
     %td.notes_content.parallel.old
       .content
 
@@ -16,6 +16,6 @@
       .content{ class: ('hide' unless discussion_right.expanded?) }
         = render "discussions/notes", discussion: discussion_right, line_type: 'new'
   - else
-    %td.notes_line.new= ""
+    %td.notes_line.new= ("")
     %td.notes_content.parallel.new
       .content
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 2a0e301c8dd66790a3cfc6edd39b195f6acefa4f..a196561f381a778b3f6baeed765cfd072cd5807a 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -10,7 +10,7 @@
       %p
         = icon("exclamation-triangle fw")
         You are an admin, which means granting access to
-        %strong #{@pre_auth.client.name}
+        %strong= @pre_auth.client.name
         will allow them to interact with GitLab as an admin as well. Proceed with caution.
 
   - if @pre_auth.scopes
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index bc5d3c797ac3086c5e76791b64ea79c21ccf1cac..f4c432a095a319e2634882ebcfb0506d6cc1168a 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -25,7 +25,7 @@
   .panel.panel-default
     .panel-heading
       Users with access to
-      %strong #{@group.name}
+      %strong= @group.name
       %span.badge= @members.total_count
     %ul.content-list
       = render partial: 'shared/members/member', collection: @members, as: :member
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index b4aa4f24d9e5f03e552e1fe6864f2b216111451e..83edb71969288a715ab8a99e992f0653eee1b762 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -18,12 +18,11 @@
 
   .row-content-block.second-block
     Only issues from the
-    %strong #{@group.name}
+    %strong= @group.name
     group are listed here.
     - if current_user
       To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
 
-  .prepend-top-default
-    = render 'shared/issues'
+  = render 'shared/issues'
 - else
   = render 'shared/empty_states/issues', project_select_button: true
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index dbbdb583a24d94e50d2e20e988205653b16f1401..6ad76d23df53a58ec0876661cbf26c36596f6fab 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -10,10 +10,9 @@
 
 .row-content-block.second-block
   Only merge requests from
-  %strong #{@group.name}
+  %strong= @group.name
   group are listed here.
   - if current_user
     To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
 
-.prepend-top-default
-  = render 'shared/merge_requests'
+= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index a8fdbd8c426503eb4d44c73b7bbb70fbaa9e3dd4..cd5388fe40269abac2b9bb2466537a8cbc0fb499 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -10,7 +10,7 @@
 
 .row-content-block
   Only milestones from
-  %strong #{@group.name}
+  %strong= @group.name
   group are listed here.
 
 .milestones
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index b74cc8222956576c709a7ebbfda8f8eb3f5419c8..da2df0d8080a55390b3b5a944325cc40f70c779a 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -143,7 +143,7 @@
                     .key g
                     .key b
                   %td
-                    Go to builds
+                    Go to jobs
                 %tr
                   %td.shortcut
                     .key g
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index 864c5c0ff9562ee1a10ee1e7a8c4e2235804d15c..0e7f0b5ed4fb1ba87070ee310a3874987f1c1e73 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -16,7 +16,7 @@
     %colgroup.import-jobs-status-col
     %thead
       %tr
-        %th= "From #{provider_title}"
+        %th From #{provider_title}
         %th To GitLab
         %th Status
     %tbody
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index 07338736bacd48449a7ea68a8321a9429fff869c..9999a4362c615fe2a765abb2a8661b79f61bd58b 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -37,7 +37,7 @@
       %tbody
         - @user_map.each do |id, user|
           %tr
-            %td= id
+            %td= (id)
             %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
             %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
             %td
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 97e5e51abe0692fbe93a67e10168595e9583b9e9..5de5da5e6a2d0168fb60da5955048944445ee7cc 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -50,7 +50,7 @@
           %td
             = repo.name
           %td.import-target
-            = "#{current_user.username}/#{repo.name}"
+            #{current_user.username}/#{repo.name}
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
               Import
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 9f1507cade6c6b40d3b19b36d2f40d1025b1d3e7..5e01af008bec56fc06a60036fd9ee4de2f66a4fe 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -55,7 +55,7 @@
           %td
             = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
           %td.import-target
-            = "#{current_user.username}/#{repo.name}"
+            #{current_user.username}/#{repo.name}
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
               Import
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ac04f57e2172c1899ca883413b4bef8e2385ae60..19a947af4ca2686703cee91dbdf94ea70c443ec0 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,5 +1,5 @@
+= render 'layouts/nav/admin_settings'
 .scrolling-tabs-container{ class: nav_control_class }
-  = render 'layouts/nav/admin_settings'
   .fade-left
     = icon('angle-left')
   .fade-right
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index a8bbd67de80623d75ee75fe1f3142610a6b71479..7883823b21ea065f768cec678f0d1ff38a62c5ad 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -96,8 +96,8 @@
     -# Shortcut to builds page
     - if project_nav_tab? :builds
       %li.hidden
-        = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
-          Builds
+        = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+          Jobs
 
     -# Shortcut to commits page
     - if project_nav_tab? :commits
diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml
index 56d81b2ed2e074eddefce56844eddf3f241027d3..fd35713f79caa0fb5be1533fbc0c7bb67cdaad86 100644
--- a/app/views/notify/_reassigned_issuable_email.html.haml
+++ b/app/views/notify/_reassigned_issuable_email.html.haml
@@ -2,9 +2,9 @@
   Assignee changed
   - if @previous_assignee
     from
-    %strong #{@previous_assignee.name}
+    %strong= @previous_assignee.name
   to
   - if issuable.assignee_id
-    %strong #{issuable.assignee_name}
+    %strong= issuable.assignee_name
   - else
     %strong Unassigned
diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
index a744c4be9d6ba0b0f638f6125db3e23d2bfb89a8..060b50ffc698aad7db2714254bf75a65bf61a906 100644
--- a/app/views/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -1,6 +1,6 @@
 - content_for :header do
   %h1{ style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
-    GitLab (build failed)
+    GitLab (job failed)
 
 %h3
   Project:
@@ -21,4 +21,4 @@
   Message: #{@build.pipeline.git_commit_message}
 
 %p
-  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
+  Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb
index 9d497983498fa52a8ed3f189a16cf3754b2ebda8..2a94688a6b060e4714f890e33f1df91beda250ba 100644
--- a/app/views/notify/build_fail_email.text.erb
+++ b/app/views/notify/build_fail_email.text.erb
@@ -1,4 +1,4 @@
-Build failed for <%= @project.name %>
+Job failed for <%= @project.name %>
 
 Status:   <%= @build.status %>
 Commit:   <%= @build.pipeline.short_sha %>
diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
index 8c2e6db1426dc8bf4aaa9e0b858777c2efaababa..ca0eaa96a9db3166a915ab257adae6665b252371 100644
--- a/app/views/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -1,6 +1,6 @@
 - content_for :header do
   %h1{ style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
-    GitLab (build successful)
+    GitLab (job successful)
 
 %h3
   Project:
@@ -21,4 +21,4 @@
   Message: #{@build.pipeline.git_commit_message}
 
 %p
-  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
+  Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb
index c5ed4f84861c0979e5e462acfb6d0d1200250b0e..445cd46e64fe66daa390891a1f0839b7920f5113 100644
--- a/app/views/notify/build_success_email.text.erb
+++ b/app/views/notify/build_success_email.text.erb
@@ -1,4 +1,4 @@
-Build successful for <%= @project.name %>
+Job successful for <%= @project.name %>
 
 Status:   <%= @build.status %>
 Commit:   <%= @build.pipeline.short_sha %>
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index 56c18cd83cd6fc688b923d355a33050801518487..b7284dd819b25a308d9ee929d249ca3c3e561e83 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,2 +1,2 @@
 %p
-  = "Issue was closed by #{@updated_by.name}"
+  Issue was closed by #{@updated_by.name}
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index ac703b31eddf5d4f6ea739612a122b1274f58902..bc12e38675f749db55257edb9e648a6fe6dcbdd4 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
-= "Issue was closed by #{@updated_by.name}"
+Issue was closed by #{@updated_by.name}
 
 Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 81c7c88fc96b0dda2306e9a73dabf008281f6acb..44e018304e1e13f0ccf0e746d142daafb4893cec 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,2 @@
 %p
-  = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
+  Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index b435067d5a6618f9363c36aa402653619a9ba391..d0c96b83976be60216e867b480558a4568c36a3f 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
+Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
 
 Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
 
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
index 482c884a9dbde497e3101a5c0d53fbce9a9467ae..b6051b11cea3ddaae03bd0d8cecc4fac3fd3fadb 100644
--- a/app/views/notify/issue_status_changed_email.html.haml
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -1,2 +1,2 @@
 %p
-  = "Issue was #{@issue_status} by #{@updated_by.name}"
+  Issue was #{@issue_status} by #{@updated_by.name}
diff --git a/app/views/notify/links/ci/builds/_build.text.erb b/app/views/notify/links/ci/builds/_build.text.erb
index f495a2e54863e193f566e84349e5f057b4b7d876..741c7f344c88b66ea17980fef5b7b5080ad22da0 100644
--- a/app/views/notify/links/ci/builds/_build.text.erb
+++ b/app/views/notify/links/ci/builds/_build.text.erb
@@ -1 +1 @@
-Build #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
+Job #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
diff --git a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
index 8e89c52a1f39a7d53fdd8ef0b0285e4378bcf26d..af8924bad577315b22d7b057ac1f2d4820caafed 100644
--- a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
+++ b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
@@ -1 +1 @@
-Build #<%= build.id %>
+Job #<%= build.id %>
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index 41a320d6bd8260dd4eac16db791812857ce14777..b487e26b12208a6a3a9bba9c75b41f7626eb6458 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,2 @@
 %p
-  = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
+  Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 7a5074a1dc3110b4bac765ff210081b2b2f82070..4c9719ba7326f7c5762266da5b7973a208eedc37 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
+Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
 
 Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
 
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index fbe506d4f4ddaa05f53d36822254b4496803a555..0fe54e733133bee17d4e81f37dffda06509a51b4 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
 %p
-  = "Merge Request #{@merge_request.to_reference} was merged"
+  Merge Request #{@merge_request.to_reference} was merged
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index bfbae01094fb4b19a9b073fdd3c68e114132ac49..46c1c9dee0bb3b81b28301f4c9d689a07fc8e178 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was merged"
+Merge Request #{@merge_request.to_reference} was merged
 
 Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
 
diff --git a/app/views/notify/note_personal_snippet_email.html.haml b/app/views/notify/note_personal_snippet_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2fa2f7846611da52652a8307e29b3992409f1606
--- /dev/null
+++ b/app/views/notify/note_personal_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_personal_snippet_email.text.erb b/app/views/notify/note_personal_snippet_email.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b2a8809a23b4a23ea8d7754f34754f9b8b68118f
--- /dev/null
+++ b/app/views/notify/note_personal_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(snippet_url(@snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index 82c7fe229b805015bed28e7037175c0d7fcac251..d9ebbaa27046af868a60c49a9f1d2b17afbfd081 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -139,7 +139,7 @@
                             had
                             = failed.size
                             failed
-                            = "#{'build'.pluralize(failed.size)}."
+                            #{'build'.pluralize(failed.size)}.
                         %tr.warning
                           %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
                             Logs may contain sensitive data. Please consider before forwarding this email.
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 6dddb3b63731d372f1bbfeb48d27ab77cff9353c..8add2e1820622ef2af69236b3dac6bbc22ace908 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -138,9 +138,9 @@
                             %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
                               = "\##{@pipeline.id}"
                             successfully completed
-                            = "#{build_count} #{'build'.pluralize(build_count)}"
+                            #{build_count} #{'build'.pluralize(build_count)}
                             in
-                            = "#{stage_count} #{'stage'.pluralize(stage_count)}."
+                            #{stage_count} #{'stage'.pluralize(stage_count)}.
         %tr.footer
           %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
             %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml
index 27785165c2ddd17ccf8d7cd93937ec3b14807554..6c6902994a1758dc5d33accc404ef958a82db70b 100644
--- a/app/views/notify/project_was_not_exported_email.text.haml
+++ b/app/views/notify/project_was_not_exported_email.text.haml
@@ -1,6 +1,6 @@
-= "Project #{@project.name} couldn't be exported."
+Project #{@project.name} couldn't be exported.
 
-= "The errors we encountered were:"
+The errors we encountered were:
 
 - @errors.each do |error|
   #{error}
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 36858fa6f346c44ae8e5bb4638c6ce9f8ad350c4..c6b1db17f91e9ce483a5a73952acc4b001f5ea46 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -17,7 +17,7 @@
   %ul
     - @message.commits.each do |commit|
       %li
-        %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
+        %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))
         %div
           %span by #{commit.author_name}
           %i at #{commit.committed_date.to_s(:iso8601)}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 72f658d1b68d70756e5438a69e222d2489db3f32..14b330d16ad72a6cc92efa194c4e1a308651b285 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -102,7 +102,7 @@
             = f.text_field :username, required: true, class: 'form-control'
         .help-block
           Current path:
-          = "#{root_url}#{current_user.username}"
+          #{root_url}#{current_user.username}
         .prepend-top-default
           = f.button class: "btn btn-warning", type: "submit" do
             = icon "spinner spin", class: "hidden loading-username"
@@ -128,7 +128,7 @@
         - if @user.solo_owned_groups.present?
           %p
             Your account is currently an owner in these groups:
-            %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+            %strong= @user.solo_owned_groups.map(&:name).join(', ')
           %p
             You must transfer ownership or delete these groups before you can delete your account.
 .append-bottom-default
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index c0c82cde2f6cfe39f0735d504abfc5754645221c..d551754a2e53826e9ddcbf5e8f628afe19cb7085 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -62,7 +62,7 @@
             %span.help-block
               Please click the link in the confirmation email before continuing. It was sent to
               = succeed "." do
-                %strong #{@user.unconfirmed_email}
+                %strong= @user.unconfirmed_email
               %p
               = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
 
diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml
index e2b73cee5a91f84d839d03c4c7bd4f1a44ca7593..a41791f0eca25aac153efef51426143225cff375 100644
--- a/app/views/projects/_customize_workflow.html.haml
+++ b/app/views/projects/_customize_workflow.html.haml
@@ -3,6 +3,6 @@
     %h4
       Customize your workflow!
     %p
-      Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production!
+      Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and pipelines, GitLab can help manage your workflow from idea to production!
     - if can?(current_user, :admin_project, @project)
       = link_to "Get started", edit_project_path(@project), class: "btn btn-success"
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 085f79de7850f4101b45b8289847c6f4f1ec02de..23e27c1105c7d0c4c602815751be16a06778fe8b 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -23,7 +23,7 @@
           = markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
           = markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
         .toolbar-group
-          %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+          %button.toolbar-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
             = icon("arrows-alt fw")
 
   .md-write-holder
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index afe2fd7fd7b0bb1a486823da43a8eb72daa52d08..27d25a6b68204b4a27cc4b829b40b1e2c38cc5eb 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -4,11 +4,11 @@
   .checkbox.builds-feature
     = form.label :only_allow_merge_if_build_succeeds do
       = form.check_box :only_allow_merge_if_build_succeeds
-      %strong Only allow merge requests to be merged if the build succeeds
+      %strong Only allow merge requests to be merged if the pipeline succeeds
       %br
       %span.descr
-        Builds need to be configured to enable this feature.
-        = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+        Pipelines need to be configured to enable this feature.
+        = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')
   .checkbox
     = form.label :only_allow_merge_if_all_discussions_are_resolved do
       = form.check_box :only_allow_merge_if_all_discussions_are_resolved
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index d0ff14e45e68b279426639f32cd765d91e07ede9..edf55d59f28939b25dff83c6dfe7782eb7e2d6ed 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
+- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
 
 .top-block.row-content-block.clearfix
   .pull-right
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 1d058daa0943301b6766f53a45f3b90d4a5c994f..228ac61fc8c07c53e040dc1be12713b105d67c36 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -34,7 +34,7 @@
         = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
 
   .file-editor.code
-    %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
+    %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data]
     - if local_assigns[:path]
       .js-edit-mode-pane#preview.hide
         .center
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index a486b2fe4913025dbf5744a50cb3525e71a6ce23..f864702d862c30c78f1fbb3f83d2001327fa1677 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,8 +1,8 @@
 .file-content.image_file
   - if blob.svg?
     - if blob.size_within_svg_limits?
-      - # We need to scrub SVG but we cannot do so in the RawController: it would
-      - # be wrong/strange if RawController modified the data.
+      -# We need to scrub SVG but we cannot do so in the RawController: it would
+      -# be wrong/strange if RawController modified the data.
       - blob.load_all_data!(@repository)
       - blob = sanitize_svg(blob)
       %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" }
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 61a7ffdd0abb08c20c0e4b4e48abd92888a37a07..4924c73cf8ea270c1513b49aa7e5c25176a313c8 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -3,7 +3,7 @@
     .modal-content
       .modal-header
         %a.close{ href: "#", "data-dismiss" => "modal" } ×
-        %h3.page-title #{title}
+        %h3.page-title= title
       .modal-body
         = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do
           .dropzone
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 04efc2e996c2aa4c33a9498cf5b36dbd6d9f48fc..19ffe73a08d37729079acd6fd53b769c5628880f 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -18,7 +18,7 @@
     - if @project.protected_branch? branch.name
       %span.label.label-success
         protected
-    .controls.hidden-xs
+    .controls.hidden-xs<
       - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
         = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
           Merge Request
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 5f8f56150f9dd38bd30d226564659cde6f23ff6c..bd1f2d96f56fa7038657850c1bfea1c9e295438b 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -12,7 +12,7 @@
       = form_tag(filter_branches_path, method: :get) do
         = 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
+      .dropdown.inline>
         %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
           %span.light
             = projects_sort_options_hash[@sort]
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index b15be0d861d562dec758bfad92e025e7596f0b26..27e81c2bec30047a199dc547700992d348580455 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,8 +1,8 @@
 .content-block.build-header
   .header-content
-    = render 'ci/status/badge', status: @build.detailed_status(current_user)
-    Build
-    %strong ##{@build.id}
+    = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
+    Job
+    %strong.js-build-id ##{@build.id}
     in pipeline
     = link_to pipeline_path(@build.pipeline) do
       %strong ##{@build.pipeline.id}
@@ -17,6 +17,6 @@
       = render "user"
     = time_ago_with_tooltip(@build.created_at)
   - if can?(current_user, :update_build, @build) && @build.retryable?
-    = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
+    = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
   %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
     = icon('angle-double-left')
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 37bf085130a41d8eb1aa071eb8db7967e90ca5ac..56fc5f5e68b873df959d98a21cc55d11fb7d7686 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -2,7 +2,7 @@
 
 %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
+    Job
     %strong ##{@build.id}
     %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
       = icon('angle-double-right')
@@ -17,7 +17,7 @@
     - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
       .block{ class: ("block-first" if !@build.coverage) }
         .title
-          Build artifacts
+          Job artifacts
         - if @build.artifacts_expired?
           %p.build-detail-row
             The artifacts were removed
@@ -42,9 +42,9 @@
 
     .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
       .title
-        Build details
+        Job details
         - if can?(current_user, :update_build, @build) && @build.retryable?
-          = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
+          = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
       - if @build.merge_request
         %p.build-detail-row
           %span.build-light-text Merge Request:
@@ -136,4 +136,4 @@
               - else
                 = build.id
             - if build.retried?
-              %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Build was retried' }
+              %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
index 028664f5bbaccd86de1c72fd41009f2075592578..acfdb250aff9a8f85470bf9480e9e003c7416eef 100644
--- a/app/views/projects/builds/_table.html.haml
+++ b/app/views/projects/builds/_table.html.haml
@@ -2,14 +2,14 @@
 
 - if builds.blank?
   %div
-    .nothing-here-block No builds to show
+    .nothing-here-block No jobs to show
 - else
   .table-holder
     %table.table.ci-table.builds-page
       %thead
         %tr
           %th Status
-          %th Build
+          %th Job
           %th Pipeline
           - if admin
             %th Project
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index c623e39b21f7feb8138b826c2379694d7ea4cd8e..5ffc0e20d10ce481a6da5f78d6146f953ea8c0ca 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- page_title "Builds"
+- page_title "Jobs"
 = render "projects/pipelines/head"
 
 %div{ class: container_class }
@@ -14,7 +14,7 @@
             data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
 
         - unless @repository.gitlab_ci_yml
-          = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
+          = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
 
         = link_to ci_lint_path, class: 'btn btn-default' do
           %span CI Lint
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index c613e473e4cc6ccac9f49c91bf09646d1ac1db96..228dad528ab6a34db64532d3875e80b18d9e385f 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,5 +1,5 @@
 - @no_container = true
-- page_title "#{@build.name} (##{@build.id})", "Builds"
+- page_title "#{@build.name} (##{@build.id})", "Jobs"
 - trace_with_state = @build.trace_with_state
 = render "projects/pipelines/head", build_subnav: true
 
@@ -12,14 +12,14 @@
         .bs-callout.bs-callout-warning
           %p
             - if no_runners_for_project?(@build.project)
-              This build is stuck, because the project doesn't have any runners online assigned to it.
+              This job is stuck, because the project doesn't have any runners online assigned to it.
             - elsif @build.tags.any?
-              This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
+              This job is stuck, because you don't have any active runners online with any of these tags assigned to them:
               - @build.tags.each do |tag|
                 %span.label.label-primary
                   = tag
             - else
-              This build is stuck, because you don't have any active runners that can run this build.
+              This job is stuck, because you don't have any active runners that can run this job.
 
             %br
             Go to
@@ -37,14 +37,14 @@
           - environment = environment_for_build(@build.project, @build)
           - if @build.success? && @build.last_deployment.present?
             - if @build.last_deployment.last?
-              This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
+              This job 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)}.
+              This job is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
               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.
+            The deployment of this job 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)}
+            This job is creating a deployment to #{environment_link_for_build(@build.project, @build)}
             - if environment.try(:last_deployment)
               and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
 
@@ -52,9 +52,9 @@
       - if @build.erased?
         .erased.alert.alert-warning
           - if @build.erased_by_user?
-            Build has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
+            Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
           - else
-            Build has been erased #{time_ago_with_tooltip(@build.erased_at)}
+            Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
       - else
         #js-build-scroll.scroll-controls
           .scroll-step
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 324a7f8cd3fdc7f74661bb5fe316a951e3e7dbe4..762ff34a9eccdf03f81821a0a51158a03a1eef06 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,5 +1,5 @@
 - if !project.empty_repo? && can?(current_user, :download_code, project)
-  .project-action-button.dropdown.inline
+  .project-action-button.dropdown.inline>
     %button.btn{ 'data-toggle' => 'dropdown' }
       = icon('download')
       = icon("caret-down")
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 520113639b7e20e2c1aaee3f9d362fd12e380385..5ea85f9fd4c9c8f2563846a6fba38c25026f7f3d 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -32,10 +32,10 @@
       = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
 
     - if build.stuck?
-      = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+      = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
 
     - if retried
-      = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried')
+      = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
 
     .label-container
       - if build.tags.any?
@@ -85,7 +85,7 @@
     - if build.finished_at
       %p.finished-at
         = icon("calendar")
-        %span #{time_ago_with_tooltip(build.finished_at)}
+        %span= time_ago_with_tooltip(build.finished_at)
 
   %td.coverage
     - if coverage && build.try(:coverage)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 990bfbcf951f6d24796c4c9f7960fc64d50ec398..cdab1e1b1a6bfddb2252ac4212af84fe49471938 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -15,7 +15,7 @@
     - else
       %span.api.monospace API
     - if pipeline.latest?
-      %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
+      %span.label.label-success.has-tooltip{ title: 'Latest job for this branch' } latest
     - if pipeline.triggered?
       %span.label.label-primary triggered
     - if pipeline.yaml_errors.present?
@@ -78,7 +78,7 @@
         .btn-group.inline
           - if actions.any?
             .btn-group
-              %button.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{ type: 'button', 'data-toggle' => 'dropdown' }
+              %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual job', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual job' }
                 = custom_icon('icon_play')
                 = icon('caret-down', 'aria-hidden' => 'true')
               %ul.dropdown-menu.dropdown-menu-align-right
@@ -89,7 +89,7 @@
                       %span= build.name
           - if artifacts.present?
             .btn-group
-              %button.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{ type: 'button', 'data-toggle' => 'dropdown' }
+              %button.dropdown-toggle.btn.btn-default.build-artifacts.has-tooltip.js-pipeline-dropdown-download{ type: 'button', title: 'Artifacts', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Artifacts' }
                 = icon("download")
                 = icon('caret-down')
               %ul.dropdown-menu.dropdown-menu-align-right
@@ -102,8 +102,8 @@
       - if can?(current_user, :update_pipeline, pipeline.project)
         .cancel-retry-btns.inline
           - if pipeline.retryable?
-            = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+            = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: 'Retry', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Retry' , method: :post do
               = icon("repeat")
           - if pipeline.cancelable?
-            = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+            = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: 'Cancel', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Cancel' , method: :post do
               = icon("remove")
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 08eb0c57f669b87f355d12f3bade600083b21fc6..4d0b7a5ca8537f30013acb0ebddc03bfdd840d3f 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,8 +1,7 @@
 .page-content-header
   .header-main-content
-    %strong
+    %strong Commit #{@commit.short_id}
     = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
-    = @commit.short_id
     %span.hidden-xs authored
     #{time_ago_with_tooltip(@commit.authored_date)}
     %span by
@@ -64,9 +63,10 @@
   - if @commit.status
     .well-segment.pipeline-info
       %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
-        = ci_icon_for_status(@commit.status)
+        = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
+          = ci_icon_for_status(@commit.status)
       Pipeline
-      = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace"
+      = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
       for
       = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
       %span.ci-status-label
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 08d3443b3d02f8afbf68052b25af604805aa636e..6abff6aaf95b7aca0b375c6a73d7a26b2da8c56c 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -13,7 +13,7 @@
         Pipeline
         = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
         with
-        = pluralize pipeline.statuses.count(:id), "build"
+        = pluralize pipeline.statuses.count(:id), "job"
         - if pipeline.ref
           for
           = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
@@ -44,7 +44,7 @@
     %thead
       %tr
         %th Status
-        %th Build ID
+        %th Job ID
         %th Name
         %th
         - if pipeline.project.build_coverage_enabled?
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index 00e7cdd17297d19226aeae58103156c161db0282..89968cf4e0dc64335fc63ba7766df772466d5a79 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -1,6 +1,5 @@
-- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits"
+- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
 
-= render "commit_box"
-
-= render "ci_menu"
-= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
+= render 'commit_box'
+= render 'ci_menu'
+= render 'pipelines_list', pipelines: @pipelines
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index fcc367951ad30a53b42841fac20db06462aa40f6..904cdb5767f383e1b2626854755030f7394f063f 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -2,7 +2,7 @@
 - commits, hidden = limited_commits(@commits)
 
 - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
-  %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
+  %li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}
   %li.commits-row
     %ul.content-list.commit-list.table-list.table-wide
       = render commits, project: project, ref: ref
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index e77f23c7fd878f205463950e2a1b2c51b70fdfbb..d94f23f5a3839b1e474ec633fcb7afc4c8eb6e5d 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -9,10 +9,13 @@
   = render "head"
 
 %div{ class: container_class }
-  .row-content-block.second-block.content-component-block
+  .row-content-block.second-block.content-component-block.flex-container-block
     .tree-ref-holder
       = render 'shared/ref_switcher', destination: 'commits'
 
+    %ul.breadcrumb.repo-breadcrumb
+      = commits_breadcrumbs
+
     .block-controls.hidden-xs.hidden-sm
       - if @merge_request.present?
         .control
@@ -30,8 +33,6 @@
         .control
           = link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do
             = icon("rss")
-    %ul.breadcrumb.repo-breadcrumb
-      = commits_breadcrumbs
 
   %div{ id: dom_id(@project) }
     %ol#commits-list.list-unstyled.content_list
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 819e9bc15ae591b2ae222f0760b4d71e62e0b8ff..9c8f58d4aeac84e41da37618535bd33a70db9da9 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -16,9 +16,9 @@
           There isn't anything to compare.
         %p.slead
           - if params[:to] == params[:from]
-            %span.label-branch #{params[:from]}
+            %span.label-branch= params[:from]
             and
-            %span.label-branch #{params[:to]}
+            %span.label-branch= params[:to]
             are the same.
           - else
             You'll need to use different branch names to get a valid comparison.
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 9238f232c7eab3d03a3bf395611a23f60b7374d7..c468202569f70d5757f74f03355280e4b0c9410f 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -1,6 +1,6 @@
 %tr.deployment
   %td
-    %strong= "##{deployment.iid}"
+    %strong ##{deployment.iid}
 
   %td
     = render 'projects/deployments/commit', deployment: deployment
@@ -8,7 +8,7 @@
   %td.build-column
     - if deployment.deployable
       = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
-        = "#{deployment.deployable.name} (##{deployment.deployable.id})"
+        #{deployment.deployable.name} (##{deployment.deployable.id})
       - if deployment.user
         by
         = user_avatar(user: deployment.user, size: 20)
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index 52a1ece7d605a72ca3dc8a69ed6a0eb502cc5562..b87b79b170ebd76f0224c5c9f7213599d9635ac8 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -1,5 +1,5 @@
 .diff-content.diff-wrap-lines
-  - # Skip all non non-supported blobs
+  -# Skip all non non-supported blobs
   - return unless blob.respond_to?(:text?)
   - if diff_file.too_large?
     .nothing-here-block This diff could not be displayed because it is too large.
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c37a33bbcd5af4049643c0d9174f37c02d10fab7..fc478ccc9954e74cacd18c3fca763f49ccc76881 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -5,7 +5,7 @@
     - unless diff_file.submodule?
       .file-actions.hidden-xs
         - if blob_text_viewable?(blob)
-          = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
+          = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
             = icon('comment')
           \
           - if editable_diff?(diff_file)
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 90c9a0c6c2b3edb22cd01fba125c07de210b5f95..ddec775b789d1529b85a7e100010b45f32bb4604 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -25,4 +25,4 @@
 
   - if diff_file.mode_changed?
     %small
-      = "#{diff_file.a_mode} → #{diff_file.b_mode}"
+      #{diff_file.a_mode} → #{diff_file.b_mode}
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 1bccaaf527323859820e1b676bf41b8ed318a94e..ca10921c5e2e485cddc1fd543b898cd662887c66 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -9,7 +9,7 @@
     %span.wrap
       .frame{ class: image_diff_class(diff) }
         %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path }
-      %p.image-info= "#{number_to_human_size file.size}"
+      %p.image-info= number_to_human_size(file.size)
 - else
   .image
     .two-up.view
@@ -18,7 +18,7 @@
           %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) }
             %img{ src: old_file_raw_path, alt: diff.old_path }
         %p.image-info.hide
-          %span.meta-filesize= "#{number_to_human_size old_file.size}"
+          %span.meta-filesize= number_to_human_size(old_file.size)
           |
           %b W:
           %span.meta-width
@@ -30,7 +30,7 @@
           %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) }
             %img{ src: file_raw_path, alt: diff.new_path }
         %p.image-info.hide
-          %span.meta-filesize= "#{number_to_human_size file.size}"
+          %span.meta-filesize= number_to_human_size(file.size)
           |
           %b W:
           %span.meta-width
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index b087485aa17648dd5a7ad4c53539d0671f93d2f2..f361204ecac06043705000fff8110f96ed69d0d2 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -14,7 +14,7 @@
             - left_line_code = diff_file.line_code(left)
             - left_position = diff_file.position(left)
             %td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
-              %a{ href: "##{left_line_code}" }= raw(left.old_pos)
+              %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
             %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
         - else
           %td.old_line.diff-line-num.empty-cell
@@ -27,7 +27,7 @@
             - right_line_code = diff_file.line_code(right)
             - right_position = diff_file.position(right)
             %td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
-              %a{ href: "##{right_line_code}" }= raw(right.new_pos)
+              %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
             %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
         - else
           %td.old_line.diff-line-num.empty-cell
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 290f696d58270b639b46984bbe8556594b96a7e9..8e24e28765f4ac7da7af8153a2fd91fd856e5a48 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,7 +2,7 @@
   .commit-stat-summary
     Showing
     = link_to '#', class: 'js-toggle-button' do
-      %strong #{pluralize(diff_files.size, "changed file")}
+      %strong= pluralize(diff_files.size, "changed file")
     with
     %strong.cgreen #{diff_files.sum(&:added_lines)} additions
     and
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 1d7fdf68cb30876ec13a3f32090c0addd8ae18cd..7a2dacdb1e7d583b8490aac8ec28c3fd53f5919b 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -7,10 +7,17 @@
       .project-edit-errors
       = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
         %fieldset.append-bottom-0
-          .form-group
-            = f.label :name, class: 'label-light' do
-              Project name
-            = f.text_field :name, class: "form-control", id: "project_name_edit"
+          .row
+            .form-group.col-md-9
+              = f.label :name, class: 'label-light', for: 'project_name_edit' do
+                Project name
+              = f.text_field :name, class: "form-control", id: "project_name_edit"
+
+            .form-group.col-md-3
+              = f.label :id, class: 'label-light' do
+                Project ID
+              = f.text_field :id, class: 'form-control', readonly: true
+
           .form-group
             = f.label :description, class: 'label-light' do
               Project description
@@ -56,7 +63,7 @@
 
                 .row
                   .col-md-9.project-feature.nested
-                    = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+                    = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
                     %span.help-block Submit, test and deploy your changes before merge
                   .col-md-3
                     = project_feature_access_select(:builds_access_level)
@@ -173,11 +180,13 @@
           %p
             The following items will NOT be exported:
           %ul
-            %li Build traces and artifacts
+            %li Job traces and artifacts
             %li LFS objects
             %li Container registry images
-  %hr
+            %li CI variables
+            %li Any encrypted tokens
   - if can? current_user, :archive_project, @project
+    %hr
     .row.prepend-top-default
       .col-lg-3
         %h4.warning-title.prepend-top-0
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6e0d9456900b7bb8bfec6d2a5b7f9c4376a1413d..7800d6ac382048abbd2e8b3e87cd5d9ba170d7d4 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -5,7 +5,7 @@
 %div{ class: container_class }
   .top-area.adjust
     .col-md-9
-      %h3.page-title= @environment.name.capitalize
+      %h3.page-title= @environment.name
     .col-md-3
       .nav-controls
         = render 'projects/environments/terminal_button', environment: @environment
@@ -32,8 +32,8 @@
             %tr
               %th ID
               %th Commit
-              %th Build
-              %th
+              %th Job
+              %th Created
               %th.hidden-xs
 
           = render @deployments
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 6c8a6f051a915d334591fed95588a8e24fc71e17..f4aa523b32d9e263cf810b0ce1324e1ea491fd0c 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -1,7 +1,7 @@
 .top-area
   .nav-text
     - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
-    = "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}"
+    #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
 
   .nav-controls
     = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
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 6d7af1685fddbebf3898b0b53a813b89fad77893..07fb80750d61178aba8182742d01b37854dd3b5a 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
@@ -77,7 +77,7 @@
     - if generic_commit_status.finished_at
       %p.finished-at
         = icon("calendar")
-        %span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
+        %span= time_ago_with_tooltip(generic_commit_status.finished_at)
 
   %td.coverage
     - if coverage && generic_commit_status.try(:coverage)
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
index 431657c4dcbedcd0c49dda35a3028f1f9a0717bb..b6f453b97363776b955e2c775ce7275781e02a26 100644
--- a/app/views/projects/graphs/ci/_builds.haml
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -1,4 +1,4 @@
-%h4 Build charts
+%h4 Pipelines charts
 %p
   &nbsp;
   %span.cgreen
@@ -11,19 +11,19 @@
 
 .prepend-top-default
   %p.light
-    Builds for last week
+    Jobs for last week
     (#{date_from_to(Date.today - 7.days, Date.today)})
   %canvas#weekChart{ height: 200 }
 
 .prepend-top-default
   %p.light
-    Builds for last month
+    Jobs for last month
     (#{date_from_to(Date.today - 30.days, Date.today)})
   %canvas#monthChart{ height: 200 }
 
 .prepend-top-default
   %p.light
-    Builds for last year
+    Jobs for last year
   %canvas#yearChart.padded{ height: 250 }
 
 - [:week, :month, :year].each do |scope|
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 7e34a89f9ae772aa694c2c9b538824b6c2b701a2..c8a82f7bca39300e77c89057c674683fd3303761 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -11,7 +11,7 @@
 
   %p.lead
     Commit statistics for
-    %strong #{@ref}
+    %strong= @ref
     #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
 
   .row
@@ -19,19 +19,19 @@
       %ul
         %li
           %p.lead
-            %strong #{@commits_graph.commits.size}
+            %strong= @commits_graph.commits.size
             commits during
-            %strong #{@commits_graph.duration}
+            %strong= @commits_graph.duration
             days
         %li
           %p.lead
             Average
-            %strong #{@commits_graph.commit_per_day}
+            %strong= @commits_graph.commit_per_day
             commits per day
         %li
           %p.lead
             Contributed by
-            %strong #{@commits_graph.authors}
+            %strong= @commits_graph.authors
             authors
     .col-md-6
       %div
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index bd46af339cf18f540125c53dee44c0b2aabe4fec..f3be343daae546e687432b2f3aeffcc39f30b07d 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -34,7 +34,7 @@
           = note_count
 
   .issue-info
-    #{issue.to_reference} &middot;
+    #{issuable_reference(issue)} &middot;
     opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
     by #{link_to_member(@project, issue.author, avatar: false)}
     - if issue.milestone
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 18e8372ecab67019a842486d613aa9c4825ed4d3..5fbed8b9ab81c15683ed2bb969e958db520b946f 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -19,10 +19,8 @@
       = render 'shared/issuable/nav', type: :issues
       .nav-controls
         - if current_user
-          = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do
+          = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
             = icon('rss')
-            %span.icon-label
-              Subscribe
         - if can? current_user, :create_issue, @project
           = link_to new_namespace_project_issue_path(@project.namespace,
                                                      @project,
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 9fa00811af08bcdd1cf341b6104d48a10ccfb560..11636d7ebc7396e723a3ffaee93c742eeeede390 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -75,7 +75,7 @@
       // This element is filled in using JavaScript.
 
   .content-block.content-block-small
-    = render 'new_branch'
+    = render 'new_branch' unless @issue.confidential?
     = render 'award_emoji/awards_block', awardable: @issue, inline: true
 
   %section.issuable-discussion
diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml
deleted file mode 100644
index 8d09e2bda114a2780f6a6c7f50a336bd2afbfdb6..0000000000000000000000000000000000000000
--- a/app/views/projects/labels/destroy.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- if @labels.empty?
-  $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 05a8475dcd68c22cb6364476874de91f701d940d..29f861c09c614955e4060c9335fc8a026c4ffcef 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,37 +3,35 @@
 - hide_class = ''
 = render "projects/issues/head"
 
-%div{ class: container_class }
-  .top-area.adjust
-    .nav-text
-      Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
+- if @labels.exists? || @prioritized_labels.exists?
+  %div{ class: container_class }
+    .top-area.adjust
+      .nav-text
+        Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
 
-    .nav-controls
-      - if can?(current_user, :admin_label, @project)
-        = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
-          New label
-
-  .labels
-    - if can?(current_user, :admin_label, @project)
-      -# Only show it in the first page
-      - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
-      .prioritized-labels{ class: ('hide' if hide) }
-        %h5 Prioritized Labels
-        %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
-          %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
-          - if @prioritized_labels.present?
-            = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
+      .nav-controls
+        - if can?(current_user, :admin_label, @project)
+          = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+            New label
 
-    .other-labels
+    .labels
       - if can?(current_user, :admin_label, @project)
-        %h5{ class: ('hide' if hide) } Other Labels
-      %ul.content-list.manage-labels-list.js-other-labels
-        - if @labels.present?
-          = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
-        = paginate @labels, theme: 'gitlab'
-      - if @labels.blank?
-        .nothing-here-block
+        -# Only show it in the first page
+        - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
+        .prioritized-labels{ class: ('hide' if hide) }
+          %h5 Prioritized Labels
+          %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
+            #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
+              = render 'shared/empty_states/priority_labels'
+            - if @prioritized_labels.present?
+              = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
+
+      - if @labels.present?
+        .other-labels
           - if can?(current_user, :admin_label, @project)
-            Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
-          - else
-            No labels created
+            %h5{ class: ('hide' if hide) } Other Labels
+          %ul.content-list.manage-labels-list.js-other-labels
+            = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
+            = paginate @labels, theme: 'gitlab'
+- else
+  = render 'shared/empty_states/labels'
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index 605c7f61dee2355566f7451e874298e6172601d5..aac74a25b7549b0149fdc1a6b40e934877b8ddd8 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -1,3 +1,7 @@
+- if @teams_error_message
+  = content_for :flash_message do
+    .alert.alert-danger= @teams_error_message
+
 %p
   You aren’t a member of any team on the Mattermost instance at
   %strong= Gitlab.config.mattermost.host
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index e3b0aa7e644edaae0944502b175cc997a2f18cd3..513f0818169b781cba5abea9b5a917e5fcd3c8e8 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -46,7 +46,7 @@
           = note_count
 
   .merge-request-info
-    #{merge_request.to_reference} &middot;
+    #{issuable_reference(merge_request)} &middot;
     opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
     by #{link_to_member(@project, merge_request.author, avatar: false)}
     - if merge_request.target_project.default_branch != merge_request.target_branch
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 36c6e7a8dad92d68ff63c9679d92d4561654c596..d3c013b3f21c613a85a9edc780eafda3aab1cca3 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -3,9 +3,9 @@
 %p.slead
   - source_title, target_title = format_mr_branch_names(@merge_request)
   From
-  %strong.label-branch #{source_title}
+  %strong.label-branch= source_title
   %span into
-  %strong.label-branch #{target_title}
+  %strong.label-branch= target_title
 
   %span.pull-right
     = link_to 'Change branches', mr_change_branches_path(@merge_request)
@@ -43,7 +43,7 @@
       #commits.commits.tab-pane.active
         = render "projects/merge_requests/show/commits"
       #diffs.diffs.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
       - if @pipelines.any?
         #pipelines.pipelines.tab-pane
           = render "projects/merge_requests/show/pipelines"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 2a7cd3a19d0bb6d117f1526ee07f0f3949962a4c..9585a9a3ad4e18b59cf9631914f265e73a0bc58f 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -52,7 +52,7 @@
       = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
 
     .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
-      %div{ class: container_class }
+      .merge-request-tabs-container
         %ul.merge-request-tabs.nav-links.no-top.no-bottom
           %li.notes-tab
             = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
@@ -92,11 +92,11 @@
               = render "projects/merge_requests/discussion"
 
       #commits.commits.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
       #pipelines.pipelines.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
       #diffs.diffs.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
 
     .mr-loading-status
       = spinner
@@ -115,4 +115,3 @@
   });
 
   var mrRefreshWidgetUrl = "#{mr_widget_refresh_url(@merge_request)}";
-
diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
index 2595ce74ac0ee2a8c090346273a16b02a34e5308..0839880713fe24a50e21edba4c1ef5faf7fc60e4 100644
--- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
@@ -8,5 +8,5 @@
       '@click' => "onClickResolveModeButton(file, 'edit')",
       type: 'button' }
       Edit inline
-  %a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" }
+  %a.btn.view-file{ ":href" => "file.blobPath" }
     View file @{{conflictsData.shortCommitSha}}
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index b0f3c86fd210b65afe0acfcde294b7db57b0deb2..74a7b1dc4981413eb1016d4abe057e71431eba11 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -25,7 +25,7 @@
                         latest version
                       - else
                         version #{version_index(merge_request_diff)}
-                    .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+                    .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)},
                       = time_ago_with_tooltip(merge_request_diff.created_at)
@@ -55,14 +55,14 @@
                           latest version
                         - else
                           version #{version_index(merge_request_diff)}
-                      .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+                      .monospace= short_sha(merge_request_diff.head_commit_sha)
                       %small
                         = time_ago_with_tooltip(merge_request_diff.created_at)
                 %li
                   = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
                     %strong
                       #{@merge_request.target_branch} (base)
-                    .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+                    .monospace= short_sha(@merge_request_diff.base_commit_sha)
 
     - if different_base?(@start_version, @merge_request_diff)
       .content-block
@@ -72,7 +72,7 @@
         = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
           new commits
         from
-        %code #{@merge_request.target_branch}
+        %code= @merge_request.target_branch
 
     - unless @merge_request_diff.latest? && !@start_sha
       .comments-disabled-notif.content-block
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index c80dc33058d3d60d21779222c527069c964479b6..ae134563ead0bfa931bd61c3d78c9e2f6d2dba57 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -2,25 +2,26 @@
   .mr-widget-heading
     - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
       .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
-        = ci_icon_for_status(status)
+        = link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
+          = ci_icon_for_status(status)
         %span
           Pipeline
           = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
           = ci_label_for_status(status)
         for
         = succeed "." do
-          = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
+          = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace js-commit-link"
         %span.ci-coverage
 
 - elsif @merge_request.has_ci?
-  - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
-  - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
+  -# Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
+  -# TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
   .mr-widget-heading
     - %w[success skipped canceled failed running pending].each do |status|
       .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" }
         = ci_icon_for_status(status)
         %span
-          CI build
+          CI job
           = ci_label_for_status(status)
         for
         - commit = @merge_request.diff_head_commit
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 38328501ffd4ed6c612483ceb2ba8dd4cdc5320b..5de594738405bb2bac6041ca8487c366101fb795 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -16,14 +16,18 @@
     gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
     ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
     ci_message: {
-      normal: "Build {{status}} for \"{{title}}\"",
-      preparing: "{{status}} build for \"{{title}}\""
+      normal: "Job {{status}} for \"{{title}}\"",
+      preparing: "{{status}} job for \"{{title}}\""
     },
     ci_enable: #{@project.ci_service ? "true" : "false"},
     ci_title: {
-      preparing: "{{status}} build",
-      normal: "Build {{status}}"
+      preparing: "{{status}} job",
+      normal: "Job {{status}}"
     },
+    ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}",
+    ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json},
+    commits_path: "#{project_commits_path(@project)}",
+    pipeline_path: "#{project_pipelines_path(@project)}",
     pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
   };
 
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 7809e9c8c72fa318f5e935d6e86bc1d8f0be8d51..39cb0ca9cdcfa9c54d6719de0bec87000cf0f564 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -35,10 +35,10 @@
           The source branch will be removed.
       - elsif @merge_request.can_remove_source_branch?(current_user)
         .accept-control.checkbox
-          = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+          = label_tag :should_remove_source_branch, class: "merge-param-checkbox" do
             = check_box_tag :should_remove_source_branch
             Remove source branch
-      .accept-control.right
+      .accept-control
         = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
           = icon('edit')
           Modify commit message
diff --git a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
index 14f51af536080a74885a1dcb2dc39de640fb99e2..a18c2ad768f977945bbe95da21798732b2f15d80 100644
--- a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
@@ -1,6 +1,6 @@
 %h4
   = icon('exclamation-triangle')
-  The build for this merge request failed
+  The job for this merge request failed
 
 %p
-  Please retry the build or push a new commit to fix the failure.
+  Please retry the job or push a new commit to fix the failure.
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index 072d01d144e89eafb05142883d0e6d27f0ee5e45..304b0afcf9394ed87f1ad6e4cba568032fabc352 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -1,3 +1,6 @@
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_tag('merge_request_widget/ci_bundle.js')
+
 %h4
   Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
   to be merged automatically when the pipeline succeeds.
@@ -16,7 +19,7 @@
   - if remove_source_branch_button || user_can_cancel_automatic_merge
     .clearfix.prepend-top-10
       - if remove_source_branch_button
-        = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.diff_head_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+        = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
           = icon('times')
           Remove Source Branch When Merged
 
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 36c388c3318ae2505b39a9082531e600f6cc403c..09339e520dd9d5d118df0edfb040e4aa8b100ac9 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -69,7 +69,7 @@
         - if note_editable
           .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
             #{note.note}
-          %textarea.hidden.js-task-list-field.original-task-list #{note.note}
+          %textarea.hidden.js-task-list-field.original-task-list= note.note
         .note-awards
           = render 'award_emoji/awards_block', awardable: note, inline: false
         - if note.system
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index b10dd47709f07e02eb6b6c61345511ac93e804b3..721a9b6beb51556cd3e76f0a8d6959d6bd615938 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -11,9 +11,9 @@
 
         - if project_nav_tab? :builds
           = nav_link(controller: %w(builds)) do
-            = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+            = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
               %span
-                Builds
+                Jobs
 
         - if project_nav_tab? :environments
           = nav_link(controller: %w(environments)) do
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index ca76f13ef5ead69fa6d122e961802efb821672ec..a6cd2d83bd5b30fe1e1fce24b45e5f14ece74f95 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -23,9 +23,9 @@
 .info-well
   - if @commit.status
     .well-segment.pipeline-info
-      %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
-        = ci_icon_for_status(@commit.status)
-      = pluralize @pipeline.statuses.count(:id), "build"
+      .icon-container
+        = icon('clock-o')
+      = pluralize @pipeline.statuses.count(:id), "job"
       - if @pipeline.ref
         from
         = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 88af41aa83516987397dc909fad01b86fced2fd3..53067cdcba405916dc8d90b140df5a497d59a2de 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -5,7 +5,7 @@
         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
+        Jobs
         %span.badge.js-builds-counter= pipeline.statuses.count
 
 
@@ -33,7 +33,7 @@
         %thead
           %tr
             %th Status
-            %th Build ID
+            %th Job ID
             %th Name
             %th
             - if pipeline.project.build_coverage_enabled?
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
index 1f698558bce553db179fb13c70b345fd2171df9f..18328c67f0243a9c94209f0bac6912eaead49b5e 100644
--- a/app/views/projects/pipelines_settings/show.html.haml
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -66,7 +66,7 @@
             %span.input-group-addon /
           %p.help-block
             A regular expression that will be used to find the test coverage
-            output in the build trace. Leave blank to disable
+            output in the job trace. Leave blank to disable
             = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing')
           .bs-callout.bs-callout-info
             %p Below are examples of regex for existing tools:
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 9738f369a35368d50cbb842893b7b4698066df3b..c7996077bc7eabffde102ff1bfe06af416be9cbe 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -1,7 +1,7 @@
 .panel.panel-default
   .panel-heading
     Group members with access to
-    %strong #{@group.name}
+    %strong= @group.name
     %span.badge= members.size
     - if can?(current_user, :admin_group_member, @group)
       .controls
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
index d7f5fa965270246888f60fa0ce1c5590e05df7ef..fdeb5f21fbe697c42c3522601e7ec1c3bc8a82db 100644
--- a/app/views/projects/project_members/_groups.html.haml
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -1,7 +1,7 @@
 .panel.panel-default.project-members-groups
   .panel-heading
     Groups with access to
-    %strong #{@project.name}
+    %strong= @project.name
     %span.badge= group_links.size
   %ul.content-list
     = render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 77370c14def6276cfb408d853c0136d097e40bba..7902ddb1ae9d4d5c05aaa5a6e3d88ce7f7f3530d 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -5,9 +5,9 @@
   .panel.panel-default
     .panel-heading
       Shared with
-      %strong #{shared_group.name}
+      %strong= shared_group.name
       group, members with
-      %strong #{group_links.human_access}
+      %strong= group_links.human_access
       role (#{shared_group_users_count})
       - if can?(current_user, :admin_group, shared_group)
         .panel-head-actions
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 5292e73be7af837289dfa7b8ca116baebb0918c0..81d57c77edfbbbdf161aaf16dd6e62ddce853f9d 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,7 +1,7 @@
 .panel.panel-default
   .panel-heading
     Members with access to
-    %strong #{@project.name}
+    %strong= @project.name
     %span.badge= @project_members.total_count
     = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
       .form-group
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 33d5cbff42069036094b23d9ef39901ee25b3700..79d8d721aa9b7caf0e2f2d80c6760c9dc246b873 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -7,7 +7,7 @@
     .oneline
       .title
         Release notes for tag
-        %strong #{@tag.name}
+        %strong= @tag.name
 
 
   = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index 33a9a96183cb821179c9bcffe959afef5d76ca19..98e72f6c54790101690da1fd7cc62baec28d6d9e 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -5,7 +5,7 @@
     .col-sm-10
       .checkbox
         = f.check_box :active
-        %span.light Paused Runners don't accept new builds
+        %span.light Paused Runners don't accept new jobs
   .form-group
     = label :run_untagged, 'Run untagged jobs', class: 'control-label'
     .col-sm-10
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 51b0939564ebc25a0736e1db9d0a08dfddf5e9d1..dcff675eafc387954da96155acbd128123f02693 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -9,10 +9,10 @@
       (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
     %li
       Specify the following URL during the Runner setup:
-      %code #{ci_root_url(only_path: false)}
+      %code= ci_root_url(only_path: false)
     %li
       Use the following registration token during setup:
-      %code #{@project.runners_token}
+      %code= @project.runners_token
     %li
       Start the Runner!
 
diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml
index 92957470070cccf51dc020d586ba0ae7ba612221..d6f691d9c240770bd9cc8ce1e553c162a7cd0f7f 100644
--- a/app/views/projects/runners/index.html.haml
+++ b/app/views/projects/runners/index.html.haml
@@ -2,7 +2,7 @@
 
 .light.prepend-top-default
   %p
-    A 'Runner' is a process which runs a build.
+    A 'Runner' is a process which runs a job.
     You can setup as many Runners as you need.
     %br
     Runners can be placed on separate users, servers, and even on your local machine.
@@ -12,14 +12,14 @@
     %ul
       %li
         %span.label.label-success active
-        \- Runner is active and can process any new builds
+        \- Runner is active and can process any new jobs
       %li
         %span.label.label-danger paused
-        \- Runner is paused and will not receive any new builds
+        \- Runner is paused and will not receive any new jobs
 
 %hr
 
-%p.lead To start serving your builds you can either add specific Runners to your project or use shared Runners
+%p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners
 .row
   .col-sm-6
     = render 'specific_runners'
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index fc338dcf887cbc3dc5821d51aa57ccd7ac6e2641..f1a80f1d5e12ef82e598d3f224adf1e0e052c624 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -17,4 +17,4 @@
             - disabled_title = @service.disabled_title
 
           = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title
-        = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
+        = link_to "Cancel", namespace_project_settings_integrations_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index c929eee3bb92425c31a388fdc484320826c7c055..fcc91be11cd3de593f5075e5125f1d9c04e7a40f 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -4,4 +4,4 @@
       .col-sm-9.col-sm-offset-3
         = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
           = custom_icon('mattermost_logo', size: 15)
-          = 'Add to Mattermost'
+          Add to Mattermost
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 068a66103501e17fbdb7d8130c9f8960dd20c53e..e2a5107a883f7bcaa16b5ed5073b48fe439ae282 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
   - if can?(current_user, :create_project_snippet, @project)
     = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
       New snippet
+  - if @snippet.submittable_as_spam? && current_user.admin?
+    = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
 - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -27,3 +29,6 @@
           %li
             = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
               Edit
+        - if @snippet.submittable_as_spam? && current_user.admin?
+          %li
+            = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 216f70f5605240dad7e00a2746d828071e54c685..fb39028529dab1da1e9e2851427af0a03e362413 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -3,4 +3,4 @@
 %h3.page-title
   Edit Snippet
 %hr
-= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
+= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 772a594269ca8bf7e43b1c3e4249c4dad8a6ab36..cfed3a79bc5b0187eeb5576d2479ab63130346ff 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -3,4 +3,4 @@
 %h3.page-title
   New Snippet
 %hr
-= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 2c08221565b6b3a484db374867074a9b38d81051..6855c463c6d75ab5201ca67a44f9b9f2fca378f1 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -7,10 +7,12 @@
           %th.hidden-xs
             .pull-left Last commit
             .last-commit.hidden-sm.pull-left
+              %i.fa.fa-angle-right
               %small.light
-                = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
                 = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+                = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
                 = time_ago_with_tooltip(@commit.committed_date)
+                \-
                 = @commit.full_title
             %small.commit-history-link-spacer &#124;
             = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link'
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index 6e5dd1b196dde5c3b6e4eff0bef4e1beb63fc46d..b9c4e323430a7397452fd7fe697baeb00a9c4dd8 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -67,7 +67,7 @@
           In the
           %code .gitlab-ci.yml
           of another project, include the following snippet.
-          The project will be rebuilt at the end of the build.
+          The project will be rebuilt at the end of the job.
 
         %pre
           :plain
@@ -86,12 +86,12 @@
           :plain
              #{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN
         %h5.prepend-top-default
-          Pass build variables
+          Pass job variables
 
         %p.light
           Add
           %code variables[VARIABLE]=VALUE
-          to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+          to an API request. Variable values can be used to distinguish between triggered jobs and normal jobs.
 
           With cURL:
 
diff --git a/app/views/projects/variables/_content.html.haml b/app/views/projects/variables/_content.html.haml
index 0249e0c1bf1fcd81ccd8605d4244fa8e77775539..06477aba103c55b923c9dcfde69d176024ac282a 100644
--- a/app/views/projects/variables/_content.html.haml
+++ b/app/views/projects/variables/_content.html.haml
@@ -5,4 +5,4 @@
 %p
   So you can use them for passwords, secret keys or whatever you want.
 %p
-  The value of the variable can be visible in build log if explicitly asked to do so.
+  The value of the variable can be visible in job log if explicitly asked to do so.
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index e25d6a48573959760ff68cad2a3baed593f712ee..fb0efd85dcdd9f2ac73adf54d2c3353849309b9c 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -17,6 +17,13 @@
     %pre.dark
       :preserve
         gem install gollum
+    %p
+      It is recommended to install
+      %code github-markdown
+      so that GFM features render locally:
+    %pre.dark
+      :preserve
+        gem install github-markdown
 
     %h3 Clone your wiki
     %pre.dark
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 05a63016c09ac0d8c0f80d2629cbf27ba31fd463..821a39d61f542e649d6ab0f6d05b8d2d8df0718b 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -3,4 +3,4 @@
   %h4
     = icon('search')
     We couldn't find any results matching
-    %code #{@search_term}
+    %code= @search_term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 07b17bc69c06953195d552dbb1e1d327641e7658..2e6adf3027cefa48dfc36e3582ad0c8d7fc573bd 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -2,7 +2,7 @@
   %h4
     = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
       %span.term.str-truncated= merge_request.title
-    .pull-right #{merge_request.to_reference}
+    .pull-right= merge_request.to_reference
   - if merge_request.description.present?
     .description.term
       = preserve do
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index c9b7bd154afcb98bc17991fec1ab9580e57b4400..23ca647941429b4c53e4b1d0308b36102e7a4de0 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -9,7 +9,7 @@
     = link_to user_snippets_path(snippet.author) do
       = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
       = snippet.author_name
-    %span.light #{time_ago_with_tooltip(snippet.created_at)}
+    %span.light= time_ago_with_tooltip(snippet.created_at)
   %h4.snippet-title
   - snippet_path = reliable_snippet_path(snippet)
   = link_to snippet_path do
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 027d42396b48f42e52223b1870523f459bf80428..704d1d01a81fe9aa3f61265494b511dc79272ad0 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -20,4 +20,4 @@
       = link_to user_snippets_path(snippet_title.author) do
         = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
         = snippet_title.author_name
-      %span.light #{time_ago_with_tooltip(snippet_title.created_at)}
+      %span.light= time_ago_with_tooltip(snippet_title.created_at)
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
index ee0439105487cb921267d90ac6ab9f38385b62b3..94295970acf67ee2cd2cb810a42db71b4acef2ee 100644
--- a/app/views/shared/_choose_group_avatar_button.html.haml
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -1,4 +1,4 @@
-%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button
+%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button{ type: 'button' }
   %i.fa.fa-paperclip
   %span Choose File ...
 &nbsp;
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index e7cb93b17a714c74d0524f6ceedb50eaf0750bb7..f94f8ffc6049651e14e6e2386ac6c8b1b50110c1 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -14,7 +14,7 @@
           To prevent accidental actions we ask you to confirm your intention.
           %br
           Please type
-          %code.js-confirm-danger-match #{phrase}
+          %code.js-confirm-danger-match= phrase
           to proceed or close this modal to cancel.
 
         .form-group
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 65a3a6bddab3fdd9bbb50a54631eb37a996a0f17..54b5ae2402eca58dff15422a322b524252efd5e9 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
   = f.label :import_url, class: 'control-label' do
     %span Git repository URL
   .col-sm-10
-    = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
+    = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
 
     .well.prepend-top-20
       %ul
@@ -13,4 +13,4 @@
         %li
           The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
         %li
-          To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
+          To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 26b349e8a624af90dfa7ef4f835b4951dd170a88..3a49227961f4ce30d9d9af5a6f24ac6b64dfc26e 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,16 +1,7 @@
 - if @issues.to_a.any?
-  - @issues.group_by(&:project).each do |group|
-    .panel.panel-default.panel-small
-      - project = group[0]
-      .panel-heading
-        = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
-        - if can?(current_user, :create_issue, project)
-          .pull-right
-            = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
-
-      %ul.content-list.issues-list
-        - group[1].each do |issue|
-          = render 'projects/issues/issue', issue: issue
+  .panel.panel-default.panel-small.panel-without-border
+    %ul.content-list.issues-list
+      = render partial: 'projects/issues/issue', collection: @issues
   = paginate @issues, theme: "gitlab"
 - else
   = render 'shared/empty_states/issues'
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index f11f4471a9d29fdf5ca293add5ec5ee48cf8707b..ead9b84b991c80fce69c88c82b5f4cf79c625143 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -36,7 +36,7 @@
           %li
             = link_to 'Edit', edit_label_path(label)
           %li
-            = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
+            = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
 
   .pull-right.hidden-xs.hidden-sm.hidden-md
     = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
@@ -66,11 +66,15 @@
                 %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
                   Group level
 
+    - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group)
+      = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
+        %span.sr-only Promote to Group
+        = icon('level-up')
     - if can?(current_user, :admin_label, label)
       = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
         %span.sr-only Edit
         = icon('pencil-square-o')
-      = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
+      = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
         %span.sr-only Delete
         = icon('trash-o')
 
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index 2f3605b4d27547fdf103cdc11df6158d92b30d43..b7982b7fe9be4a61da9e5a73a33b54d29b5be41b 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,16 +1,8 @@
 - if @merge_requests.to_a.any?
-  - @merge_requests.group_by(&:target_project).each do |group|
-    .panel.panel-default.panel-small
-      - project = group[0]
-      .panel-heading
-        = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
-        - if can?(current_user, :create_merge_request, project)
-          .pull-right
-            = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
+  .panel.panel-default.panel-small.panel-without-border
+    %ul.content-list.mr-list
+      = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
 
-      %ul.content-list.mr-list
-        - group[1].each do |merge_request|
-          = render 'projects/merge_requests/merge_request', merge_request: merge_request
   = paginate @merge_requests, theme: "gitlab"
 
 - else
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index 39294fe1a0989d816e05b0a8fc6c4f94e6b71673..704893b4d5b29126b4b14f02e0d2752c00df6d33 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -6,14 +6,14 @@
     = link_to milestones_filter_path(state: 'opened') do
       Open
       - if @project
-        %span.badge #{counts[:opened]}
+        %span.badge= counts[:opened]
   %li{ class: milestone_class_for_state(params[:state], 'closed') }>
     = link_to milestones_filter_path(state: 'closed') do
       Closed
       - if @project
-        %span.badge #{counts[:closed]}
+        %span.badge= counts[:closed]
   %li{ class: milestone_class_for_state(params[:state], 'all') }>
     = link_to milestones_filter_path(state: 'all') do
       All
       - if @project
-        %span.badge #{counts[:all]}
+        %span.badge= counts[:all]
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
index 0eba1fe075f35762dbe37f0bae29c77dbfe02db7..c06d1ffa59b1c439631d66112947cd2c7eec5d37 100644
--- a/app/views/shared/_outdated_browser.html.haml
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -1,8 +1,7 @@
 - if outdated_browser?
-  - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
   .browser-alert
     GitLab may not work properly because you are using an outdated web browser.
     %br
     Please install a
-    = link_to 'supported web browser', link
+    = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
     for a better experience.
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ba5c2dae09dba48620f767124e52abcbb2288593
--- /dev/null
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -0,0 +1,11 @@
+.row.empty-state.labels
+  .pull-right.col-xs-12.col-sm-6
+    .svg-content
+      = render 'shared/empty_states/icons/labels.svg'
+  .col-xs-12.col-sm-6
+    .text-content
+      %h4 Labels can be applied to issues and merge requests to categorize them.
+      %p You can also star label to make it a priority label.
+      - if can?(current_user, :admin_label, @project)
+        = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
+        = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..bc268301a970c51bb09d9f9ba0aef615e8dfdf1b
--- /dev/null
+++ b/app/views/shared/empty_states/_priority_labels.html.haml
@@ -0,0 +1,3 @@
+.text-center
+  = render 'shared/empty_states/icons/priority_labels.svg'
+  %p Star labels to start sorting by priority
diff --git a/app/views/shared/empty_states/icons/_labels.svg b/app/views/shared/empty_states/icons/_labels.svg
new file mode 100644
index 0000000000000000000000000000000000000000..dc041a4a78b47cdb660b744d379e45d7ab0a099c
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_labels.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="787 240 386 274" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="37" cy="107" r="8"/><mask id="e" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="37" cy="75" r="8"/><mask id="f" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="42" cy="93" r="8"/><mask id="g" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><circle id="d" cx="43" cy="75" r="8"/><mask id="h" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(791 244)"><g transform="rotate(30 49.554 229.722)"><rect width="74" height="124" x="8.6" y="95.9" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="87" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><circle cx="26.5" cy="178.5" r="3.5" fill="#FC8A51"/><circle cx="47.5" cy="178.5" r="3.5" fill="#FC8A51"/><rect width="50" height="4" x="12" y="127" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="139" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#e)" stroke-linecap="round" xlink:href="#a"/><path stroke="#EEE" stroke-width="4" d="M37.3 107S10.5 18.3 81 .6" stroke-linecap="round"/><path fill="#FDE5D8" d="M31 189c0 3.3 2.7 6 6 6s6-2.7 6-6"/></g><g transform="translate(105 47)"><rect width="74" height="124" y="64" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><rect width="50" height="4" x="12" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#f)" stroke-linecap="round" xlink:href="#b"/><path fill="#B5A7DD" d="M56 149.7c-.6-1-.2-2 .7-2.7l1.8-1c1-.6 2-.2 2.7.7.5 1 .2 2.2-.7 2.8l-1.8 1c-1 .5-2 .2-2.7-.8zm-37.8 0c.5-1 .2-2-.7-2.7l-1.8-1c-1-.6-2-.2-2.7.7-.6 1-.2 2.2.7 2.8l1.8 1c1 .5 2 .2 2.7-.8zM33 151h9v4h-9v-4z"/><path fill="#6B4FBB" d="M59 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6zM35 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6z"/><path stroke="#EEE" stroke-width="4" d="M37 75S30 0 80 0" stroke-linecap="round"/></g><g transform="rotate(15 -82.507 752.644)"><rect width="74" height="124" x="14.6" y="81.8" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="5" y="73" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><path fill="#FDE5D8" d="M41 147c0-1 1-2 2-2s2 1 2 2v3c0 1-1 2-2 2s-2-1-2-2v-3zm16.8 6.2c.8-.7 2-.6 2.8.3.7.8.5 2-.3 2.8L58 158c-1 .8-2.2.7-3 0-.6-1-.4-2.3.4-3l2.4-1.8zm-32 3c-1-.6-1-2-.4-2.7.7-1 2-1 2.8-.3l2.4 1.8c.8.7 1 2 .3 3-.8.7-2 1-3 0l-2.3-1.7z"/><rect width="2" height="7" x="39" y="168" fill="#FC8A51" rx="1"/><rect width="2" height="7" x="45" y="168" fill="#FC8A51" rx="1"/><circle cx="40" cy="169" r="2" fill="#FC8A51"/><circle cx="46" cy="169" r="2" fill="#FC8A51"/><rect width="22" height="18" x="32" y="158" stroke="#FC8A51" stroke-width="4" rx="8"/><rect width="34" height="5" x="26" y="174" fill="#FC8A51" rx="2.5"/><rect width="50" height="4" x="17" y="113" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="23" y="125" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#g)" stroke-linecap="round" xlink:href="#c"/><path stroke="#EEE" stroke-width="4" d="M42 93S50 0 0 0" stroke-linecap="round"/></g><g transform="rotate(-15 276.18 -697.744)"><rect width="74" height="124" x="18.7" y="65.6" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="6" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><g transform="translate(25 129)"><path stroke="#B5A7DD" stroke-width="4" d="M32 14c0-7.7-6.3-14-14-14S4 6.3 4 14" stroke-linecap="round"/><path stroke="#B5A7DD" stroke-width="2" d="M33 15v13c0 4.4-3.6 8-8 8" stroke-linecap="round"/><rect width="7" height="4" x="20" y="34" fill="#6B4FBB" rx="2"/><rect width="7" height="13" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" rx="3.5"/><rect width="7" height="13" x="29" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" transform="matrix(-1 0 0 1 65 0)" rx="3.5"/></g><rect width="50" height="4" x="18" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="24" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#h)" stroke-linecap="round" xlink:href="#d"/><path stroke="#EEE" stroke-width="4" d="M43 75S50 0 0 0" stroke-linecap="round"/></g><circle cx="193" cy="47" r="12" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><circle cx="193" cy="47" r="5" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><g opacity=".2"><path fill="#FC8A51" d="M30.7 254.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM374.7 133.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM5.6 95H1.8c-1.3.2-2-.8-1.4-2l1.4-3.4-.2-3.8c0-1.3 1-2 2-1.4l3.6 1.4 3.7-.2c1.2 0 2 1 1.4 2L11 91.3V95c.2 1.2-.8 2-2 1.4L5.6 95z"/><path fill="#6B4FBB" d="M308.8 62l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8zM318 226.6h-3c-1-.2-1.4-1-1-2l1.4-2.5v-3c.2-1 1-1.4 2-1l2.6 1.4h3c1 .2 1.5 1 1 2l-1.4 2.6v3c-.2 1-1 1.5-2 1l-2.5-1.4zM121.8 8l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8z"/></g></g></svg>
diff --git a/app/views/shared/empty_states/icons/_priority_labels.svg b/app/views/shared/empty_states/icons/_priority_labels.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7282c2b215eb06d00d65e5b1313e41eab2c96617
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_priority_labels.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zM10.4 9.2C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg>
diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/icons/_todos_all_done.svg
similarity index 100%
rename from app/views/shared/empty_states/_todos_all_done.svg
rename to app/views/shared/empty_states/icons/_todos_all_done.svg
diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/icons/_todos_empty.svg
similarity index 100%
rename from app/views/shared/empty_states/_todos_empty.svg
rename to app/views/shared/empty_states/icons/_todos_empty.svg
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index f9a7aa4e29bd7372be09fa6d77c76086cbfa630f..dd9e433491bcac2b065713fe8b30ba14f362c5a7 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -32,7 +32,7 @@
 
     - if group_member
       as
-      %span #{group_member.human_access}
+      %span= group_member.human_access
 
   - if group.description.present?
     .description
diff --git a/app/views/shared/icons/_icon_action_cancel.svg b/app/views/shared/icons/_icon_action_cancel.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a1f700eb0ff6768bb87cd26088fe2aa59fc2ec08
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_cancel.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M19.25,14.9765625 C19.25,14.1380166 19.0234398,13.3697952 18.5703125,12.671875 L12.6796875,18.5546875 C13.3932327,19.0182315 14.1666625,19.25 15,19.25 C15.5781279,19.25 16.1289036,19.1367199 16.6523438,18.9101562 C17.1757839,18.6835926 17.6276023,18.3802102 18.0078125,18 C18.3880227,17.6197898 18.690103,17.1653672 18.9140625,16.6367188 C19.138022,16.1080703 19.25,15.5546904 19.25,14.9765625 Z M11.4453125,17.3125 L17.34375,11.421875 C16.6406215,10.9479143 15.8593793,10.7109375 15,10.7109375 C14.2291628,10.7109375 13.5182324,10.9010398 12.8671875,11.28125 C12.2161426,11.6614602 11.7005227,12.1796842 11.3203125,12.8359375 C10.9401023,13.4921908 10.75,14.2057253 10.75,14.9765625 C10.75,15.8203167 10.9817685,16.5989548 11.4453125,17.3125 Z M21,14.9765625 C21,15.7942749 20.8411474,16.5755171 20.5234375,17.3203125 C20.2057276,18.0651079 19.7799506,18.7057265 19.2460938,19.2421875 C18.7122369,19.7786485 18.0742225,20.2057276 17.3320312,20.5234375 C16.58984,20.8411474 15.8125041,21 15,21 C14.1874959,21 13.41016,20.8411474 12.6679688,20.5234375 C11.9257775,20.2057276 11.2877631,19.7786485 10.7539062,19.2421875 C10.2200494,18.7057265 9.79427242,18.0651079 9.4765625,17.3203125 C9.15885258,16.5755171 9,15.7942749 9,14.9765625 C9,14.1588501 9.15885258,13.37891 9.4765625,12.6367188 C9.79427242,11.8945275 10.2200494,11.255211 10.7539062,10.71875 C11.2877631,10.182289 11.9257775,9.75520992 12.6679688,9.4375 C13.41016,9.11979008 14.1874959,8.9609375 15,8.9609375 C15.8125041,8.9609375 16.58984,9.11979008 17.3320312,9.4375 C18.0742225,9.75520992 18.7122369,10.182289 19.2460938,10.71875 C19.7799506,11.255211 20.2057276,11.8945275 20.5234375,12.6367188 C20.8411474,13.37891 21,14.1588501 21,14.9765625 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_play.svg b/app/views/shared/icons/_icon_action_play.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6ac192cd7e96007d7c294d68dfebc5e789ef1440
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_play.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M21.5401786,15.2320328 L11.90625,20.5858274 C11.7950143,20.6486998 11.6994982,20.6559541 11.6196987,20.6075908 C11.5398992,20.5592275 11.5,20.4721748 11.5,20.3464301 L11.5,9.66785867 C11.5,9.54211399 11.5398992,9.45506129 11.6196987,9.40669795 C11.6994982,9.35833462 11.7950143,9.36558901 11.90625,9.42846135 L21.5401786,14.782256 C21.6514142,14.8451283 21.7070312,14.9200904 21.7070312,15.0071444 C21.7070312,15.0941984 21.6514142,15.1691604 21.5401786,15.2320328 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_retry.svg b/app/views/shared/icons/_icon_action_retry.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0fa0243f3c07c919254e6e121e0ed0943c1aa779
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_retry.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M20.6114971,16.0821413 C20.6114971,16.106323 20.6090789,16.1232499 20.6042426,16.1329226 C20.2947172,17.42906 19.6466582,18.4797378 18.6600462,19.2849873 C17.6734341,20.0902369 16.5175677,20.4928556 15.1924122,20.4928556 C14.4863075,20.4928556 13.8031856,20.3598584 13.1430261,20.0938601 C12.4828665,19.8278617 11.8940517,19.4482152 11.376564,18.9549092 L10.4407381,19.8907351 C10.3488478,19.9826254 10.2400319,20.0285699 10.1142872,20.0285699 C9.98854256,20.0285699 9.87972669,19.9826254 9.78783635,19.8907351 C9.69594601,19.7988447 9.65000153,19.6900289 9.65000153,19.5642842 L9.65000153,16.3142842 C9.65000153,16.1885395 9.69594601,16.0797236 9.78783635,15.9878333 C9.87972669,15.895943 9.98854256,15.8499985 10.1142872,15.8499985 L13.3642872,15.8499985 C13.4900319,15.8499985 13.5988478,15.895943 13.6907381,15.9878333 C13.7826285,16.0797236 13.828573,16.1885395 13.828573,16.3142842 C13.828573,16.4400289 13.7826285,16.5488447 13.6907381,16.6407351 L12.6968765,17.6345967 C13.0402562,17.9537947 13.4295752,18.200444 13.8648453,18.374552 C14.3001153,18.5486601 14.7523057,18.6357128 15.2214301,18.6357128 C15.8694988,18.6357128 16.4740315,18.4785343 17.0350462,18.1641726 C17.5960609,17.8498109 18.0458332,17.4169655 18.3843765,16.8656235 C18.4375762,16.7834058 18.5657371,16.5004845 18.7688631,16.0168512 C18.8075538,15.9056155 18.8800977,15.8499985 18.9864971,15.8499985 L20.3793542,15.8499985 C20.4422265,15.8499985 20.4966345,15.8729707 20.5425797,15.9189159 C20.5885248,15.9648611 20.6114971,16.019269 20.6114971,16.0821413 Z M20.7928587,10.2785699 L20.7928587,13.5285699 C20.7928587,13.6543146 20.7469142,13.7631305 20.6550238,13.8550208 C20.5631335,13.9469111 20.4543176,13.9928556 20.328573,13.9928556 L17.078573,13.9928556 C16.9528283,13.9928556 16.8440124,13.9469111 16.7521221,13.8550208 C16.6602317,13.7631305 16.6142872,13.6543146 16.6142872,13.5285699 C16.6142872,13.4028252 16.6602317,13.2940094 16.7521221,13.202119 L17.7532381,12.2010029 C17.0374607,11.5384252 16.1935332,11.2071413 15.2214301,11.2071413 C14.5733614,11.2071413 13.9688287,11.3643198 13.407814,11.6786815 C12.8467993,11.9930432 12.397027,12.4258886 12.0584837,12.9772306 C12.005284,13.0594483 11.8771231,13.3423696 11.6739971,13.8260029 C11.6353064,13.9372386 11.5627625,13.9928556 11.4563631,13.9928556 L10.0127247,13.9928556 C9.9498524,13.9928556 9.89544446,13.9698834 9.84949929,13.9239382 C9.80355412,13.877993 9.78058188,13.8235851 9.78058188,13.7607128 L9.78058188,13.7099315 C10.0949436,12.4137941 10.7478388,11.3631163 11.7392872,10.5578668 C12.7307356,9.75261722 13.8914383,9.34999847 15.2214301,9.34999847 C15.9275348,9.34999847 16.6142839,9.48420472 17.281698,9.75262124 C17.949112,10.0210378 18.541554,10.3994752 19.0590417,10.8879449 L20.0021221,9.95211901 C20.0940124,9.86022867 20.2028283,9.81428419 20.328573,9.81428419 C20.4543176,9.81428419 20.5631335,9.86022867 20.6550238,9.95211901 C20.7469142,10.0440094 20.7928587,10.1528252 20.7928587,10.2785699 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_stop.svg b/app/views/shared/icons/_icon_action_stop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1c8e2fe4156d91e131b49fd19e9fe33ceb7e2c6d
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_stop.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M20.1357204,10.2985704 L20.1357204,19.7271418 C20.1357204,19.8432138 20.0933101,19.9436592 20.0084882,20.0284811 C19.9236664,20.1133029 19.823221,20.1557132 19.707149,20.1557132 L10.2785775,20.1557132 C10.1625055,20.1557132 10.0620601,20.1133029 9.97723825,20.0284811 C9.89241639,19.9436592 9.8500061,19.8432138 9.8500061,19.7271418 L9.8500061,10.2985704 C9.8500061,10.1824984 9.89241639,10.0820529 9.97723825,9.99723107 C10.0620601,9.91240922 10.1625055,9.86999893 10.2785775,9.86999893 L19.707149,9.86999893 C19.823221,9.86999893 19.9236664,9.91240922 20.0084882,9.99723107 C20.0933101,10.0820529 20.1357204,10.1824984 20.1357204,10.2985704 Z"></path></svg>
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index c0e8a49831667247ff6f726486aa0ead4e96cc46..cb92b2e97a73845428628a9a7c357707e786c8d8 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -43,6 +43,8 @@
 
 = render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
 
+= render 'shared/issuable/form/merge_params', issuable: issuable
+
 - if @merge_request_for_resolving_discussions
   .form-group
     .col-sm-10.col-sm-offset-2
@@ -68,7 +70,7 @@
   - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
     .inline.prepend-left-10
       Please review the
-      %strong #{link_to 'contribution guidelines', guide_url}
+      %strong= link_to('contribution guidelines', guide_url)
       for this project.
 
   - if issuable.new_record?
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 8d7b1d616f476b21f13cf2a807ea783943dd4d2b..55360dadbc46014131d4830b8095ecc0eb56cc9f 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -11,13 +11,13 @@
             class: "check_all_issues left"
       .issues-other-filters.filtered-search-container
         .filtered-search-input-container
-          %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id }
+          %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]) }
           = icon('filter')
           %button.clear-search.hidden{ type: 'button' }
             = icon('times')
           #js-dropdown-hint.dropdown-menu.hint-dropdown
             %ul{ 'data-dropdown' => true }
-              %li.filter-dropdown-item{ 'data-value' => '' }
+              %li.filter-dropdown-item{ 'data-action' => 'submit' }
                 %button.btn.btn-link
                   = icon('search')
                   %span
@@ -121,7 +121,13 @@
   new MilestoneSelect();
   new IssueStatusSelect();
   new SubscriptionSelect();
-  $('form.filter-form').on('submit', function (event) {
-    event.preventDefault();
-    Turbolinks.visit(this.action + '&' + $(this).serialize());
+
+  $(document).off('page:restore').on('page:restore', function (event) {
+    if (gl.FilteredSearchManager) {
+      new gl.FilteredSearchManager();
+    }
+    Issuable.init();
+    new gl.IssuableBulkActions({
+      prefixId: 'issue_',
+    });
   });
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ec9bcaf63ddf992e4e2f639c8c8e63aa98bdb567..10fa790187420ed1c5ec81a2cdc0b68fbba76493 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -130,7 +130,7 @@
           .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
             - if selected_labels.any?
               - selected_labels.each do |label|
-                = link_to_label(label, type: issuable.to_ability_name)
+                = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
             - else
               %span.no-value None
           .selectbox.hide-collapsed
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index b757893ea049a4821faa8c2c9b947c135802c794..2793e7bcff430052a244b52a66813f0f9d51df50 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -19,12 +19,3 @@
     - 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/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..03309722326a8fed78599be1b94f9e97d9a372ac
--- /dev/null
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -0,0 +1,16 @@
+- issuable = local_assigns.fetch(:issuable)
+
+- return unless issuable.is_a?(MergeRequest)
+- return if issuable.closed_without_fork?
+
+-# This check is duplicated below, to avoid conflicts with EE.
+- return unless issuable.can_remove_source_branch?(current_user)
+
+.form-group
+  .col-sm-10.col-sm-offset-2
+    - if issuable.can_remove_source_branch?(current_user)
+      .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/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index b5c0a7fd6d4e4a32e390b56ed4042a89821febbf..a736bfd91e2876d44a88b55b363ea5dbba5d2da8 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -18,7 +18,7 @@
                 %p
                   Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out
                   = succeed "." do
-                    %a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails
+                    %a{ href: help_page_path('workflow/notifications'), target: "_blank" } notification emails
               .col-lg-8
                 - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
                   - field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 0c7880320208f9bda6bf6f5a6344e6632314480e..2d22782eb36ae6fbf7ed9b0a1ecdb8aa9de10381 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -11,7 +11,7 @@
       .col-sm-10
         = f.text_field :title, class: 'form-control', required: true, autofocus: true
 
-    = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
+    = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
 
     .file-editor
       .form-group
@@ -34,4 +34,3 @@
         = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
       - else
         = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
-
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index 5074afb63a1245bdd4a18fc48a80cf0542f09a3b..8bbaf431536dc19b9de7a56b9c47226dcc1a6a1f 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -5,5 +5,5 @@
 - scopes.each do |scope|
   %fieldset
     = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
-    = label_tag "#{prefix}_scopes_#{scope}", scope
-    %span= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
+    = label_tag ("#{prefix}_scopes_#{scope}"), scope
+    %span= t(scope, scope: [:doorkeeper, :scopes])
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 13586a5a12aadcb70abdabb701355e40adeaad2e..c212d1c86bfa94566734a609fd816368215ec05e 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -66,9 +66,9 @@
             = f.check_box :build_events, class: 'pull-left'
             .prepend-left-20
               = f.label :build_events, class: 'list-label' do
-                %strong Build events
+                %strong Jobs events
               %p.light
-                This URL will be triggered when the build status changes
+                This URL will be triggered when the job status changes
           %li
             = f.check_box :pipeline_events, class: 'pull-left'
             .prepend-left-20
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 95fc71981044a0253152391a8cd231ce61d32891..9a9a3ff9220070ea7308ae5d9b725671f8454e29 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
   - if current_user
     = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
       New snippet
+  - if @snippet.submittable_as_spam? && current_user.admin?
+    = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
 - if current_user
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -26,3 +28,6 @@
           %li
             = link_to edit_snippet_path(@snippet) do
               Edit
+        - if @snippet.submittable_as_spam? && current_user.admin?
+          %li
+            = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 82f44a9a5c3ba062497557c8bd75ee0f67b2d391..915bf98eb3eae637fe7333de6f4bba6f8a4a39e3 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -2,4 +2,4 @@
 %h3.page-title
   Edit Snippet
 %hr
-= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
+= render 'shared/snippets/form', url: snippet_path(@snippet)
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 79e2392490d5396cb581863b74da99e972e1c87c..ca8afb4bb6a21a6a822a89ae3774079679ac6c59 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -2,4 +2,4 @@
 %h3.page-title
   New Snippet
 %hr
-= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index f51599212db817e1d146b4b0045f22cf3b7f6312..b09782749f52d167de58354ef37e40659a96bd59 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -1,6 +1,6 @@
 %h4.prepend-top-20
   Contributions for
-  %strong #{@calendar_date.to_s(:short)}
+  %strong= @calendar_date.to_s(:short)
 
 - if @events.any?
   %ul.bordered-list
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index fb25eed4f37e612ef7ed60e5da7de901f08dd2fa..c3d33d49c1ede04ae233542fbb0b8a0ef3abea6e 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -110,16 +110,16 @@
         = spinner
 
       #groups.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
 
       #contributed.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
 
       #projects.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
 
       #snippets.tab-pane
-        - # This tab is always loaded via AJAX
+        -# This tab is always loaded via AJAX
 
     .loading-status
       = spinner
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 2badd0680fbaae34867aeee28149e81ab8d49deb..6abbb5a525041b51e501c9b24cc65fca165e4780 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -2,6 +2,13 @@ class AuthorizedProjectsWorker
   include Sidekiq::Worker
   include DedicatedSidekiqQueue
 
+  # Schedules multiple jobs and waits for them to be completed.
+  def self.bulk_perform_and_wait(args_list)
+    job_ids = bulk_perform_async(args_list)
+
+    Gitlab::JobWaiter.new(job_ids).wait
+  end
+
   def self.bulk_perform_async(args_list)
     Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
   end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index b9cd49985dcd650f98b273b351fd3c88b69e9dfe..f5ccc84c160c8585158fbce902c279ed2c1472c9 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -33,13 +33,15 @@ class EmailsOnPushWorker
     reverse_compare = false
 
     if action == :push
-      compare = CompareService.new.execute(project, after_sha, project, before_sha)
+      compare = CompareService.new(project, after_sha)
+        .execute(project, before_sha)
       diff_refs = compare.diff_refs
 
       return false if compare.same
 
       if compare.commits.empty?
-        compare = CompareService.new.execute(project, before_sha, project, after_sha)
+        compare = CompareService.new(project, before_sha)
+          .execute(project, after_sha)
         diff_refs = compare.diff_refs
 
         reverse_compare = true
diff --git a/changelogs/unreleased/17662-rename-builds.yml b/changelogs/unreleased/17662-rename-builds.yml
new file mode 100644
index 0000000000000000000000000000000000000000..12f2998d1c8584abdb35ce7623cbf79f5af5e0d5
--- /dev/null
+++ b/changelogs/unreleased/17662-rename-builds.yml
@@ -0,0 +1,4 @@
+---
+title: Rename Builds to Pipelines, CI/CD Pipelines, or Jobs everywhere
+merge_request: 8787
+author:
diff --git a/changelogs/unreleased/18786-go-to-a-project-order.yml b/changelogs/unreleased/18786-go-to-a-project-order.yml
deleted file mode 100644
index 1b9e246d1a77ff3d35c4de392673638d73a44647..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/18786-go-to-a-project-order.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Go to a project order
-merge_request: 7737 
-author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/19086-double-newline.yml b/changelogs/unreleased/19086-double-newline.yml
deleted file mode 100644
index dd9b58920fb23f2d058ada50575834e1c7e468ff..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/19086-double-newline.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix double spaced CI log
-merge_request: 8349
-author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/19164-mobile-settings.yml b/changelogs/unreleased/19164-mobile-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c26a20f87e24c9a922b2b557920e6d169f72c9ff
--- /dev/null
+++ b/changelogs/unreleased/19164-mobile-settings.yml
@@ -0,0 +1,4 @@
+---
+title: 19164 Add settings dropdown to mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/19966-api-call-to-move-project-to-different-group-fails-when-using-group-and-project-names-instead-of-id.yml b/changelogs/unreleased/19966-api-call-to-move-project-to-different-group-fails-when-using-group-and-project-names-instead-of-id.yml
deleted file mode 100644
index 61cf047026b993d61a023101a8b5d6097a58f0a6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/19966-api-call-to-move-project-to-different-group-fails-when-using-group-and-project-names-instead-of-id.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow group and project paths when transferring projects via the API
-merge_request:
-author:
diff --git a/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml b/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml
deleted file mode 100644
index 5570ede4a9a00b0e9a1254fd65a79ce6926c3be4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent empty pagination when list is not empty
-merge_request: 8172
-author: 
diff --git a/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..965d0648adf49f0ae73af1d39c1b9965e04dd108
--- /dev/null
+++ b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
@@ -0,0 +1,4 @@
+---
+title: Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb
+merge_request:
+author:
diff --git a/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eda872049fdb1bae9d453d4a61aa868d6c14e0a3
--- /dev/null
+++ b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
@@ -0,0 +1,4 @@
+---
+title: Added labels empty state
+merge_request: 7443
+author: 
diff --git a/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml b/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml
deleted file mode 100644
index 574c322803c5f4a52f598268d332f9b1b81f1e29..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve visibility of "Resolve conflicts" and "Merge locally" actions
-merge_request: 8229
-author:
diff --git a/changelogs/unreleased/21698-redis-runner-last-build.yml b/changelogs/unreleased/21698-redis-runner-last-build.yml
deleted file mode 100644
index 2aba3c353a3b039c21b1502c73b674da81b12a58..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/21698-redis-runner-last-build.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Reduce DB-load for build-queues by storing last_update in Redis
-merge_request: 8084
-author:
diff --git a/changelogs/unreleased/22111-remove-lock-icon-on-protected-tag.yml b/changelogs/unreleased/22111-remove-lock-icon-on-protected-tag.yml
deleted file mode 100644
index e4f7c1b77621cb62adb1702cd9b6ec932cac67b1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/22111-remove-lock-icon-on-protected-tag.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove Lock Icon on Protected Tag
-merge_request: 8513
-author: Sergey Nikitin
diff --git a/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email b/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email
new file mode 100644
index 0000000000000000000000000000000000000000..f4011b756a521e4bb2c9d49c622faf75b140b7e0
--- /dev/null
+++ b/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email
@@ -0,0 +1,4 @@
+---
+title: Handle unsubscribe from email notifications via replying to reply+%{key}+unsubscribe@ address
+merge_request: 6597
+author:
diff --git a/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml b/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2c6883bcf7b5d7c7859b8f30f18cccfe6e0d89f8
--- /dev/null
+++ b/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml
@@ -0,0 +1,4 @@
+---
+title: Allow creating protected branches when user can merge to such branch
+merge_request: 8458
+author:
diff --git a/changelogs/unreleased/22974-trigger-service-events-through-api.yml b/changelogs/unreleased/22974-trigger-service-events-through-api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..57106e8c676b320d43179f1547ee3150c7dbfcb6
--- /dev/null
+++ b/changelogs/unreleased/22974-trigger-service-events-through-api.yml
@@ -0,0 +1,4 @@
+---
+title: Adds service trigger events to api
+merge_request: 8324
+author:
diff --git a/changelogs/unreleased/23634-remove-project-grouping.yml b/changelogs/unreleased/23634-remove-project-grouping.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dde8b2d18151db995a1b72a84b54ad029c2a95f3
--- /dev/null
+++ b/changelogs/unreleased/23634-remove-project-grouping.yml
@@ -0,0 +1,4 @@
+---
+title: Don't group issues by project on group-level and dashboard issue indexes.
+merge_request: 8111
+author: Bernardo Castro
diff --git a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
new file mode 100644
index 0000000000000000000000000000000000000000..587ef4f9a73424fdcda333a1205a24f94a56d7bc
--- /dev/null
+++ b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
@@ -0,0 +1,4 @@
+---
+title: Fix disable storing of sensitive information when importing a new repo
+merge_request: 8885
+author: Bernard Pietraga
diff --git a/changelogs/unreleased/24032-changed-visibility-level-to-public-but-project-is-not-public.yml b/changelogs/unreleased/24032-changed-visibility-level-to-public-but-project-is-not-public.yml
deleted file mode 100644
index 875106d7dd5ccc8c1f692b3cde88ad3a833c5bff..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24032-changed-visibility-level-to-public-but-project-is-not-public.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Updated project visibility settings UX
-merge_request: 7645
-author: 
diff --git a/changelogs/unreleased/24139-production-wildcard-for-cycle-analytics.yml b/changelogs/unreleased/24139-production-wildcard-for-cycle-analytics.yml
deleted file mode 100644
index 83cf3670ec046f3081b7a9673735b3999f0ed93a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24139-production-wildcard-for-cycle-analytics.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Treat environments matching `production/*` as Production
-merge_request: 8500
-author:
diff --git a/changelogs/unreleased/24185-legacy-ci-status-reactive-cache.yml b/changelogs/unreleased/24185-legacy-ci-status-reactive-cache.yml
deleted file mode 100644
index 09ff63a44fb34e4e99dfaf95d74a82f1c843c5de..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24185-legacy-ci-status-reactive-cache.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Query external CI statuses in the background
-merge_request:
-author:
diff --git a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..05fbd8f0bf2b2e14aaa95eda0749e14cc2dfe3f8
--- /dev/null
+++ b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
@@ -0,0 +1,4 @@
+---
+title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms
+merge_request: 8752
+author:
diff --git a/changelogs/unreleased/24606-force-password-reset-on-next-login.yml b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fd671d04a9f50b5cc5b4aef20ca0be0972ee68e5
--- /dev/null
+++ b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
@@ -0,0 +1,4 @@
+---
+title: Force new password after password reset via API
+merge_request:
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/24795_refactor_merge_request_build_service.yml b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b735fb576498787cbb5ad318128212dc7af30cb1
--- /dev/null
+++ b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor MergeRequests::BuildService
+merge_request: 8462
+author: Rydkin Maxim
diff --git a/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml b/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml
deleted file mode 100644
index fac7697ede1728e0373d1288e1f1349219f2affe..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: fix button layout issue on branches page
-merge_request: 8074
-author:
diff --git a/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml b/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml
new file mode 100644
index 0000000000000000000000000000000000000000..be66c370f36986a9a18b66494d484573d3170157
--- /dev/null
+++ b/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml
@@ -0,0 +1,4 @@
+---
+title: 'Allows to search within project by commit hash'
+merge_request: 
+author: YarNayar
diff --git a/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml b/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml
deleted file mode 100644
index c31c89dc4bc8f5f9d44b3c154edd387b65bc285f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: ensure permalinks scroll to correct position on multiple clicks
-merge_request: 8046
-author:
diff --git a/changelogs/unreleased/24915_merge_slash_command.yml b/changelogs/unreleased/24915_merge_slash_command.yml
deleted file mode 100644
index eb8ced8ab0149b17c0b47ced2d36c1ed57d4f80d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/24915_merge_slash_command.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Support slash comand `/merge` for merging merge requests.
-merge_request: 7746
-author: Jarka Kadlecova
diff --git a/changelogs/unreleased/24923_nested_tasks.yml b/changelogs/unreleased/24923_nested_tasks.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de35cad3dd69a8ba26a17667bd569f35b9ffe961
--- /dev/null
+++ b/changelogs/unreleased/24923_nested_tasks.yml
@@ -0,0 +1,4 @@
+---
+title: Fix nested tasks in ordered list
+merge_request: 8626
+author:
diff --git a/changelogs/unreleased/25277-milestone-counter-number-with-delimiter.yml b/changelogs/unreleased/25277-milestone-counter-number-with-delimiter.yml
deleted file mode 100644
index 0c9853de3b6ee09ec061e126007bdb91c7f04d77..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25277-milestone-counter-number-with-delimiter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added number_with_delimiter to counter on milestone panels
-merge_request: 
-author: Ryan Harris
diff --git a/changelogs/unreleased/25312-search-input-cmd-click-issue.yml b/changelogs/unreleased/25312-search-input-cmd-click-issue.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56e03a4869270cf1eb52676cf292567db7b65063
--- /dev/null
+++ b/changelogs/unreleased/25312-search-input-cmd-click-issue.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent removal of input fields if it is the parent dropdown element
+merge_request: 8397
+author:
diff --git a/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50a5c879446aac2a315c1a72b994f0c26234f4f0
--- /dev/null
+++ b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
@@ -0,0 +1,4 @@
+---
+title: Remove flash warning from login page
+merge_request: 8864
+author: Gerald J. Padilla
diff --git a/changelogs/unreleased/25371-environments-date-created-column-is-not-labeled.yml b/changelogs/unreleased/25371-environments-date-created-column-is-not-labeled.yml
deleted file mode 100644
index 13d3476fe39af8f4f5e972f2760f4a96ff4efa46..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25371-environments-date-created-column-is-not-labeled.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds label to Environments "Date Created"
-merge_request: 8376
-author: Saad Shahd
diff --git a/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml b/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml
deleted file mode 100644
index e60c42cdfba11dfc160c2087934bda6bd8cbbe37..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change status colors of runners to better defaults
-merge_request:
-author:
diff --git a/changelogs/unreleased/25507-handle-errors-environment-list.yml b/changelogs/unreleased/25507-handle-errors-environment-list.yml
deleted file mode 100644
index 4e9794f7917da363274812f73473879e1a8401b7..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25507-handle-errors-environment-list.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Handle HTTP errors in environment list
-merge_request:
-author:
diff --git a/changelogs/unreleased/25580-trucate-dropdown-for-long-branch.yml b/changelogs/unreleased/25580-trucate-dropdown-for-long-branch.yml
deleted file mode 100644
index 18d3ac050ae7e88752450df8c500226de936cbc4..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25580-trucate-dropdown-for-long-branch.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Resolves overflow in compare branch and tags dropdown
-merge_request: 8118
-author:
diff --git a/changelogs/unreleased/25678-remove-user-build.yml b/changelogs/unreleased/25678-remove-user-build.yml
deleted file mode 100644
index 873e637d6708cb3d7888ede1e38a13c11fc6c4bc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25678-remove-user-build.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: remove build_user
-merge_request: 8162 
-author: Arsenev Vladislav
diff --git a/changelogs/unreleased/25701-standardize-text-colors.yml b/changelogs/unreleased/25701-standardize-text-colors.yml
deleted file mode 100644
index a48ca6c187dec557714348c751b2eb21f393d739..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25701-standardize-text-colors.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 25701 standardize text colors
-merge_request:
-author:
diff --git a/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml b/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml
deleted file mode 100644
index 850e98518a6e8f25ecb17c80c091cb33a034e748..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: Replace wording for slash command confirmation message
-merge_request: 8123
diff --git a/changelogs/unreleased/25725-remove-window-object.yml b/changelogs/unreleased/25725-remove-window-object.yml
deleted file mode 100644
index c64b71ddd33e476c678b655e424d7e510a8617c7..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25725-remove-window-object.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Removes unneeded `window` declaration in environments related code
-merge_request: 8456
-author:
diff --git a/changelogs/unreleased/25776-alerts-should-be-responsive.yml b/changelogs/unreleased/25776-alerts-should-be-responsive.yml
deleted file mode 100644
index 15006523d3eb87c67b568908375f8dfa0480b926..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25776-alerts-should-be-responsive.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed alerts to be responsive, centered text on smaller viewports
-merge_request: 8424
-author: Connor Smallman
diff --git a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f74e9fa8b6dbf52b72f5d480e81683378bd162b0
--- /dev/null
+++ b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
@@ -0,0 +1,4 @@
+---
+title: Update pipeline and commit links when CI status is updated
+merge_request: 8351
+author:
diff --git a/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml b/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml
deleted file mode 100644
index c82bacd8bcdfcfa64596b237543481977c489d2a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: re-enable change username button after failure
-merge_request: 8332
-author:
diff --git a/changelogs/unreleased/25898-ci-icon-color-mr.yml b/changelogs/unreleased/25898-ci-icon-color-mr.yml
deleted file mode 100644
index dd0f93e176f84b2942997f437e9aff45fe4b804f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25898-ci-icon-color-mr.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds CSS class to status icon on MR widget to prevent non-colored icon
-merge_request: 8219
-author:
diff --git a/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9506692dd4063e3665632a14b2b57c90318ad609
--- /dev/null
+++ b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
@@ -0,0 +1,4 @@
+---
+title: Convert pipeline action icons to svg to have them propperly positioned
+merge_request:
+author:
diff --git a/changelogs/unreleased/25941-odd-overflow-behavior-for-long-issue-headers.yml b/changelogs/unreleased/25941-odd-overflow-behavior-for-long-issue-headers.yml
deleted file mode 100644
index c28cf7a0f8671c9388ecb74afcbcc2d2536c7119..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25941-odd-overflow-behavior-for-long-issue-headers.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change earlier to task_status_short to avoid titlebar line wraps
-merge_request:
-author:
diff --git a/changelogs/unreleased/25946-manual-pipeline-dropdown-casing.yml b/changelogs/unreleased/25946-manual-pipeline-dropdown-casing.yml
deleted file mode 100644
index b753c8233489377f1bad977c5c342383907c6141..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25946-manual-pipeline-dropdown-casing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use original casing for build action text
-merge_request: 8387
-author:
diff --git a/changelogs/unreleased/25985-combine-members-and-groups-settings-pages.yml b/changelogs/unreleased/25985-combine-members-and-groups-settings-pages.yml
deleted file mode 100644
index 206be8fe3cb769e5925e195886c099c2b0debfdb..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25985-combine-members-and-groups-settings-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Combined the settings options project members and groups into a single one
-  called members
-merge_request:
-author:
diff --git a/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml b/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e67a9c0da152b1f89852d61e59947ac9ca5e26d4
--- /dev/null
+++ b/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml
@@ -0,0 +1,4 @@
+---
+title: Remove rogue scrollbars for issue comments with inline elements
+merge_request:
+author:
diff --git a/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml b/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml
deleted file mode 100644
index e05e2dd6fede7061c7045b586689342f4d32f1c8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Move award emoji's out of the discussion tab for merge requests
-merge_request:
-author:
diff --git a/changelogs/unreleased/26014-fix-update-doc.yml b/changelogs/unreleased/26014-fix-update-doc.yml
deleted file mode 100644
index 419c032cb0f966683e4b6f1f9be7c8fec731e187..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26014-fix-update-doc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Re-order update steps in the 8.14 -> 8.15 upgrade guide
-merge_request:
-author:
diff --git a/changelogs/unreleased/26051-fix-missing-endpoint-route-method.yml b/changelogs/unreleased/26051-fix-missing-endpoint-route-method.yml
deleted file mode 100644
index 85440eb86f9f2dae009c36c101b1acdc11e25f18..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26051-fix-missing-endpoint-route-method.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't instrument 405 Grape calls
-merge_request: 8445
-author:
diff --git a/changelogs/unreleased/26068_tasklist_issue.yml b/changelogs/unreleased/26068_tasklist_issue.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c938351b8a739dca2fb4a3293788f32e249d7f87
--- /dev/null
+++ b/changelogs/unreleased/26068_tasklist_issue.yml
@@ -0,0 +1,4 @@
+---
+title: Don’t count tasks that are not defined as list items correctly
+merge_request: 8526
+author:
diff --git a/changelogs/unreleased/26109-preserve-scroll-position-on-autoreload.yml b/changelogs/unreleased/26109-preserve-scroll-position-on-autoreload.yml
deleted file mode 100644
index cde0d114d7cdd4117d8ffcae50a5012cb6287c04..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26109-preserve-scroll-position-on-autoreload.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Scroll to bottom on build completion if autoscroll was active
-merge_request: 8391
-author:
diff --git a/changelogs/unreleased/26129-add-link-to-branches-page.yml b/changelogs/unreleased/26129-add-link-to-branches-page.yml
deleted file mode 100644
index aceb92dbb9c0535a2fbb7d1899e554fb20fa3483..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26129-add-link-to-branches-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Convert project setting text into protected branch path link
-merge_request: 8377
-author: Ken Ding
diff --git a/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml b/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml
deleted file mode 100644
index 40183f8d3fa5dc2be2df8df0b0defcda0d3f16c3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make CTRL+Enter submits a new merge request
-merge_request: 8360
-author: Saad Shahd
diff --git a/changelogs/unreleased/26155-merge-request-tabs-don-t-render-when-no-commits-available.yml b/changelogs/unreleased/26155-merge-request-tabs-don-t-render-when-no-commits-available.yml
deleted file mode 100644
index 242a77b5d48afc0961ac7244641443a13b03c93b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26155-merge-request-tabs-don-t-render-when-no-commits-available.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: display merge request discussion tab for empty branches
-merge_request: 8347
-author:
diff --git a/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml b/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml
new file mode 100644
index 0000000000000000000000000000000000000000..565672917b262699012b245e88bc378261939e2c
--- /dev/null
+++ b/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml
@@ -0,0 +1,4 @@
+---
+title: Color + and - signs in diffs to increase code legibility
+merge_request:
+author:
diff --git a/changelogs/unreleased/26192-fixes-too-short-input.yml b/changelogs/unreleased/26192-fixes-too-short-input.yml
deleted file mode 100644
index ff707f4694d3d694e04d5075ba3eb4f1eeedd920..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26192-fixes-too-short-input.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes too short input for placeholder message in commit listing page
-merge_request: 8367
-author:
diff --git a/changelogs/unreleased/26207-add-hover-animations.yml b/changelogs/unreleased/26207-add-hover-animations.yml
deleted file mode 100644
index 12a69d04717440b091ed22d6c516ef561fdcb8f2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26207-add-hover-animations.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add various hover animations throughout the application
-merge_request:
-author:
diff --git a/changelogs/unreleased/26226-generate-all-haml-fixtures-within-teaspoon-fixtures-task.yml b/changelogs/unreleased/26226-generate-all-haml-fixtures-within-teaspoon-fixtures-task.yml
deleted file mode 100644
index 28981291132a4a6541bec4072530b297c587d4cd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26226-generate-all-haml-fixtures-within-teaspoon-fixtures-task.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Precompile all JavaScript fixtures
-merge_request: 8384
-author:
diff --git a/changelogs/unreleased/26238-buttons-not-accessible.yml b/changelogs/unreleased/26238-buttons-not-accessible.yml
deleted file mode 100644
index 34d38d45709b6c94da1f0e46778c73e5e895ad2b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26238-buttons-not-accessible.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes buttons not being accessible via the keyboard when creating new group
-merge_request: 8469
-author:
diff --git a/changelogs/unreleased/26261-post-api-v3-projects-idorproject-commits-commits-does-not-work-with-project-path.yml b/changelogs/unreleased/26261-post-api-v3-projects-idorproject-commits-commits-does-not-work-with-project-path.yml
deleted file mode 100644
index 37bd7e46b4950e589831375b859186494abf2b2b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26261-post-api-v3-projects-idorproject-commits-commits-does-not-work-with-project-path.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Commits API to accept a Project path upon POST
-merge_request:
-author:
diff --git a/changelogs/unreleased/26352-user-dropdown-settings.yml b/changelogs/unreleased/26352-user-dropdown-settings.yml
deleted file mode 100644
index 19bd47b8673d332dab22d480e3bc7e4a00f069ec..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26352-user-dropdown-settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 26352 Change Profile settings to User / Settings
-merge_request:
-author:
diff --git a/changelogs/unreleased/26435-show-project-avatars-on-mobile.yml b/changelogs/unreleased/26435-show-project-avatars-on-mobile.yml
deleted file mode 100644
index 43afdf450131ae7f07cbde39d161f097be3a0257..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26435-show-project-avatars-on-mobile.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display project avatars on Admin Area and Projects pages for mobile views
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/26445-accessible-piplelines-buttons.yml b/changelogs/unreleased/26445-accessible-piplelines-buttons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fb5274e5253375346716ad37ad26a4b3784f835f
--- /dev/null
+++ b/changelogs/unreleased/26445-accessible-piplelines-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Improve button accessibility on pipelines page
+merge_request: 8561
+author:
diff --git a/changelogs/unreleased/26445-make-icon-buttons-accessible-via-keyboard.yml b/changelogs/unreleased/26445-make-icon-buttons-accessible-via-keyboard.yml
deleted file mode 100644
index b4aef8fe3da28e50671f1c43e07ed91deb6ab54d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26445-make-icon-buttons-accessible-via-keyboard.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make play button on Pipelines page accessible via keyboard
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/26446-access-download-artifacts-via-keyboard.yml b/changelogs/unreleased/26446-access-download-artifacts-via-keyboard.yml
deleted file mode 100644
index 83f6233dd887a1166abaccc236137602a790a247..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26446-access-download-artifacts-via-keyboard.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Made download artifacts button accessible via keyboard by changing it from
-  an anchor tag to an actual button
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/26447-fix-tab-list-order.yml b/changelogs/unreleased/26447-fix-tab-list-order.yml
new file mode 100644
index 0000000000000000000000000000000000000000..351c53bd07679e5fb3e0f2f3374297c23dd6b81b
--- /dev/null
+++ b/changelogs/unreleased/26447-fix-tab-list-order.yml
@@ -0,0 +1,4 @@
+---
+title: Fix tab index order on branch commits list page
+merge_request:
+author: Ryan Harris
diff --git a/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml b/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml
new file mode 100644
index 0000000000000000000000000000000000000000..87ae8233c4a92a3b87caed69b6ed677ec36b5034
--- /dev/null
+++ b/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Sort by Recent Sign-in in Admin Area
+merge_request: 8637
+author: Poornima M
diff --git a/changelogs/unreleased/26472-math-margin.yml b/changelogs/unreleased/26472-math-margin.yml
deleted file mode 100644
index 3999f52155818a83fc867408fc0311da1bc7f728..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26472-math-margin.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add margin to markdown math blocks
-merge_request:
-author:
diff --git a/changelogs/unreleased/26504-mr-discussion-btn.yml b/changelogs/unreleased/26504-mr-discussion-btn.yml
deleted file mode 100644
index dec74ec61b15948e9b549f78704e51bbbe44411c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26504-mr-discussion-btn.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 26504 Fix styling of MR jump to discussion button
-merge_request:
-author:
diff --git a/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml b/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml
deleted file mode 100644
index 5891a5ef6e889c814cd7f952cff30dfe91f6549a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route
-merge_request: 8544
-author:
diff --git a/changelogs/unreleased/26601-dropdown-makes-request-close.yml b/changelogs/unreleased/26601-dropdown-makes-request-close.yml
deleted file mode 100644
index a810e04376d91486f930b9c453eec48a381eba2c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26601-dropdown-makes-request-close.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes builds dropdown making request when clicked to be closed
-merge_request: 8545
-author:
diff --git a/changelogs/unreleased/26615-pipeline-status-cell.yml b/changelogs/unreleased/26615-pipeline-status-cell.yml
deleted file mode 100644
index 9a19b041e6383c5fc07da31d2b82a1c9e181b524..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26615-pipeline-status-cell.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes pipeline status cell is too wide by adding missing classes in table head cells
-merge_request: 8549
-author:
diff --git a/changelogs/unreleased/26616-fix-search-group-project-filters.yml b/changelogs/unreleased/26616-fix-search-group-project-filters.yml
deleted file mode 100644
index 0fd0dbbfc242f3f881d9d7334e6a3a482abf2b06..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26616-fix-search-group-project-filters.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix search group/project filtering to show results
-merge_request:
-author:
diff --git a/changelogs/unreleased/26667-pipeline-width-for-huge-pipeline.yml b/changelogs/unreleased/26667-pipeline-width-for-huge-pipeline.yml
deleted file mode 100644
index 08dcc5c3e8c4d551b84ff9b95ba7e814f0da447f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26667-pipeline-width-for-huge-pipeline.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes big pipeline and small pipeline width problems and tooltips text being outside the tooltip
-merge_request: 8593
-author:
diff --git a/changelogs/unreleased/26773-fix-project-statistics-repository-size.yml b/changelogs/unreleased/26773-fix-project-statistics-repository-size.yml
deleted file mode 100644
index 8ce9bbcb3a995912ce4ac51043c6a157ca26d687..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/26773-fix-project-statistics-repository-size.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adjust ProjectStatistic#repository_size with values saved as MB
-merge_request: 8616
-author:
diff --git a/changelogs/unreleased/26787-add-copy-icon-hover-state.yml b/changelogs/unreleased/26787-add-copy-icon-hover-state.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31f1812c6f805d2337484b022e440c81a920ba12
--- /dev/null
+++ b/changelogs/unreleased/26787-add-copy-icon-hover-state.yml
@@ -0,0 +1,4 @@
+---
+title: Add hover style to copy icon on commit page header
+merge_request:
+author: Ryan Harris
diff --git a/changelogs/unreleased/26852-fix-slug-for-openshift.yml b/changelogs/unreleased/26852-fix-slug-for-openshift.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fb65b068b23ebc15f1792ffaa0a58b81e7fe1fbd
--- /dev/null
+++ b/changelogs/unreleased/26852-fix-slug-for-openshift.yml
@@ -0,0 +1,4 @@
+---
+title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG
+merge_request: 8638
+author:
diff --git a/changelogs/unreleased/26947-build-status-self-link.yml b/changelogs/unreleased/26947-build-status-self-link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..15c5821874eff063869fb062fc115754606cc05a
--- /dev/null
+++ b/changelogs/unreleased/26947-build-status-self-link.yml
@@ -0,0 +1,4 @@
+---
+title: Add link verification to badge partial in order to render a badge without a link
+merge_request: 8740
+author:
diff --git a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5c57af5aaf90f68290f2bc46ff5d0c8b1597383
--- /dev/null
+++ b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
@@ -0,0 +1,4 @@
+---
+title: Improve pipeline status icon linking in widgets
+merge_request:
+author:
diff --git a/changelogs/unreleased/27013-regression-in-commit-title-bar.yml b/changelogs/unreleased/27013-regression-in-commit-title-bar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7cb5e4b273d4a6490f9e6d6a64634db9c7ecbaa1
--- /dev/null
+++ b/changelogs/unreleased/27013-regression-in-commit-title-bar.yml
@@ -0,0 +1,4 @@
+---
+title: Fix commit title bar and repository view copy clipboard button order on last commit in repository view
+merge_request:
+author:
diff --git a/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml b/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f0301c849b69ec8feccf8446e28b12581217d281
--- /dev/null
+++ b/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml
@@ -0,0 +1,4 @@
+---
+title: Fix mini-pipeline stage tooltip text wrapping
+merge_request:
+author:
diff --git a/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml b/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b5584749098c5ee19d6d7187c44db58137e087ff
--- /dev/null
+++ b/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent copying of line numbers in parallel diff view
+merge_request: 8706
+author:
diff --git a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1758ed9e9eab72bc208705c67943956902f7128c
--- /dev/null
+++ b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
@@ -0,0 +1,4 @@
+---
+title: Support non-ASCII characters in GFM autocomplete
+merge_request: 8729
+author:
diff --git a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ddd454da3766ac79d6fadb68a26f21d61557ceec
--- /dev/null
+++ b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
@@ -0,0 +1,4 @@
+---
+title: Fix permalink discussion note being collapsed
+merge_request:
+author:
diff --git a/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml b/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..52406bba46456667998bb0591cad777b827e1493
--- /dev/null
+++ b/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml
@@ -0,0 +1,4 @@
+---
+title: Updated builds info link on the project settings page
+merge_request:
+author: Ryan Harris
diff --git a/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6e036923158383588c8f2bec094dc3677881e533
--- /dev/null
+++ b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml
@@ -0,0 +1,4 @@
+---
+title: Fix filtering with multiple words
+merge_request: 8830
+author:
diff --git a/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2591f161bc586d4e1da2753767f7fbd7fe9994ff
--- /dev/null
+++ b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml
@@ -0,0 +1,4 @@
+---
+title: Fix project name label's for reference in project settings
+merge_request: 8795
+author:
diff --git a/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9456251025b890e0ea3fce1b30c8ede2050c43db
--- /dev/null
+++ b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml
@@ -0,0 +1,4 @@
+---
+title: fixed small mini pipeline graph line glitch
+merge_request: 8804
+author:
diff --git a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..293aab67d396c284d3f2616e12c6acff2a48cff1
--- /dev/null
+++ b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Unify MR diff file button style
+merge_request: 8874
+author:
diff --git a/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..502927cd160d29563e8b85ae234b6686909b7fbc
--- /dev/null
+++ b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
@@ -0,0 +1,4 @@
+---
+title: Only render hr when user can't archive project.
+merge_request: !8917
+author:
diff --git a/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
new file mode 100644
index 0000000000000000000000000000000000000000..79316abbaf7e72f97d721204528b91d7415f2ea6
--- /dev/null
+++ b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline graph vertical spacing in Firefox and Safari
+merge_request: 8886
+author:
diff --git a/changelogs/unreleased/27484-environment-show-name.yml b/changelogs/unreleased/27484-environment-show-name.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dc400d65006354727409c15e6ed50b4de0b0b18b
--- /dev/null
+++ b/changelogs/unreleased/27484-environment-show-name.yml
@@ -0,0 +1,4 @@
+---
+title: Don't capitalize environment name in show page
+merge_request:
+author:
diff --git a/changelogs/unreleased/27488-fix-jwt-version.yml b/changelogs/unreleased/27488-fix-jwt-version.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5135ff0fd60a37259f3bf2864e5f036c43b10ee8
--- /dev/null
+++ b/changelogs/unreleased/27488-fix-jwt-version.yml
@@ -0,0 +1,4 @@
+---
+title: Update and pin the `jwt` gem to ~> 1.5.6
+merge_request:
+author:
diff --git a/changelogs/unreleased/27494-environment-list-column-headers.yml b/changelogs/unreleased/27494-environment-list-column-headers.yml
new file mode 100644
index 0000000000000000000000000000000000000000..798c01f3238763e5d34441ac3e0f27d6e6697764
--- /dev/null
+++ b/changelogs/unreleased/27494-environment-list-column-headers.yml
@@ -0,0 +1,4 @@
+---
+title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
+merge_request:
+author:
diff --git a/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc990c668664ad5c1d743e4bdc76a692a1c288fb
--- /dev/null
+++ b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
@@ -0,0 +1,4 @@
+---
+title: Fix wrong call to ProjectCacheWorker.perform
+merge_request: 8910
+author:
diff --git a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..11d1f55172b1d2a08b0a2e58fca9fa1d695d04a7
--- /dev/null
+++ b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
@@ -0,0 +1,4 @@
+---
+title: Fix notifications when set at group level
+merge_request: 6813
+author: Alexandre Maia
diff --git a/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml b/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml
deleted file mode 100644
index 74412c323756283d513645083fd300ff9d07f836..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes issue boards list colored top border visual glitch
-merge_request: 7898
-author: Pier Paolo Ramon
diff --git a/changelogs/unreleased/8-15-stable.yml b/changelogs/unreleased/8-15-stable.yml
new file mode 100644
index 0000000000000000000000000000000000000000..75502e139e7b3cb64e921b35605ce0297a5dc273
--- /dev/null
+++ b/changelogs/unreleased/8-15-stable.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure export files are removed after a namespace is deleted
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/8623-correct-robots-txt.yml b/changelogs/unreleased/8623-correct-robots-txt.yml
deleted file mode 100644
index 00ed80511cc6864706cdf60ca4a8e62e398be400..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/8623-correct-robots-txt.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Correct User-agent placement in robots.txt"
-merge_request: 8623
-author: Eric Sabelhaus
diff --git a/changelogs/unreleased/add-changelog-search-bar-first-iteration.yml b/changelogs/unreleased/add-changelog-search-bar-first-iteration.yml
deleted file mode 100644
index 4d83d744be7ba9d86c8ee84c1c2381f666d74b81..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/add-changelog-search-bar-first-iteration.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Search bar redesign first iteration
-merge_request: 7345
-author:
diff --git a/changelogs/unreleased/add_email_password_confirmation.yml b/changelogs/unreleased/add_email_password_confirmation.yml
deleted file mode 100644
index 92f9b9b7a6d15e683cd418e71a988d85fecd5d88..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/add_email_password_confirmation.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add email confirmation field to registration form
-merge_request: 7432
-author: 
diff --git a/changelogs/unreleased/add_project_update_hook.yml b/changelogs/unreleased/add_project_update_hook.yml
new file mode 100644
index 0000000000000000000000000000000000000000..915c953884386584cedf7815cc5cddbd4fa16156
--- /dev/null
+++ b/changelogs/unreleased/add_project_update_hook.yml
@@ -0,0 +1,4 @@
+---
+title: Add system hook for when a project is updated (other than rename/transfer)
+merge_request: 5711
+author: Tommy Beadle
diff --git a/changelogs/unreleased/additional-award-emoji-repositioning-fixes.yml b/changelogs/unreleased/additional-award-emoji-repositioning-fixes.yml
deleted file mode 100644
index 8d5a94c3aa8b97e948cfba557e0c084a95758ea1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/additional-award-emoji-repositioning-fixes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Removed bottom padding from merge manually from CLI because of repositioning award emoji's
-merge_request:
-author:
diff --git a/changelogs/unreleased/allow_plus_sign_for_snippets.yml b/changelogs/unreleased/allow_plus_sign_for_snippets.yml
deleted file mode 100644
index 62d9dd74d07ee81bf6ae419b880418a829f8874a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/allow_plus_sign_for_snippets.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow to use + symbol in filenames
-merge_request: 6644
-author: blackst0ne
diff --git a/changelogs/unreleased/asciidoctor-plantuml.yml b/changelogs/unreleased/asciidoctor-plantuml.yml
deleted file mode 100644
index ba6ef7c0800268e9ce721667b20c6f1b344d532d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/asciidoctor-plantuml.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add support for PlantUML diagrams in AsciiDoc documents.
-merge_request: 7810
-author: Horacio Sanson
diff --git a/changelogs/unreleased/badge-color-on-white-bg.yml b/changelogs/unreleased/badge-color-on-white-bg.yml
deleted file mode 100644
index 680d7ff11f0c0f7fca6fcf977e94f762e02a5d36..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/badge-color-on-white-bg.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added lighter count badge background-color for on white backgrounds
-merge_request: 7873
-author:
diff --git a/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml b/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77750b55e7e440da1ae174a66847e1b1a528bc7d
--- /dev/null
+++ b/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml
@@ -0,0 +1,4 @@
+---
+title: Hide version check image if there is no internet connection
+merge_request: 8355
+author: Ken Ding
diff --git a/changelogs/unreleased/bug-project-feature-compatibility.yml b/changelogs/unreleased/bug-project-feature-compatibility.yml
deleted file mode 100644
index 2124ee085e0de8c9d1d0bbda383e61666807e8dc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/bug-project-feature-compatibility.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility`
-  concern.
-merge_request: 8552
-author:
diff --git a/changelogs/unreleased/clipboard-button-text.yml b/changelogs/unreleased/clipboard-button-text.yml
deleted file mode 100644
index dc93da6042679ba46ef0c5d27bdc697c8ef98a5d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/clipboard-button-text.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: 'Copy <some text> to clipboard'
-merge_request: 8535
diff --git a/changelogs/unreleased/contribution-calendar-scroll.yml b/changelogs/unreleased/contribution-calendar-scroll.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a504d59e61c4223e5ae7a7bb2262dae970079143
--- /dev/null
+++ b/changelogs/unreleased/contribution-calendar-scroll.yml
@@ -0,0 +1,4 @@
+---
+title: contribution calendar scrolls from right to left
+merge_request:
+author:
diff --git a/changelogs/unreleased/cop-gem-fetcher.yml b/changelogs/unreleased/cop-gem-fetcher.yml
new file mode 100644
index 0000000000000000000000000000000000000000..506815a5b540edf8355c895c1f9ac35a78d9e7ed
--- /dev/null
+++ b/changelogs/unreleased/cop-gem-fetcher.yml
@@ -0,0 +1,4 @@
+---
+title: Cop for gem fetched from a git source
+merge_request: 8856
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/copy-as-md.yml b/changelogs/unreleased/copy-as-md.yml
new file mode 100644
index 0000000000000000000000000000000000000000..637e9dc36e29a822af3a4e51ece09346b91d253c
--- /dev/null
+++ b/changelogs/unreleased/copy-as-md.yml
@@ -0,0 +1,4 @@
+---
+title: Copying a rendered issue/comment will paste into GFM textareas as actual GFM
+merge_request:
+author:
diff --git a/changelogs/unreleased/didemacet-ci-lint-page.yml b/changelogs/unreleased/didemacet-ci-lint-page.yml
deleted file mode 100644
index 07386321c9dc875e4a8dfe5fa0371b9b389b66c6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/didemacet-ci-lint-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change CI template linter textarea with Ace Editor
-merge_request: 8452
-author: Didem Acet
diff --git a/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml b/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6dd0d74800197f35f68d61c48f42dde54d4819c5
--- /dev/null
+++ b/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml
@@ -0,0 +1,4 @@
+---
+title: Disable automatic login after clicking email confirmation links
+merge_request: 7472
+author:
diff --git a/changelogs/unreleased/display-project-id.yml b/changelogs/unreleased/display-project-id.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8705ed28400cb2140f9d40728edd0fd10e33da55
--- /dev/null
+++ b/changelogs/unreleased/display-project-id.yml
@@ -0,0 +1,4 @@
+---
+title: Display project ID in project settings
+merge_request: 8572
+author: winniehell
diff --git a/changelogs/unreleased/document-how-to-vue.yml b/changelogs/unreleased/document-how-to-vue.yml
new file mode 100644
index 0000000000000000000000000000000000000000..863e41b64136e2c88c4ae5ad7eef29a5fe491651
--- /dev/null
+++ b/changelogs/unreleased/document-how-to-vue.yml
@@ -0,0 +1,4 @@
+---
+title: Adds documentation for how to use Vue.js
+merge_request: 8866
+author:
diff --git a/changelogs/unreleased/dot-in-project-queries.yml b/changelogs/unreleased/dot-in-project-queries.yml
deleted file mode 100644
index fc48dc7b74d808b54cc55c6bc478f5cfbbd6f12d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dot-in-project-queries.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow API query to find projects with dots in their name
-merge_request:
-author: Bruno Melli
diff --git a/changelogs/unreleased/dz-nested-group-misc.yml b/changelogs/unreleased/dz-nested-group-misc.yml
deleted file mode 100644
index 9c9d0b1c644afd52fe81fcb7fd79f3e83cd4b275..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-nested-group-misc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show nested groups tab on group page
-merge_request: 8308
-author:
diff --git a/changelogs/unreleased/dz-nested-groups-improvements-2.yml b/changelogs/unreleased/dz-nested-groups-improvements-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e4eb7f1fff5dc1163792a685c694d989fc8842c
--- /dev/null
+++ b/changelogs/unreleased/dz-nested-groups-improvements-2.yml
@@ -0,0 +1,4 @@
+---
+title: Add read-only full_path and full_name attributes to Group API
+merge_request: 8827
+author:
diff --git a/changelogs/unreleased/dz-rename-invalid-users.yml b/changelogs/unreleased/dz-rename-invalid-users.yml
deleted file mode 100644
index f420b069531231d4f9d98b5a18f0d3635e2aac78..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dz-rename-invalid-users.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename users with namespace ending with .git
-merge_request: 8309
-author:
diff --git a/changelogs/unreleased/empty-selection-reply-shortcut.yml b/changelogs/unreleased/empty-selection-reply-shortcut.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5a42c98a8003c2c15757067d5ffe1a504a3428d7
--- /dev/null
+++ b/changelogs/unreleased/empty-selection-reply-shortcut.yml
@@ -0,0 +1,4 @@
+---
+title: Change the reply shortcut to focus the field even without a selection.
+merge_request: 8873
+author: Brian Hall
diff --git a/changelogs/unreleased/env-var-in-redis-config.yml b/changelogs/unreleased/env-var-in-redis-config.yml
deleted file mode 100644
index 561ea7f514ef46482c79e9433dbf9ffd2edf8400..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/env-var-in-redis-config.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow to use ENV variables in redis config
-merge_request: 8073
-author: Semyon Pupkov
diff --git a/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml b/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml
deleted file mode 100644
index 0fd590a877b2f1f9d0daf8709d84d25fac006a40..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow to add deploy keys with write-access
-merge_request: 5807
-author: Ali Ibrahim
diff --git a/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml b/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml
deleted file mode 100644
index 0c0b74b686a78cd2077a9f15f2178dc86263accd..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Merged the 'Groups' and 'Projects' tabs when viewing user profiles
-merge_request: 8323
-author: James Gregory
diff --git a/changelogs/unreleased/feature-gitaly-feature-flag.yml b/changelogs/unreleased/feature-gitaly-feature-flag.yml
deleted file mode 100644
index 1fa566aeb10fa53fe0fe990403a84042a457e743..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-gitaly-feature-flag.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Pass Gitaly resource path to gitlab-workhorse if Gitaly is enabled
-merge_request: 8440
-author:
diff --git a/changelogs/unreleased/feature-log-ldap-to-application-log.yml b/changelogs/unreleased/feature-log-ldap-to-application-log.yml
deleted file mode 100644
index 4cfbc23edb7f81f1a7a7f2d33488d4c0ee4b12a3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-log-ldap-to-application-log.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Log LDAP blocking/unblocking events to application log
-merge_request: 8042
-author: Markus Koller
diff --git a/changelogs/unreleased/feature-more-storage-statistics.yml b/changelogs/unreleased/feature-more-storage-statistics.yml
deleted file mode 100644
index 824fd36dc34dbb4ff0be0aa9a773728c6ca17105..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/feature-more-storage-statistics.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add more storage statistics
-merge_request: 7754
-author: Markus Koller
diff --git a/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml b/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5fba0332881958fcb9ba1a3dab1fded8eb7eb3df
--- /dev/null
+++ b/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml
@@ -0,0 +1,4 @@
+---
+title: Use warning icon in mini-graph if stage passed conditionally
+merge_request: 8503
+author:
diff --git a/changelogs/unreleased/filename-to-file-path.yml b/changelogs/unreleased/filename-to-file-path.yml
deleted file mode 100644
index 3c6c838595a5196af051051655300b737cff0730..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/filename-to-file-path.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: Rename filename to file path in tooltip of file header in merge request diff
-merge_request: 8314
\ No newline at end of file
diff --git a/changelogs/unreleased/fill-authorized-projects.yml b/changelogs/unreleased/fill-authorized-projects.yml
deleted file mode 100644
index e8e33011a15ee27a9df03c65d500cc551a5750ff..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fill-authorized-projects.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fill missing authorized projects rows
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-27479.yml b/changelogs/unreleased/fix-27479.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cc72a8306951a7fc2446d18521ab88421e353f46
--- /dev/null
+++ b/changelogs/unreleased/fix-27479.yml
@@ -0,0 +1,4 @@
+---
+title: Remove new branch button for confidential issues
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-api-deprecation.yml b/changelogs/unreleased/fix-api-deprecation.yml
deleted file mode 100644
index 90285ddf058935e31147f01b10b1b12c347e1508..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-api-deprecation.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix a Grape deprecation, use `#request_method` instead of `#route_method`
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-api-mr-permissions.yml b/changelogs/unreleased/fix-api-mr-permissions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..33b677b1f2919f9e273b63fb694db2569ec6bed5
--- /dev/null
+++ b/changelogs/unreleased/fix-api-mr-permissions.yml
@@ -0,0 +1,4 @@
+---
+title: Don't allow project guests to subscribe to merge requests through the API
+merge_request:
+author: Robert Schilling
diff --git a/changelogs/unreleased/fix-blame-500.yml b/changelogs/unreleased/fix-blame-500.yml
deleted file mode 100644
index 379d81aaa444fec2705db4db0e352d863337ff79..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-blame-500.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix blame 500 error on invalid path.
-merge_request: 25761
-author: Jeff Stubler
diff --git a/changelogs/unreleased/fix-boards-search-typo.yml b/changelogs/unreleased/fix-boards-search-typo.yml
deleted file mode 100644
index 0c083fc0d10746d22be5b595d812a743eefef991..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-boards-search-typo.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Fix typo: seach to search'
-merge_request: 8370
-author:
diff --git a/changelogs/unreleased/fix-broken-url-on-group-avatar.yml b/changelogs/unreleased/fix-broken-url-on-group-avatar.yml
deleted file mode 100644
index 7ce22b4826e740fcfe2d812f1d440d76b4b71466..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-broken-url-on-group-avatar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix broken url on group avatar
-merge_request: 8464
-author: hogewest
diff --git a/changelogs/unreleased/fix-build-sort-order.yml b/changelogs/unreleased/fix-build-sort-order.yml
deleted file mode 100644
index a6d6371f69acb972417ee491f71f5e54e229c5d5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-build-sort-order.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Sort numbers in build names more intelligently
-merge_request: 8277
-author:
diff --git a/changelogs/unreleased/fix-cancel-integration-settings.yml b/changelogs/unreleased/fix-cancel-integration-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..294b0aa5db9dcd4aee0335cf84ec975f3cdc4cfd
--- /dev/null
+++ b/changelogs/unreleased/fix-cancel-integration-settings.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed services form cancel not redirecting back the integrations settings view
+merge_request: 8843
+author:
diff --git a/changelogs/unreleased/fix-ci-build-policy.yml b/changelogs/unreleased/fix-ci-build-policy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..26003713ed443867868f35b25ae0bd59baeb6222
--- /dev/null
+++ b/changelogs/unreleased/fix-ci-build-policy.yml
@@ -0,0 +1,4 @@
+---
+title: Improve build policy and access abilities
+merge_request: 8711
+author:
diff --git a/changelogs/unreleased/fix-copy-issues-empty-state.yml b/changelogs/unreleased/fix-copy-issues-empty-state.yml
deleted file mode 100644
index a87b7612217f8fced0a056cfd5f783257a189594..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-copy-issues-empty-state.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve copy in Issue Tracker empty state
-merge_request: 8202
-author:
diff --git a/changelogs/unreleased/fix-depr-warn.yml b/changelogs/unreleased/fix-depr-warn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..618170277204559c81beeeb58bb15940d06fe77e
--- /dev/null
+++ b/changelogs/unreleased/fix-depr-warn.yml
@@ -0,0 +1,4 @@
+---
+title: resolve deprecation warnings
+merge_request: 8855
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/fix-external-status-badge-links.yml b/changelogs/unreleased/fix-external-status-badge-links.yml
deleted file mode 100644
index 2287a9b76c42a4552cfe5ea0ca55b109a697da9b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-external-status-badge-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Link external build badge to its target URL
-merge_request: 8611
-author:
diff --git a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3513f5afdfb1e2c1fdccd85a70a23801576270db
--- /dev/null
+++ b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
@@ -0,0 +1,4 @@
+---
+title: Fix filtering usernames with multiple words
+merge_request: 8851
+author:
diff --git a/changelogs/unreleased/fix-guest-access-posting-to-notes.yml b/changelogs/unreleased/fix-guest-access-posting-to-notes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..81377c0c6f0814d10a2f2c1f886d3bfdf9a5afdb
--- /dev/null
+++ b/changelogs/unreleased/fix-guest-access-posting-to-notes.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent users from creating notes on resources they can't access
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-import-encrypt-atts.yml b/changelogs/unreleased/fix-import-encrypt-atts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e34d895570b6fd804133b1267bd0528552a94e39
--- /dev/null
+++ b/changelogs/unreleased/fix-import-encrypt-atts.yml
@@ -0,0 +1,4 @@
+---
+title: Ignore encrypted attributes in Import/Export
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/fix-import-user-validation-error.yml b/changelogs/unreleased/fix-import-user-validation-error.yml
new file mode 100644
index 0000000000000000000000000000000000000000..985a3b0b26f730d89b40033d3d4e1e93f82d7032
--- /dev/null
+++ b/changelogs/unreleased/fix-import-user-validation-error.yml
@@ -0,0 +1,4 @@
+---
+title: Remove old project members when retrying an export
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml b/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml
deleted file mode 100644
index 3d8cf1c74a2044854359690fa60c228437079f6a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Hide build artifacts keep button if operation is not allowed
-merge_request: 8501
-author:
diff --git a/changelogs/unreleased/fix-light-hr-in-descriptions.yml b/changelogs/unreleased/fix-light-hr-in-descriptions.yml
deleted file mode 100644
index 8efd471e416287ddaacdbefa9262ce19327c562e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-light-hr-in-descriptions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Darkened hr border color in descriptions because of update of bootstrap
-merge_request: 8333
-author:
diff --git a/changelogs/unreleased/fix-more-orphans-remove-undeleted-groups.yml b/changelogs/unreleased/fix-more-orphans-remove-undeleted-groups.yml
deleted file mode 100644
index bc2068b8177dda8f9c456ca14d88fae529e3c20c..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-more-orphans-remove-undeleted-groups.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove extra orphaned rows when removing stray namespaces
-merge_request: 7841
-author: 
diff --git a/changelogs/unreleased/fix-no-milestone-option-for-projects-endpoint-23194.yml b/changelogs/unreleased/fix-no-milestone-option-for-projects-endpoint-23194.yml
deleted file mode 100644
index 980665377234f6321493fd870867a3d04cc9503a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-no-milestone-option-for-projects-endpoint-23194.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: fix query response for `/projects/:id/issues?milestone="No%20Milestone"`'
-merge_request: 8457
-author: Panagiotis Atmatzidis, David Eisner
diff --git a/changelogs/unreleased/fix-project-delete-tooltip.yml b/changelogs/unreleased/fix-project-delete-tooltip.yml
deleted file mode 100644
index 42fd9c32519e080d244ddc38c913f392afa2f3e0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-project-delete-tooltip.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix project queued for deletion re-creation tooltip
-merge_request: 
-author: 
diff --git a/changelogs/unreleased/fix-search-bar-search-param.yml b/changelogs/unreleased/fix-search-bar-search-param.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4df14d3bf134db0e91eec71ff199e0bdaad2db5a
--- /dev/null
+++ b/changelogs/unreleased/fix-search-bar-search-param.yml
@@ -0,0 +1,4 @@
+---
+title: Fix search bar search param encoding
+merge_request: 8753
+author:
diff --git a/changelogs/unreleased/fix-serialized-commit-path.yml b/changelogs/unreleased/fix-serialized-commit-path.yml
deleted file mode 100644
index 4e4df5038741ccccaac41d94304f803534bf1b71..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-serialized-commit-path.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix links to commits pages on pipelines list page
-merge_request: 8558
-author:
diff --git a/changelogs/unreleased/fix-timezone-due-date-picker.yml b/changelogs/unreleased/fix-timezone-due-date-picker.yml
deleted file mode 100644
index 2e6b71c70ca68b2a13f4a77351f1d2f2df79b038..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-timezone-due-date-picker.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix date inconsistency on due date picker
-merge_request: 7422
-author: Giuliano Varriale
diff --git a/changelogs/unreleased/fix-user-api-confirm-param.yml b/changelogs/unreleased/fix-user-api-confirm-param.yml
deleted file mode 100644
index 4264257663480b762e0698a1fd5fabc712e47ed1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-user-api-confirm-param.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix 500 error when POSTing to Users API with optional confirm param
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml b/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c9edd1de86c89f16befdedc24be482b588ff8e06
--- /dev/null
+++ b/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent users from deleting system deploy keys via the project deploy key API
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix_broken_diff_discussions.yml b/changelogs/unreleased/fix_broken_diff_discussions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4551212759f73540ee1f766a805960004bd95fe5
--- /dev/null
+++ b/changelogs/unreleased/fix_broken_diff_discussions.yml
@@ -0,0 +1,4 @@
+---
+title: Make MR-review-discussions more reliable
+merge_request:
+author:
diff --git a/changelogs/unreleased/fwn-to-find-by-full-path.yml b/changelogs/unreleased/fwn-to-find-by-full-path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1427e4e7624ba1d6e94a782319967bb98e2359e5
--- /dev/null
+++ b/changelogs/unreleased/fwn-to-find-by-full-path.yml
@@ -0,0 +1,4 @@
+---
+title: replace `find_with_namespace` with `find_by_full_path`
+merge_request: 8949
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f60417d185ebfef087a8d500c24208c6c75a3c7f
--- /dev/null
+++ b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml
@@ -0,0 +1,4 @@
+---
+title: Make notification_service spec DRYer by making test reusable
+merge_request: 
+author: YarNayar
diff --git a/changelogs/unreleased/get_last_used_date_of_ssh_key.yml b/changelogs/unreleased/get_last_used_date_of_ssh_key.yml
deleted file mode 100644
index b753949922c7ffaa7dbb125e71a722e3fd1637f3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/get_last_used_date_of_ssh_key.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Record and show last used date of SSH Keys
-merge_request: 8113
-author: Vincent Wong
diff --git a/changelogs/unreleased/group-label-sidebar-link.yml b/changelogs/unreleased/group-label-sidebar-link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c11c2d4ede1b4be508f966a521989e8ea2ecf059
--- /dev/null
+++ b/changelogs/unreleased/group-label-sidebar-link.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed group label links in issue/merge request sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/hardcode-title-system-note.yml b/changelogs/unreleased/hardcode-title-system-note.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1b0a63efa51871bdfe304060bbebf52057fe9bd9
--- /dev/null
+++ b/changelogs/unreleased/hardcode-title-system-note.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure autogenerated title does not cause failing spec
+merge_request: 8963
+author: brian m. carlson
diff --git a/changelogs/unreleased/i--25814-500-error.yml b/changelogs/unreleased/i--25814-500-error.yml
deleted file mode 100644
index cd55ede84c8b29a02fb9512dada2647227d242aa..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/i--25814-500-error.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Compare page throws 500 error when any branch/reference is not selected
-merge_request: 8492
-author: Martin Cabrera
diff --git a/changelogs/unreleased/improve-ci-example-php-doc.yml b/changelogs/unreleased/improve-ci-example-php-doc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39a85e3d26176df5fe8a1d5575a0ba33e18c85d5
--- /dev/null
+++ b/changelogs/unreleased/improve-ci-example-php-doc.yml
@@ -0,0 +1,4 @@
+---
+title: Changed composer installer script in the CI PHP example doc
+merge_request: 4342
+author: Jeffrey Cafferata
diff --git a/changelogs/unreleased/input-button-hover.yml b/changelogs/unreleased/input-button-hover.yml
deleted file mode 100644
index cbb35adb7690e460838069a1017a5a035e5f0fb8..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/input-button-hover.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add hover state to MR comment reply button
-merge_request:
-author:
diff --git a/changelogs/unreleased/issuable-sidebar-bug.yml b/changelogs/unreleased/issuable-sidebar-bug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4086292eb89273923ec069173639c48094d0ff8c
--- /dev/null
+++ b/changelogs/unreleased/issuable-sidebar-bug.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed Issuable sidebar not closing on smaller/mobile sized screens
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/issue-20428.yml b/changelogs/unreleased/issue-20428.yml
new file mode 100644
index 0000000000000000000000000000000000000000..60da1c14702b2e8cd5b0ac5264a326340c9e1326
--- /dev/null
+++ b/changelogs/unreleased/issue-20428.yml
@@ -0,0 +1,4 @@
+---
+title: Add ability to define a coverage regex in the .gitlab-ci.yml
+merge_request: 7447
+author: Leandro Camargo
diff --git a/changelogs/unreleased/issue-boards-animate.yml b/changelogs/unreleased/issue-boards-animate.yml
deleted file mode 100644
index 28394aec3d590f4516a34a65d46b55c17baea7e2..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-boards-animate.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added animations to issue boards interactions
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-sidebar-empty-assignee.yml b/changelogs/unreleased/issue-sidebar-empty-assignee.yml
new file mode 100644
index 0000000000000000000000000000000000000000..263af75b9e952d10b8e5ae7cc02e4892a60faa34
--- /dev/null
+++ b/changelogs/unreleased/issue-sidebar-empty-assignee.yml
@@ -0,0 +1,4 @@
+---
+title: Resets assignee dropdown when sidebar is open
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_22664.yml b/changelogs/unreleased/issue_22664.yml
deleted file mode 100644
index 18a8d9ec6be4ffca9b2e19e4aff8af8a4d93414b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_22664.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Check if user can read project before being assigned to issue
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_25017.yml b/changelogs/unreleased/issue_25017.yml
deleted file mode 100644
index 09126ae81bcf911b9be2634760469f180a554817..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_25017.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show 'too many changes' message for created merge requests when they are too large
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_25578.yml b/changelogs/unreleased/issue_25578.yml
deleted file mode 100644
index e10f1d232afb2da9708ff702cc5b5d68e3bfe94b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_25578.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix redirect after update file when user has forked project
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_25682.yml b/changelogs/unreleased/issue_25682.yml
deleted file mode 100644
index a50138756ba0464f1894947cde36e7ab2c46b988..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue_25682.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Parse JIRA issue references even if Issue Tracker is disabled
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_27211.yml b/changelogs/unreleased/issue_27211.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ad48fec5d853eb366aaa9f6c1fe85c0b36d28bac
--- /dev/null
+++ b/changelogs/unreleased/issue_27211.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unused js response from refs controller
+merge_request:
+author:
diff --git a/changelogs/unreleased/issues-8081.yml b/changelogs/unreleased/issues-8081.yml
deleted file mode 100644
index 82f746937bc592c37f1ef67d9d512fa1a2cca31f..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issues-8081.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: change 'gray' color theme name to 'black' to match the actual color
-merge_request: 7908
-author: BM5k
diff --git a/changelogs/unreleased/label-promotion.yml b/changelogs/unreleased/label-promotion.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2ab997bf42027079a612850b83cf491fa485fac6
--- /dev/null
+++ b/changelogs/unreleased/label-promotion.yml
@@ -0,0 +1,4 @@
+---
+title: "Project labels can now be promoted to group labels"
+merge_request: 7242
+author: Olaf Tomalka
diff --git a/changelogs/unreleased/ldap_maint_task.yml b/changelogs/unreleased/ldap_maint_task.yml
deleted file mode 100644
index 8acffba0ce52c978102ba5e8e0dea2d1b6ed5b82..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/ldap_maint_task.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add LDAP Rake task to rename a provider
-merge_request: 2181
-author: 
diff --git a/changelogs/unreleased/login-page-font-size.yml b/changelogs/unreleased/login-page-font-size.yml
deleted file mode 100644
index e7775006673e62bca5316dfb288534611c88136b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/login-page-font-size.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Decreases font-size on login page
-merge_request:
-author:
diff --git a/changelogs/unreleased/markdown-area-height-fix.yml b/changelogs/unreleased/markdown-area-height-fix.yml
deleted file mode 100644
index bf1b82cfd128b333808184ba613e8becc13b82f3..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/markdown-area-height-fix.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Autoresize markdown preview
-merge_request: 8607
-author: Didem Acet
diff --git a/changelogs/unreleased/markdown-plantuml.yml b/changelogs/unreleased/markdown-plantuml.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c855f0cbcf7a62e139abf867780c1a2554277151
--- /dev/null
+++ b/changelogs/unreleased/markdown-plantuml.yml
@@ -0,0 +1,4 @@
+---
+title: PlantUML support for Markdown
+merge_request: 8588
+author: Horacio Sanson
diff --git a/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml b/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f32b3aea3c89e09f613511df9c7b81ef46e076d2
--- /dev/null
+++ b/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml
@@ -0,0 +1,4 @@
+---
+title: adds avatar for discussion note
+merge_request: 8734
+author:
diff --git a/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml b/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml
deleted file mode 100644
index b8c7b78cf0d630324eedbc7775acb785b3c275a1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed merge request tabs dont move when opening collapsed sidebar
-merge_request:
-author:
diff --git a/changelogs/unreleased/mr-tabs-container-offset.yml b/changelogs/unreleased/mr-tabs-container-offset.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5df8abfcf2a4bb209606e4cbbbb0834d737ae4a
--- /dev/null
+++ b/changelogs/unreleased/mr-tabs-container-offset.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed merge requests tab extra margin when fixed to window
+merge_request:
+author:
diff --git a/changelogs/unreleased/newline-eslint-rule.yml b/changelogs/unreleased/newline-eslint-rule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5ce080b69126571942bee77932eb20268e91f5c6
--- /dev/null
+++ b/changelogs/unreleased/newline-eslint-rule.yml
@@ -0,0 +1,4 @@
+---
+title: Flag multiple empty lines in eslint, fix offenses.
+merge_request: 8137
+author:
diff --git a/changelogs/unreleased/no_project_notes.yml b/changelogs/unreleased/no_project_notes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6106c027360788635b60365aa6eafe76114888d5
--- /dev/null
+++ b/changelogs/unreleased/no_project_notes.yml
@@ -0,0 +1,4 @@
+---
+title: Support notes when a project is not specified (personal snippet notes)
+merge_request: 8468
+author:
diff --git a/changelogs/unreleased/nuke-ugly-spaces-in-changelog-generator.yml b/changelogs/unreleased/nuke-ugly-spaces-in-changelog-generator.yml
deleted file mode 100644
index fd17303110733b323c42eb136742a328cb4ea1b0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/nuke-ugly-spaces-in-changelog-generator.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove trailing whitespace when generating changelog entry
-merge_request: 7948
-author:
diff --git a/changelogs/unreleased/pc-add-gitaly-to-architecture.yml b/changelogs/unreleased/pc-add-gitaly-to-architecture.yml
deleted file mode 100644
index 7c18da698df2dd5837903479db22646c805da1e7..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/pc-add-gitaly-to-architecture.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Gitaly to the architecture documentation
-merge_request: 8264
-author: Pablo Carranza <pablo@gitlab.com>
diff --git a/changelogs/unreleased/pipelines-graph-html-css.yml b/changelogs/unreleased/pipelines-graph-html-css.yml
deleted file mode 100644
index ff0c3122fdb479f1806d529db15895fb8896c270..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/pipelines-graph-html-css.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes and Improves CSS and HTML problems in mini pipeline graph and builds dropdown
-merge_request: 8443
-author:
diff --git a/changelogs/unreleased/pmq20-gitlab-ce-psvr-head-cache.yml b/changelogs/unreleased/pmq20-gitlab-ce-psvr-head-cache.yml
deleted file mode 100644
index 23230128dc93b160aaf6685c45061e27ce5b8be6..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/pmq20-gitlab-ce-psvr-head-cache.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Expire related caches after changing HEAD
-merge_request:
-author: Minqi Pan
diff --git a/changelogs/unreleased/re-style-issue-new-branch.yml b/changelogs/unreleased/re-style-issue-new-branch.yml
deleted file mode 100644
index 977a54ff2aedaeabe04a49675451baa5ca7ef49e..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/re-style-issue-new-branch.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: Remove checking branches state in issue new branch button
-merge_request: 8023
diff --git a/changelogs/unreleased/recaptcha_500.yml b/changelogs/unreleased/recaptcha_500.yml
deleted file mode 100644
index de9ef183d5e217ef2d296df8cb4d2c51e2f42be1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/recaptcha_500.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Properly handle failed reCAPTCHA on user registration
-merge_request: 8403
-author:
diff --git a/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml b/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e0f7e11b6d1aa1d9ebf0615d74b9c40437f9d092
--- /dev/null
+++ b/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml
@@ -0,0 +1,5 @@
+---
+title: 'Search feature: redirects to commit page if query is commit sha and only commit
+  found'
+merge_request: 8028
+author: YarNayar
diff --git a/changelogs/unreleased/reduce-queries-milestone-index.yml b/changelogs/unreleased/reduce-queries-milestone-index.yml
deleted file mode 100644
index a779b58c973a8eda2a6bd6da37e3efbd23b43398..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/reduce-queries-milestone-index.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use cached values to compute total issues count in milestone index pages
-merge_request: 8518
-author:
diff --git a/changelogs/unreleased/refresh-authorizations-tighter-lease.yml b/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
deleted file mode 100644
index ab42b2eb72dcd63d703c1d89fae40cbf94fecddc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Synchronize all project authorization refreshing work to prevent race conditions
-merge_request:
-author:
diff --git a/changelogs/unreleased/relative-url-assets.yml b/changelogs/unreleased/relative-url-assets.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0877664aca4526c28f452d814ba28d3807f97d63
--- /dev/null
+++ b/changelogs/unreleased/relative-url-assets.yml
@@ -0,0 +1,4 @@
+---
+title: allow relative url change without recompiling frontend assets
+merge_request: 8831
+author:
diff --git a/changelogs/unreleased/remove-project-authorizations-id-column.yml b/changelogs/unreleased/remove-project-authorizations-id-column.yml
deleted file mode 100644
index 24c86f0fb1bd5ac99d917579de1bb6395c499177..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/remove-project-authorizations-id-column.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove the project_authorizations.id column
-merge_request:
-author:
diff --git a/changelogs/unreleased/remove-successful-pipeline-emails-for-now.yml b/changelogs/unreleased/remove-successful-pipeline-emails-for-now.yml
deleted file mode 100644
index 47d484e5c84fadff6a2da0125eb715c2e3d3ef51..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/remove-successful-pipeline-emails-for-now.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make successful pipeline emails off for watchers
-merge_request: 8176
-author:
diff --git a/changelogs/unreleased/restore-backup-when-env-variable-is-passed.yml b/changelogs/unreleased/restore-backup-when-env-variable-is-passed.yml
deleted file mode 100644
index 8ec3cfdbb089962050739a28b23465d427b1e7ec..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/restore-backup-when-env-variable-is-passed.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Restore backup correctly when "BACKUP" environment variable is passed
-merge_request: 8477
-author:
diff --git a/changelogs/unreleased/sandish-gitlab-ce-update_ret_val.yml b/changelogs/unreleased/sandish-gitlab-ce-update_ret_val.yml
deleted file mode 100644
index 7107ddfd982d8c4ac29058176cc37074f0c09f6d..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/sandish-gitlab-ce-update_ret_val.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ensure updating project settings shows a flash message on success
-merge_request: 8579
-author: Sandish Chen
diff --git a/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e69fcd2aa63531705d3eca1e8f42d9a6246b5158
--- /dev/null
+++ b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml
@@ -0,0 +1,4 @@
+---
+title: Add project ID index to `project_authorizations` table to optimize queries
+merge_request:
+author:
diff --git a/changelogs/unreleased/single-edit-comment-widget-2.yml b/changelogs/unreleased/single-edit-comment-widget-2.yml
deleted file mode 100644
index e8b8beb15dee7b09487dcba2596f9eeba66c6e27..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/single-edit-comment-widget-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refactored note edit form to improve frontend performance on MR and Issues
-  pages, especially pages with has a lot of discussions in it
-merge_request: 8356
-author:
diff --git a/changelogs/unreleased/small-screen-fullscreen-button.yml b/changelogs/unreleased/small-screen-fullscreen-button.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4c269bc473a71a525ad7235e577d0c5efa125b9
--- /dev/null
+++ b/changelogs/unreleased/small-screen-fullscreen-button.yml
@@ -0,0 +1,4 @@
+---
+title: Display fullscreen button on small screens
+merge_request: 5302
+author: winniehell
diff --git a/changelogs/unreleased/snippet-spam.yml b/changelogs/unreleased/snippet-spam.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4867f08895384be489bdf5bc651251fd2952a9ec
--- /dev/null
+++ b/changelogs/unreleased/snippet-spam.yml
@@ -0,0 +1,4 @@
+---
+title: Check public snippets for spam
+merge_request:
+author:
diff --git a/changelogs/unreleased/speed-up-dashboard-milestone-index.yml b/changelogs/unreleased/speed-up-dashboard-milestone-index.yml
deleted file mode 100644
index ba4ff931ea8007d12d39ad7edd45050cbdea5c2b..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/speed-up-dashboard-milestone-index.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Speed up dashboard milestone index by scoping IssuesFinder to user authorized
-  projects
-merge_request: 8524
-author:
diff --git a/changelogs/unreleased/support-google-cloud-storage-backups.yml b/changelogs/unreleased/support-google-cloud-storage-backups.yml
deleted file mode 100644
index cec279a5c736a588b90c9b5cfdd9b763893304f0..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/support-google-cloud-storage-backups.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Re-add Google Cloud Storage as a backup strategy
-merge_request:
-author:
diff --git a/changelogs/unreleased/tc-only-mr-button-if-allowed.yml b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7f5dcb560ce96aa2cfcea549d2606505b1104d9
--- /dev/null
+++ b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml
@@ -0,0 +1,4 @@
+---
+title: Only show Merge Request button when user can create a MR
+merge_request: 8639
+author:
diff --git a/changelogs/unreleased/time-tracking-api.yml b/changelogs/unreleased/time-tracking-api.yml
deleted file mode 100644
index b58d73bef81a14d3b60337a2d1384b7533074647..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/time-tracking-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add new endpoints for Time Tracking.
-merge_request: 8483
-author:
diff --git a/changelogs/unreleased/update-gitlab-markup-gem.yml b/changelogs/unreleased/update-gitlab-markup-gem.yml
deleted file mode 100644
index 96cdfd051f09a2ba91ba5524230289c8fd0ddd42..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/update-gitlab-markup-gem.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update the gitlab-markup gem to the version 1.5.1
-merge_request: 8509
-author:
diff --git a/changelogs/unreleased/upgrade-omniauth.yml b/changelogs/unreleased/upgrade-omniauth.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e0334566dcaa91dd1af307ec5b8f9dcff779b2d
--- /dev/null
+++ b/changelogs/unreleased/upgrade-omniauth.yml
@@ -0,0 +1,4 @@
+---
+title: Upgrade omniauth gem to 1.3.2
+merge_request:
+author:
diff --git a/changelogs/unreleased/validate-title-length.yml b/changelogs/unreleased/validate-title-length.yml
deleted file mode 100644
index 7abf1c4d05acb9d56129fba6d16ad3047911f6b1..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/validate-title-length.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Validate label's title length"
-merge_request: 5767
-author: Tomáš Kukrál
diff --git a/changelogs/unreleased/view-ce-vs-ee.yml b/changelogs/unreleased/view-ce-vs-ee.yml
deleted file mode 100644
index 38bce4ac7c3ff7e820917aa5e7c3aba99d020f51..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/view-ce-vs-ee.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: About GitLab link in sidebar that links to help page
-merge_request: 8316
-author:
diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2494884f5c92a5bad9b1f7b88f992dcf52194c04
--- /dev/null
+++ b/changelogs/unreleased/zj-format-chat-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Reformat messages ChatOps
+merge_request: 8528
+author:
diff --git a/changelogs/unreleased/zj-requeue-pending-delete.yml b/changelogs/unreleased/zj-requeue-pending-delete.yml
new file mode 100644
index 0000000000000000000000000000000000000000..464c5948f8cd8d9f327d63fbf7a6936729aac6fd
--- /dev/null
+++ b/changelogs/unreleased/zj-requeue-pending-delete.yml
@@ -0,0 +1,4 @@
+---
+title: Requeue pending deletion projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-slow-service-fetch.yml b/changelogs/unreleased/zj-slow-service-fetch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8037361d2fc2d2138ac15cfefa0022c19df7d0bf
--- /dev/null
+++ b/changelogs/unreleased/zj-slow-service-fetch.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of slash commands
+merge_request: 8876
+author:
diff --git a/changelogs/unreleased/zj-unadressable-url-variables.yml b/changelogs/unreleased/zj-unadressable-url-variables.yml
deleted file mode 100644
index 6c412bd05400b54e730c3ee0af6bf060b1f93734..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/zj-unadressable-url-variables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't validate environment urls on .gitlab-ci.yml
-merge_request:
-author:
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index d970287024995378a00405edb1e69dc3d5bb2220..a33e40e8eb39f494bbc87d4352afc265f72b4920 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -3,8 +3,8 @@
 #
 production:
   adapter: mysql2
-  encoding: utf8mb4
-  collation: utf8mb4_general_ci
+  encoding: utf8
+  collation: utf8_general_ci
   reconnect: false
   database: gitlabhq_production
   pool: 10
@@ -18,8 +18,8 @@ production:
 #
 development:
   adapter: mysql2
-  encoding: utf8mb4
-  collation: utf8mb4_general_ci
+  encoding: utf8
+  collation: utf8_general_ci
   reconnect: false
   database: gitlabhq_development
   pool: 5
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 906ec11f01252ad9b3db8843ffb00cafd05a6287..4f33aad8693af2617590816f85f0f12be10bd6e7 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -419,10 +419,4 @@ if Rails.env.test?
 end
 
 # Force a refresh of application settings at startup
-begin
-  ApplicationSetting.expire
-  Ci::ApplicationSetting.expire
-rescue
-  # Gracefully handle when Redis is not available. For example,
-  # omnibus may fail here during assets:precompile.
-end
+ApplicationSetting.expire
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index ed88c8ee1b827d043d1cb2a815112a3280a5a982..2bd159ca7f189b4ef4452858387a91fff3bd0f09 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,9 +1,3 @@
-# GIT over SSH
-require_dependency Rails.root.join('lib/gitlab/backend/shell')
-
-# GitLab shell adapter
-require_dependency Rails.root.join('lib/gitlab/backend/shell_adapter')
-
 required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
 current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
 
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 3b8771543e4fb5f1a1500cb1071924aca5f8388b..e0702e06cc9f7d30264be919bd41133e9771f4e7 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -1,3 +1,117 @@
+# Autoload all classes that we want to instrument, and instrument the methods we
+# need. This takes the Gitlab::Metrics::Instrumentation module as an argument so
+# that we can stub it for testing, as it is only called when metrics are
+# enabled.
+#
+# rubocop:disable Metrics/AbcSize
+def instrument_classes(instrumentation)
+  instrumentation.instrument_instance_methods(Gitlab::Shell)
+
+  instrumentation.instrument_methods(Gitlab::Git)
+
+  Gitlab::Git.constants.each do |name|
+    const = Gitlab::Git.const_get(name)
+
+    next unless const.is_a?(Module)
+
+    instrumentation.instrument_methods(const)
+    instrumentation.instrument_instance_methods(const)
+  end
+
+  # Path to search => prefix to strip from constant
+  paths_to_instrument = {
+    ['app', 'finders']                    => ['app', 'finders'],
+    ['app', 'mailers', 'emails']          => ['app', 'mailers'],
+    ['app', 'services', '**']             => ['app', 'services'],
+    ['lib', 'gitlab', 'conflicts']        => ['lib'],
+    ['lib', 'gitlab', 'diff']             => ['lib'],
+    ['lib', 'gitlab', 'email', 'message'] => ['lib'],
+    ['lib', 'gitlab', 'checks']           => ['lib']
+  }
+
+  paths_to_instrument.each do |(path, prefix)|
+    prefix = Rails.root.join(*prefix)
+
+    Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
+      path = Pathname.new(file_path).relative_path_from(prefix)
+      const = path.to_s.sub('.rb', '').camelize.constantize
+
+      instrumentation.instrument_methods(const)
+      instrumentation.instrument_instance_methods(const)
+    end
+  end
+
+  instrumentation.instrument_methods(Premailer::Adapter::Nokogiri)
+  instrumentation.instrument_instance_methods(Premailer::Adapter::Nokogiri)
+
+  [
+    :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
+    :Tag, :TagCollection, :Tree
+  ].each do |name|
+    const = Rugged.const_get(name)
+
+    instrumentation.instrument_methods(const)
+    instrumentation.instrument_instance_methods(const)
+  end
+
+  # Instruments all Banzai filters and reference parsers
+  {
+    Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
+    ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb')
+  }.each do |const_name, path|
+    Dir[path].each do |file|
+      klass = File.basename(file, File.extname(file)).camelize
+      const = Banzai.const_get(const_name).const_get(klass)
+
+      instrumentation.instrument_methods(const)
+      instrumentation.instrument_instance_methods(const)
+    end
+  end
+
+  instrumentation.instrument_methods(Banzai::Renderer)
+  instrumentation.instrument_methods(Banzai::Querying)
+
+  instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
+  instrumentation.instrument_instance_methods(Banzai::Redactor)
+  instrumentation.instrument_methods(Banzai::NoteRenderer)
+
+  [Issuable, Mentionable, Participable].each do |klass|
+    instrumentation.instrument_instance_methods(klass)
+    instrumentation.instrument_instance_methods(klass::ClassMethods)
+  end
+
+  instrumentation.instrument_methods(Gitlab::ReferenceExtractor)
+  instrumentation.instrument_instance_methods(Gitlab::ReferenceExtractor)
+
+  # Instrument the classes used for checking if somebody has push access.
+  instrumentation.instrument_instance_methods(Gitlab::GitAccess)
+  instrumentation.instrument_instance_methods(Gitlab::GitAccessWiki)
+
+  instrumentation.instrument_instance_methods(API::Helpers)
+
+  instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
+
+  instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet)
+  instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
+
+  [:XML, :HTML].each do |namespace|
+    namespace_mod = Nokogiri.const_get(namespace)
+
+    instrumentation.instrument_methods(namespace_mod)
+    instrumentation.instrument_methods(namespace_mod::Document)
+  end
+
+  instrumentation.instrument_methods(Rinku)
+  instrumentation.instrument_instance_methods(Repository)
+
+  instrumentation.instrument_methods(Gitlab::Highlight)
+  instrumentation.instrument_instance_methods(Gitlab::Highlight)
+
+  # This is a Rails scope so we have to instrument it manually.
+  instrumentation.instrument_method(Project, :visible_to_user)
+end
+# rubocop:enable Metrics/AbcSize
+
 if Gitlab::Metrics.enabled?
   require 'pathname'
   require 'influxdb'
@@ -49,110 +163,7 @@ if Gitlab::Metrics.enabled?
   end
 
   Gitlab::Metrics::Instrumentation.configure do |config|
-    config.instrument_instance_methods(Gitlab::Shell)
-
-    config.instrument_methods(Gitlab::Git)
-
-    Gitlab::Git.constants.each do |name|
-      const = Gitlab::Git.const_get(name)
-
-      next unless const.is_a?(Module)
-
-      config.instrument_methods(const)
-      config.instrument_instance_methods(const)
-    end
-
-    # Path to search => prefix to strip from constant
-    paths_to_instrument = {
-      ['app', 'finders']                    => ['app', 'finders'],
-      ['app', 'mailers', 'emails']          => ['app', 'mailers'],
-      ['app', 'services', '**']             => ['app', 'services'],
-      ['lib', 'gitlab', 'conflicts']        => ['lib'],
-      ['lib', 'gitlab', 'diff']             => ['lib'],
-      ['lib', 'gitlab', 'email', 'message'] => ['lib'],
-      ['lib', 'gitlab', 'checks']           => ['lib']
-    }
-
-    paths_to_instrument.each do |(path, prefix)|
-      prefix = Rails.root.join(*prefix)
-
-      Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
-        path = Pathname.new(file_path).relative_path_from(prefix)
-        const = path.to_s.sub('.rb', '').camelize.constantize
-
-        config.instrument_methods(const)
-        config.instrument_instance_methods(const)
-      end
-    end
-
-    config.instrument_methods(Premailer::Adapter::Nokogiri)
-    config.instrument_instance_methods(Premailer::Adapter::Nokogiri)
-
-    [
-      :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
-      :Tag, :TagCollection, :Tree
-    ].each do |name|
-      const = Rugged.const_get(name)
-
-      config.instrument_methods(const)
-      config.instrument_instance_methods(const)
-    end
-
-    # Instruments all Banzai filters and reference parsers
-    {
-      Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
-      ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb')
-    }.each do |const_name, path|
-      Dir[path].each do |file|
-        klass = File.basename(file, File.extname(file)).camelize
-        const = Banzai.const_get(const_name).const_get(klass)
-
-        config.instrument_methods(const)
-        config.instrument_instance_methods(const)
-      end
-    end
-
-    config.instrument_methods(Banzai::Renderer)
-    config.instrument_methods(Banzai::Querying)
-
-    config.instrument_instance_methods(Banzai::ObjectRenderer)
-    config.instrument_instance_methods(Banzai::Redactor)
-    config.instrument_methods(Banzai::NoteRenderer)
-
-    [Issuable, Mentionable, Participable].each do |klass|
-      config.instrument_instance_methods(klass)
-      config.instrument_instance_methods(klass::ClassMethods)
-    end
-
-    config.instrument_methods(Gitlab::ReferenceExtractor)
-    config.instrument_instance_methods(Gitlab::ReferenceExtractor)
-
-    # Instrument the classes used for checking if somebody has push access.
-    config.instrument_instance_methods(Gitlab::GitAccess)
-    config.instrument_instance_methods(Gitlab::GitAccessWiki)
-
-    config.instrument_instance_methods(API::Helpers)
-
-    config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
-
-    config.instrument_instance_methods(Rouge::Plugins::Redcarpet)
-    config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
-
-    [:XML, :HTML].each do |namespace|
-      namespace_mod = Nokogiri.const_get(namespace)
-
-      config.instrument_methods(namespace_mod)
-      config.instrument_methods(namespace_mod::Document)
-    end
-
-    config.instrument_methods(Rinku)
-    config.instrument_instance_methods(Repository)
-
-    config.instrument_methods(Gitlab::Highlight)
-    config.instrument_instance_methods(Gitlab::Highlight)
-
-    # This is a Rails scope so we have to instrument it manually.
-    config.instrument_method(Project, :visible_to_user)
+    instrument_classes(config)
   end
 
   GC::Profiler.enable
diff --git a/config/initializers/plantuml_lexer.rb b/config/initializers/plantuml_lexer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8a77b146fa0dc82ac4cd53348d7ae62cdac4216
--- /dev/null
+++ b/config/initializers/plantuml_lexer.rb
@@ -0,0 +1,2 @@
+# Touch the lexers so it is registered with Rouge
+Rouge::Lexers::Plantuml
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
index a9aa802681a0211835a6f5dfb8b00fda27c56868..fb5a7b8372e55b2a34b361a1287197131e11edf7 100644
--- a/config/initializers/request_profiler.rb
+++ b/config/initializers/request_profiler.rb
@@ -1,5 +1,3 @@
-require 'gitlab/request_profiler/middleware'
-
 Rails.application.configure do |config|
   config.middleware.use(Gitlab::RequestProfiler::Middleware)
 end
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f462e654b2cc1251dd860b8adcbad1b4681a9286
--- /dev/null
+++ b/config/initializers/rspec_profiling.rb
@@ -0,0 +1,14 @@
+module RspecProfilingConnection
+  def establish_connection
+    ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+  end
+end
+
+if Rails.env.test?
+  RspecProfiling.configure do |config|
+    if ENV['RSPEC_PROFILING_POSTGRES_URL']
+      RspecProfiling::Collectors::PSQL.prepend(RspecProfilingConnection)
+      config.collector = RspecProfiling::Collectors::PSQL
+    end
+  end
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 5a7365bb0f6fd64a7d55bf65fbb7300ae87fd116..fa31838440569eca1374469df0e4e5c79ba452bb 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -12,6 +12,11 @@ Sidekiq.configure_server do |config|
     chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
     chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
     chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
+    chain.add Gitlab::SidekiqStatus::ServerMiddleware
+  end
+
+  config.client_middleware do |chain|
+    chain.add Gitlab::SidekiqStatus::ClientMiddleware
   end
 
   # Sidekiq-cron: load recurring jobs from gitlab.yml
@@ -46,6 +51,10 @@ end
 
 Sidekiq.configure_client do |config|
   config.redis = redis_config_hash
+
+  config.client_middleware do |chain|
+    chain.add Gitlab::SidekiqStatus::ClientMiddleware
+  end
 end
 
 # The Sidekiq client API always adds the queue to the Sidekiq queue
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 776c31c9dac33b0a851fa16daf85bf62234634bd..60a1175fe8057a2423d651a1e98be7e116265e54 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -19,13 +19,11 @@ end
 
 scope(path: 'groups/*id',
       controller: :groups,
-      constraints: { id: Gitlab::Regex.namespace_route_regex }) do
+      constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
   get :edit, as: :edit_group
   get :issues, as: :issues_group
   get :merge_requests, as: :merge_requests_group
   get :projects, as: :projects_group
   get :activity, as: :activity_group
+  get '/', action: :show, as: :group_canonical
 end
-
-# Must be last route in this file
-get 'groups/*id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex }
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 6620b765e026c8a5cb62e9feec15c98f3cfacc28..efe2fbc521d8e55ef7f960c53765887b3320007a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do
       resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
         member do
           get 'raw'
+          post :mark_as_spam
         end
       end
 
@@ -220,6 +221,7 @@ constraints(ProjectUrlConstrainer.new) do
         end
 
         member do
+          post :promote
           post :toggle_subscription
           delete :remove_priority
         end
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index 3ca096f31ba6ba13cae95337022148a1a73a3b16..ce0d131429216e3329b9b836674c69e2dfb3e51c 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do
   member do
     get 'raw'
     get 'download'
+    post :mark_as_spam
   end
 end
 
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index bba2fc4b186ea050f64b106928d22db3011d38ef..6f241f6fa4a782e0c0a73efff526bd36048d4600 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   User.seed do |s|
     s.id = 1
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index a984eda5ab5900a1b80de211903510a29f9f530d..c2b8f7ba819efb6ed3d24433f1b38f9182633ab5 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,4 +1,4 @@
-require 'sidekiq/testing'
+require './spec/support/sidekiq'
 
 Sidekiq::Testing.inline! do
   Gitlab::Seeder.quiet do
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index 03da29c4c686930c5020b6fac27400b204a485aa..101ff3a12099b471ea890888a557e8709260f59e 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   20.times do |i|
     begin
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index 5c2a03fec3f09a2621aab18b7c368af15d0aa65f..86e0a38aae17b65e7136133dd39984dff297f006 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,4 +1,4 @@
-require 'sidekiq/testing'
+require './spec/support/sidekiq'
 
 Sidekiq::Testing.inline! do
   Gitlab::Seeder.quiet do
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index 540e4e6825947dc2c04a67ff513ac365f6a8878f..271bfbc97e016fdc9c06b6560e55a8e1de9c6195 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   Project.all.each do |project|
     5.times do |i|
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 4fa572fca9bdab3570781c49c95954b7dc1f164a..d93d133d157678a3d1c422e027d4ee02e2ba4cba 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   Project.all.each do |project|
     10.times do
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 87fb8e3300d735a7e0f90c525c79fa0cfc174780..c304e0706dc090d348fdf7f014b25e83db0fc46d 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   # Limit the number of merge requests per project to avoid long seeds
   MAX_NUM_MERGE_REQUESTS = 10
@@ -24,7 +26,7 @@ Gitlab::Seeder.quiet do
     end
   end
 
-  project = Project.find_with_namespace('gitlab-org/gitlab-test')
+  project = Project.find_by_full_path('gitlab-org/gitlab-test')
 
   params = {
     source_branch: 'feature',
diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb
index 8b4bee384e1551dda3e98511ecb0a48612a71b25..51e22137d6f8a0a03c42f35d87da72d9d5ecc53b 100644
--- a/db/fixtures/development/11_keys.rb
+++ b/db/fixtures/development/11_keys.rb
@@ -1,12 +1,18 @@
-Gitlab::Seeder.quiet do
-  User.first(10).each do |user|
-    key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{user.id + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+require './spec/support/sidekiq'
 
-    user.keys.create(
-      title: "Sample key #{user.id}",
-      key: key
-    )
+# Creating keys runs a gitlab-shell worker. Since we may not have the right
+# gitlab-shell path set (yet) we need to disable this for these fixtures.
+Sidekiq::Testing.disable! do
+  Gitlab::Seeder.quiet do
+    User.first(10).each do |user|
+      key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{user.id + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
 
-    print '.'
+      user.keys.create(
+        title: "Sample key #{user.id}",
+        key: key
+      )
+
+      print '.'
+    end
   end
 end
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index 74898544a69a49c6656ba17669392ed00743d490..4f3bdba043d008d96b4e9163c3e3ce7dacd0466a 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   content =<<eos
 class Member < ActiveRecord::Base
diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb
index 566c07056387d4b7c05d110d363f2ef2b89d91f5..29b8081055d30a429f09680d4e154277157b2760 100644
--- a/db/fixtures/development/13_comments.rb
+++ b/db/fixtures/development/13_comments.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   Issue.all.each do |issue|
     project = issue.project
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index be95d788850b9506db0efd49d891ef67440b45db..534847a71079c212f8af3951af066fe40d1b26ed 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 class Gitlab::Seeder::Pipelines
   STAGES = %w[build test deploy notify]
   BUILDS = [
diff --git a/db/fixtures/development/15_award_emoji.rb b/db/fixtures/development/15_award_emoji.rb
index baac32f2d10038233a1dc65beea7f42e61b1dec3..ea343c26b69a3e3ff847f9409d85c6290b558bfb 100644
--- a/db/fixtures/development/15_award_emoji.rb
+++ b/db/fixtures/development/15_award_emoji.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   emoji = Gitlab::AwardEmoji.emojis.keys
 
diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb
index 103c7f9445c08f9cfa436e2f8e092095cc6c413f..39d466fb43fe4de5580206eec525bc509e16dd8d 100644
--- a/db/fixtures/development/16_protected_branches.rb
+++ b/db/fixtures/development/16_protected_branches.rb
@@ -1,3 +1,5 @@
+require './spec/support/sidekiq'
+
 Gitlab::Seeder.quiet do
   admin_user = User.find(1)
 
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 916ee8dbac8a6cc937550fcd70cfd59c7b407c4c..747901dd63488e622eae4e07dc7fd43d70200696 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -1,4 +1,4 @@
-require 'sidekiq/testing'
+require './spec/support/sidekiq'
 require './spec/support/test_env'
 
 class Gitlab::Seeder::CycleAnalytics
diff --git a/db/migrate/20161114024742_add_coverage_regex_to_builds.rb b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..88aa5d52b396ae2290a9f92f93ff00e6f2b06ab8
--- /dev/null
+++ b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
@@ -0,0 +1,13 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddCoverageRegexToBuilds < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def change
+    add_column :ci_builds, :coverage_regex, :string
+  end
+end
diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb
index 7153e6a32b1cdc93088899852d3c17824cefa8ba..8e98ee5b9ba18fd3488fe2bf4e1d256637e971b2 100644
--- a/db/migrate/20161207231626_add_environment_slug.rb
+++ b/db/migrate/20161207231626_add_environment_slug.rb
@@ -8,8 +8,9 @@ class AddEnvironmentSlug < ActiveRecord::Migration
   DOWNTIME_REASON = 'Adding NOT NULL column environments.slug with dependent data'
 
   # Used to generate random suffixes for the slug
+  LETTERS = 'a'..'z'
   NUMBERS = '0'..'9'
-  SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
+  SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
 
   def up
     environments = Arel::Table.new(:environments)
@@ -39,17 +40,24 @@ class AddEnvironmentSlug < ActiveRecord::Migration
     slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
 
     # Must start with a letter
-    slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
+    slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
+
+    # Repeated dashes are invalid (OpenShift limitation)
+    slugified.gsub!(/\-+/, '-')
 
     # Maximum length: 24 characters (OpenShift limitation)
     slugified = slugified[0..23]
 
-    # Cannot end with a "-" character (Kubernetes label limitation)
-    slugified = slugified[0..-2] if slugified[-1] == "-"
+    # Cannot end with a dash (Kubernetes label limitation)
+    slugified.chop! if slugified.end_with?('-')
 
     # Add a random suffix, shortening the current string if necessary, if it
     # has been slugified. This ensures uniqueness.
-    slugified = slugified[0..16] + "-" + random_suffix if slugified != name
+    if slugified != name
+      slugified = slugified[0..16]
+      slugified << '-' unless slugified.end_with?('-')
+      slugified << random_suffix
+    end
 
     slugified
   end
diff --git a/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb
index 2cbe626d7524a52ee32b9afeb733d6c979b629af..d5116dfab49a59c80b0e5c669b5c8ed9191f7a0a 100644
--- a/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb
+++ b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb
@@ -3,7 +3,7 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration
 
   DOWNTIME = false
 
-  def change
+  def up
     unless column_exists?(:issues, :time_estimate)
       add_column :issues, :time_estimate, :integer
     end
@@ -12,4 +12,14 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration
       add_column :merge_requests, :time_estimate, :integer
     end
   end
+
+  def down
+    if column_exists?(:issues, :time_estimate)
+      remove_column :issues, :time_estimate
+    end
+
+    if column_exists?(:merge_requests, :time_estimate)
+      remove_column :merge_requests, :time_estimate
+    end
+  end
 end
diff --git a/db/migrate/20161223034646_create_timelogs_ce.rb b/db/migrate/20161223034646_create_timelogs_ce.rb
index e8a4b4060120d54a75efb963ed96e8a2b61991dd..66d9cd823fb646f1d338e76b290c3de34101453a 100644
--- a/db/migrate/20161223034646_create_timelogs_ce.rb
+++ b/db/migrate/20161223034646_create_timelogs_ce.rb
@@ -3,7 +3,7 @@ class CreateTimelogsCe < ActiveRecord::Migration
 
   DOWNTIME = false
 
-  def change
+  def up
     unless table_exists?(:timelogs)
       create_table :timelogs do |t|
         t.integer :time_spent, null: false
@@ -17,4 +17,8 @@ class CreateTimelogsCe < ActiveRecord::Migration
       add_index :timelogs, :user_id
     end
   end
+
+  def down
+    drop_table :timelogs if table_exists?(:timelogs)
+  end
 end
diff --git a/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb b/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4ea953f2b78ad382610cbc221514453950ff97b8
--- /dev/null
+++ b/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb
@@ -0,0 +1,17 @@
+class AddIndexToCiBuildsForStatusRunnerIdAndType < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :ci_builds, [:status, :type, :runner_id]
+  end
+
+  def down
+    if index_exists?(:ci_builds, [:status, :type, :runner_id])
+      remove_index :ci_builds, column: [:status, :type, :runner_id]
+    end
+  end
+end
diff --git a/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb b/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb
new file mode 100644
index 0000000000000000000000000000000000000000..620befcf4d77cd9f29769d89ee581b21ebe1a633
--- /dev/null
+++ b/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb
@@ -0,0 +1,17 @@
+class AddIndexToCiRunnersForIsShared < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :ci_runners, :is_shared
+  end
+
+  def down
+    if index_exists?(:ci_runners, :is_shared)
+      remove_index :ci_runners, :is_shared
+    end
+  end
+end
diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e9a0aee4d6a62d2828b02e07a044b9ce8daff01b
--- /dev/null
+++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb
@@ -0,0 +1,11 @@
+class AddIndexToProjectAuthorizations < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index(:project_authorizations, :project_id)
+  end
+end
diff --git a/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb b/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f399950bd5e4981e214b31bc0068b922dccc6d82
--- /dev/null
+++ b/db/post_migrate/20170104150317_requeue_pending_delete_projects.rb
@@ -0,0 +1,49 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RequeuePendingDeleteProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    admin = User.find_by(admin: true)
+    return unless admin
+
+    @offset = 0
+
+    loop do
+      ids = pending_delete_batch
+
+      break if ids.rows.count.zero?
+
+      args = ids.map { |id| [id['id'], admin.id, {}] }
+
+      Sidekiq::Client.push_bulk('class' => "ProjectDestroyWorker", 'args' => args)
+
+      @offset += 1
+    end
+  end
+
+  def down
+    # noop
+  end
+
+  private
+
+  def pending_delete_batch
+    connection.exec_query(find_batch)
+  end
+
+  BATCH_SIZE = 5000
+
+  def find_batch
+    projects = Arel::Table.new(:projects)
+    projects.project(projects[:id]).
+      where(projects[:pending_delete].eq(true)).
+      where(projects[:namespace_id].not_eq(nil)).
+      skip(@offset * BATCH_SIZE).
+      take(BATCH_SIZE).
+      to_sql
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7815392c1c3b84fb785ac5b182ba66e2ede806a8..c73c311ccb28a4d8cc139bb297bfb65e917d8104 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: 20170106172224) do
+ActiveRecord::Schema.define(version: 20170130204620) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170106172224) do
     t.datetime "queued_at"
     t.string "token"
     t.integer "lock_version"
+    t.string "coverage_regex"
   end
 
   add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -224,6 +225,7 @@ ActiveRecord::Schema.define(version: 20170106172224) do
   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
+  add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
   add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
   add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
 
@@ -327,6 +329,7 @@ ActiveRecord::Schema.define(version: 20170106172224) do
     t.boolean "locked", default: false, null: false
   end
 
+  add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree
   add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
   add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
 
@@ -872,6 +875,7 @@ ActiveRecord::Schema.define(version: 20170106172224) do
     t.integer "access_level"
   end
 
+  add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
   add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
 
   create_table "project_features", force: :cascade do |t|
diff --git a/doc/README.md b/doc/README.md
index 43261ef7457af24c3dd8c5837f2258257b40d288..909740211a65c8dd6394cd113ea51a3adabf93c8 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -6,7 +6,7 @@
 
 ## User documentation
 
-- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc.
+- [Account Security](user/profile/account/two_factor_authentication.md) Securing your account via two-factor authentication, etc.
 - [API](api/README.md) Automate GitLab via a simple and powerful API.
 - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
 - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
@@ -44,7 +44,7 @@
 - [Operations](administration/operations.md) Keeping GitLab up and running.
 - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
 - [Repository checks](administration/repository_checks.md) Periodic Git repository checks.
-- [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories.
+- [Repository storage paths](administration/repository_storage_paths.md) Manage the paths used to store repositories.
 - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
 - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
 - [Update](update/README.md) Update guides to upgrade your installation.
@@ -53,14 +53,14 @@
 - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
 - [Git LFS configuration](workflow/lfs/lfs_administration.md)
 - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
-- [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
+- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
+- [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics.
 - [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
 - [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
 - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
 - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
 - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
 - [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
-- [Multiple mountpoints for the repositories storage](administration/repository_storages.md) Define multiple repository storage paths to distribute the storage load.
 
 ## Contributor documentation
 
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 047233652770f7dc2d43ff3492a206c64f210247..f6027b2f99eb1cd7455c8640edc480c3805b609e 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -65,11 +65,15 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
   #
   # Example: 'Paris' or 'Acme, Ltd.'
   label: 'LDAP'
-
+  
+  # Example: 'ldap.mydomain.com'
   host: '_your_ldap_server'
+  # This port is an example, it is sometimes different but it is always an integer and not a string
   port: 389
   uid: 'sAMAccountName'
   method: 'plain' # "tls" or "ssl" or "plain"
+  
+  # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
   bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
   password: '_the_password_of_the_bind_user'
 
@@ -101,7 +105,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
 
   # Base where we can search for users
   #
-  #   Ex. ou=People,dc=gitlab,dc=example
+  #   Ex. 'ou=People,dc=gitlab,dc=example' or 'DC=mydomain,DC=com'
   #
   base: ''
 
@@ -112,6 +116,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
   #
   #   Note: GitLab does not support omniauth-ldap's custom filter syntax.
   #
+  #   Below an example for get only specific users
+  #   Example: '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+  #
   user_filter: ''
 
   # LDAP attributes that GitLab will use to create an account for the LDAP user.
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index d7cfb464f743bb3d7baf05ba03955a99588e93b0..a6300e18dc0b285a66bce1e03f90a88e3854916c 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -379,6 +379,10 @@ Read more about the individual driver's config options in the
 filesystem. Remember to enable backups with your object storage provider if
 desired.
 
+> **Important** Enabling storage driver other than `filesystem` would mean
+that your Docker client needs to be able to access the storage backend directly.
+So you must use an address that resolves and is accessible outside GitLab server.
+
 ---
 
 **Omnibus GitLab installations**
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index e5cf592e0a660757847379006028235dbec58223..6515b1a264a6d84d94af00b1391d3d308046a698 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -3,8 +3,8 @@
 > [Introduced][ce-7810] in GitLab 8.16.
 
 When [PlantUML](http://plantuml.com) integration is enabled and configured in
-GitLab we are able to create simple diagrams in AsciiDoc documents created in
-snippets, wikis, and repos.
+GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents
+created in snippets, wikis, and repos.
 
 ## PlantUML Server
 
@@ -54,7 +54,7 @@ that, login with an Admin account and do following:
 ## Creating Diagrams
 
 With PlantUML integration enabled and configured, we can start adding diagrams to
-our AsciiDoc snippets, wikis and repos using blocks:
+our AsciiDoc snippets, wikis and repos using delimited blocks:
 
 ```
 [plantuml, format="png", id="myDiagram", width="200px"]
@@ -64,7 +64,14 @@ Alice -> Bob : Go Away
 --
 ```
 
-The above block will be converted to an HTML img tag with source pointing to the
+And in Markdown using fenced code blocks:
+
+    ```plantuml
+    Bob -> Alice : hello
+    Alice -> Bob : Go Away
+    ```
+
+The above blocks will be converted to an HTML img tag with source pointing to the
 PlantUML instance. If the PlantUML server is correctly configured, this should
 render a nice diagram instead of the block:
 
@@ -77,7 +84,7 @@ Inside the block you can add any of the supported diagrams by PlantUML such as
 and [Object](http://plantuml.com/object-diagram) diagrams. You do not need to use the PlantUML
 diagram delimiters `@startuml`/`@enduml` as these are replaced by the AsciiDoc `plantuml` block.
 
-Some parameters can be added to the block definition:
+Some parameters can be added to the AsciiDoc block definition:
 
  - *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
    all browsers so use with care. The default is `png`.
@@ -85,3 +92,4 @@ Some parameters can be added to the block definition:
  - *width*: Width attribute added to the img tag.
  - *height*: Height attribute added to the img tag.
 
+Markdown does not support any parameters and will always use PNG format.
diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md
index 79904916b7e2bdb08719bf1d3186e0cc337f6e6f..8b106e89cc23b3cb15519ad0ef832f805a4064e0 100644
--- a/doc/administration/monitoring/performance/introduction.md
+++ b/doc/administration/monitoring/performance/introduction.md
@@ -12,6 +12,11 @@ documents in order to understand and properly configure GitLab Performance Monit
 - [InfluxDB Schema](influxdb_schema.md)
 - [Grafana Install/Configuration](grafana_configuration.md)
 
+>**Note:**
+Omnibus GitLab 8.16 includes Prometheus as an additional tool to collect
+metrics. It will eventually replace InfluxDB when their metrics collection is
+on par. Read more in the [Prometheus documentation](prometheus.md).
+
 ## Introduction to GitLab Performance Monitoring
 
 GitLab Performance Monitoring makes it possible to measure a wide variety of statistics
diff --git a/doc/administration/monitoring/performance/prometheus.md b/doc/administration/monitoring/performance/prometheus.md
new file mode 100644
index 0000000000000000000000000000000000000000..51c63325064c16f20fe4c635164f6a476ead7fe5
--- /dev/null
+++ b/doc/administration/monitoring/performance/prometheus.md
@@ -0,0 +1,102 @@
+# GitLab Prometheus
+
+>**Notes:**
+- Prometheus and the node exporter are bundled in the Omnibus GitLab package
+  since GitLab 8.16. For installations from source you will have to install
+  them yourself. Over subsequent releases additional GitLab metrics will be
+  captured.
+- Prometheus services are off by default but will be on starting with GitLab 9.0.
+
+[Prometheus] is a powerful time-series monitoring service, providing a flexible
+platform for monitoring GitLab and other software products.
+GitLab provides out of the box monitoring with Prometheus, providing easy
+access to high quality time-series monitoring of GitLab services.
+
+## Overview
+
+Prometheus works by periodically connecting to data sources and collecting their
+performance metrics. To view and work with the monitoring data, you can either
+connect directly to Prometheus or utilize a dashboard tool like [Grafana].
+
+## Configuring Prometheus
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+To enable Prometheus:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Find and uncomment the following line, making sure it's set to `true`:
+
+    ```ruby
+    prometheus['enable'] = true
+    ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+   take effect
+
+By default, Prometheus will run as the `gitlab-prometheus` user and listen on
+TCP port `9090` under localhost. If the [node exporter](#node-exporter) service
+has been enabled, it will automatically be set up as a monitoring target for
+Prometheus.
+
+## Viewing Performance Metrics
+
+After you have [enabled Prometheus](#configuring-prometheus), you can visit
+`<your_domain_name>:9090` for the dashboard that Prometheus offers by default.
+
+The performance data collected by Prometheus can be viewed directly in the
+Prometheus console or through a compatible dashboard tool.
+The Prometheus interface provides a [flexible query language][prom-query] to work
+with the collected data where you can visualize their output.
+For a more fully featured dashboard, Grafana can be used and has
+[official support for Prometheus][prom-grafana].
+
+## Prometheus exporters
+
+There are a number of libraries and servers which help in exporting existing
+metrics from third-party systems as Prometheus metrics. This is useful for cases
+where it is not feasible to instrument a given system with Prometheus metrics
+directly (for example, HAProxy or Linux system stats). You can read more in the
+[Prometheus exporters and integrations documentation][prom-exporters].
+
+While you can use any exporter you like with your GitLab installation, the
+following ones documented here are bundled in the Omnibus GitLab packages
+making it easy to configure and use.
+
+### Node exporter
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+The [node exporter] allows you to measure various machine resources such as
+memory, disk and CPU utilization.
+
+To enable the node exporter:
+
+1. [Enable Prometheus](#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Find and uncomment the following line, making sure it's set to `true`:
+
+    ```ruby
+    node_exporter['enable'] = true
+    ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+   take effect
+
+Prometheus it will now automatically begin collecting performance data from
+the node exporter. You can visit `<your_domain_name>:9100/metrics` for a real
+time representation of the metrics that are collected. Refresh the page and
+you will see the data change.
+
+[grafana]: https://grafana.net
+[node exporter]: https://github.com/prometheus/node_exporter
+[prometheus]: https://prometheus.io
+[prom-query]: https://prometheus.io/docs/querying/basics
+[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
+[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
+[prom-exporters]: https://prometheus.io/docs/instrumenting/exporters/
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index f3c2e72341f69db88b044a800671260a92381837..5b6ee354887a6f07e4ac7718ccbbb43b60ca2df8 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -27,6 +27,7 @@ Ruby Version:     2.1.5p273
 Gem Version:      2.4.3
 Bundler Version:  1.7.6
 Rake Version:     10.3.2
+Redis Version:    3.2.5
 Sidekiq Version:  2.17.8
 
 GitLab information
@@ -171,14 +172,14 @@ Omnibus packages.
 
 ```
 cd /home/git/gitlab
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production
 ```
 
 For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
 the release of upstream GitLab. The omnibus version includes optimized versions
 of those assets. Unless you are modifying the JavaScript / CSS code on your
 production machine after installing the package, there should be no reason to redo
-rake assets:precompile on the production machine. If you suspect that assets
+rake gitlab:assets:compile on the production machine. If you suspect that assets
 have been corrupted, you should reinstall the omnibus package.
 
 ## Tracking Deployments
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index bc2b1f20ed30de3beaddcf8ccbd8c2c432c4b23e..ee37ea498743f18a92bec427b1a77272fee2a0e1 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -13,12 +13,12 @@ checks failed you can see their output on the admin log page under
 
 ## Periodic checks
 
-GitLab periodically runs a repository check on all project repositories and
-wiki repositories in order to detect data corruption problems. A
-project will be checked no more than once per week. If any projects
+When enabled, GitLab periodically runs a repository check on all project 
+repositories and wiki repositories in order to detect data corruption problems. 
+A project will be checked no more than once per month. If any projects
 fail their repository checks all GitLab administrators will receive an email
-notification of the situation. This notification is sent out no more
-than once a day.
+notification of the situation. This notification is sent out once a week on
+Sunday, by default. 
 
 ## Disabling periodic checks
 
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
new file mode 100644
index 0000000000000000000000000000000000000000..d6aa610102627497e70787098be5974274968f17
--- /dev/null
+++ b/doc/administration/repository_storage_paths.md
@@ -0,0 +1,102 @@
+# Repository storage paths
+
+> [Introduced][ce-4578] in GitLab 8.10.
+
+GitLab allows you to define multiple repository storage paths to distribute the
+storage load between several mount points.
+
+>**Notes:**
+>
+- You must have at least one storage path called `default`.
+- The paths are defined in key-value pairs. The key is an arbitrary name you
+  can pick to name the file path.
+- The target directories and any of its subpaths must not be a symlink.
+
+## Configure GitLab
+
+>**Warning:**
+In order for [backups] to work correctly, the storage path must **not** be a
+mount point and the GitLab user should have correct permissions for the parent
+directory of the path. In Omnibus GitLab this is taken care of automatically,
+but for source installations you should be extra careful.
+>
+The thing is that for compatibility reasons `gitlab.yml` has a different
+structure than Omnibus. In `gitlab.yml` you indicate the path for the
+repositories, for example `/home/git/repositories`, while in Omnibus you
+indicate `git_data_dirs`, which for the example above would be `/home/git`.
+Then, Omnibus will create a `repositories` directory under that path to use with
+`gitlab.yml`.
+>
+This little detail matters because while restoring a backup, the current
+contents of  `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`,
+so if `/home/git/repositories` is the mount point, then `mv` would be moving
+things between mount points, and bad things could happen. Ideally,
+`/home/git` would be the mount point, so then things would be moving within the
+same mount point. This is guaranteed with Omnibus installations (because they
+don't specify the full repository path but the parent path), but not for source
+installations.
+
+---
+
+Now that you've read that big fat warning above, let's edit the configuration
+files and add the full paths of the alternative repository storage paths. In
+the example below, we add two more mountpoints that are named `nfs` and `cephfs`
+respectively.
+
+**For installations from source**
+
+1. Edit `gitlab.yml` and add the storage paths:
+
+    ```yaml
+    repositories:
+      # Paths where repositories can be stored. Give the canonicalized absolute pathname.
+      # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
+      storages: # You must have at least a 'default' storage path.
+        default: /home/git/repositories
+        nfs: /mnt/nfs/repositories
+        cephfs: /mnt/cephfs/repositories
+    ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+>**Note:**
+The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be
+deprecated and replaced by `repositories: storages` in the future, so if you
+are upgrading from a version prior to 8.10, make sure to add the configuration
+as described in the step above. After you make the changes and confirm they are
+working, you can remove the `repos_path` line.
+
+---
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the
+   default one:
+
+    ```ruby
+    git_data_dirs({
+      "default" => "/var/opt/gitlab/git-data",
+      "nfs" => "/mnt/nfs/git-data",
+      "cephfs" => "/mnt/cephfs/git-data"
+    })
+    ```
+
+    Note that Omnibus stores the repositories in a `repositories` subdirectory
+    of the `git-data` directory.
+
+## Choose where new project repositories will be stored
+
+Once you set the multiple storage paths, you can choose where new projects will
+be stored via the **Application Settings** in the Admin area.
+
+![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
+
+Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
+randomly placed on one of the selected paths.
+
+[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
+[restart gitlab]: restart_gitlab.md#installations-from-source
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[backups]: ../raketasks/backup_restore.md
+[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56
+[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457
diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md
index ab70557b69adc7f3cbd7cc12a3551102615f5d5c..9d41ba77f348179472775e7bf66e2dde88688c0a 100644
--- a/doc/administration/repository_storages.md
+++ b/doc/administration/repository_storages.md
@@ -1,102 +1,3 @@
 # Repository storages
 
-> [Introduced][ce-4578] in GitLab 8.10.
-
-GitLab allows you to define multiple repository storage paths to distribute the
-storage load between several mount points.
-
->**Notes:**
->
-- You must have at least one storage path called `default`.
-- The paths are defined in key-value pairs. The key is an arbitrary name you
-  can pick to name the file path.
-- The target directories and any of its subpaths must not be a symlink.
-
-## Configure GitLab
-
->**Warning:**
-In order for [backups] to work correctly, the storage path must **not** be a
-mount point and the GitLab user should have correct permissions for the parent
-directory of the path. In Omnibus GitLab this is taken care of automatically,
-but for source installations you should be extra careful.
->
-The thing is that for compatibility reasons `gitlab.yml` has a different
-structure than Omnibus. In `gitlab.yml` you indicate the path for the
-repositories, for example `/home/git/repositories`, while in Omnibus you
-indicate `git_data_dirs`, which for the example above would be `/home/git`.
-Then, Omnibus will create a `repositories` directory under that path to use with
-`gitlab.yml`.
->
-This little detail matters because while restoring a backup, the current
-contents of  `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`,
-so if `/home/git/repositories` is the mount point, then `mv` would be moving
-things between mount points, and bad things could happen. Ideally,
-`/home/git` would be the mount point, so then things would be moving within the
-same mount point. This is guaranteed with Omnibus installations (because they
-don't specify the full repository path but the parent path), but not for source
-installations.
-
----
-
-Now that you've read that big fat warning above, let's edit the configuration
-files and add the full paths of the alternative repository storage paths. In
-the example below, we add two more mountpoints that are named `nfs` and `cephfs`
-respectively.
-
-**For installations from source**
-
-1. Edit `gitlab.yml` and add the storage paths:
-
-    ```yaml
-    repositories:
-      # Paths where repositories can be stored. Give the canonicalized absolute pathname.
-      # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
-      storages: # You must have at least a 'default' storage path.
-        default: /home/git/repositories
-        nfs: /mnt/nfs/repositories
-        cephfs: /mnt/cephfs/repositories
-    ```
-
-1. [Restart GitLab] for the changes to take effect.
-
->**Note:**
-The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be
-deprecated and replaced by `repositories: storages` in the future, so if you
-are upgrading from a version prior to 8.10, make sure to add the configuration
-as described in the step above. After you make the changes and confirm they are
-working, you can remove the `repos_path` line.
-
----
-
-**For Omnibus installations**
-
-1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the
-   default one:
-
-    ```ruby
-    git_data_dirs({
-      "default" => "/var/opt/gitlab/git-data",
-      "nfs" => "/mnt/nfs/git-data",
-      "cephfs" => "/mnt/cephfs/git-data"
-    })
-    ```
-
-    Note that Omnibus stores the repositories in a `repositories` subdirectory
-    of the `git-data` directory.
-
-## Choose where new project repositories will be stored
-
-Once you set the multiple storage paths, you can choose where new projects will
-be stored via the **Application Settings** in the Admin area.
-
-![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
-
-Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
-randomly placed on one of the selected paths.
-
-[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
-[restart gitlab]: restart_gitlab.md#installations-from-source
-[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
-[backups]: ../raketasks/backup_restore.md
-[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56
-[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457
+This document was moved to a [new location](repository_storage_paths.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 5c11d0f83bbd6f78cabcb8380438738a65c28597..53ce381c8ae14f7249fc9ea76116dfefd3b18dab 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -245,7 +245,7 @@ Example response:
 ```json
 [
   {
-    "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
+    "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
     "new_path": "doc/update/5.4-to-6.0.md",
     "old_path": "doc/update/5.4-to-6.0.md",
     "a_mode": null,
diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md
index 1299aca8c45c7f499930034ec5578c106f188d7e..e0ee20d96108d90fbd30b348a99a27a7b3924d25 100644
--- a/doc/api/enviroments.md
+++ b/doc/api/enviroments.md
@@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id
 | `external_url`  | string  | no                                | The new external_url             |
 
 ```bash
-curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1"
 ```
 
 Example response:
@@ -106,7 +106,7 @@ DELETE /projects/:id/environments/:environment_id
 | `environment_id` | integer | yes | The ID of the environment |
 
 ```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1"
 ```
 
 Example response:
diff --git a/doc/api/groups.md b/doc/api/groups.md
index f7807390e6862f0b67b0acf6eed2d66a82a25296..3b38e3e1bee2816c2e4ade39ff22de6dcfe9a54c 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -25,7 +25,14 @@ GET /groups
     "id": 1,
     "name": "Foobar Group",
     "path": "foo-bar",
-    "description": "An interesting group"
+    "description": "An interesting group",
+    "visibility_level": 20,
+    "lfs_enabled": true,
+    "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg",
+    "web_url": "http://localhost:3000/groups/foo-bar",
+    "request_access_enabled": false,
+    "full_name": "Foobar Group",
+    "full_path": "foo-bar"
   }
 ]
 ```
@@ -149,6 +156,8 @@ Example response:
   "avatar_url": null,
   "web_url": "https://gitlab.example.com/groups/twitter",
   "request_access_enabled": false,
+  "full_name": "Foobar Group",
+  "full_path": "foo-bar",
   "projects": [
     {
       "id": 7,
@@ -372,6 +381,8 @@ Example response:
   "avatar_url": null,
   "web_url": "http://gitlab.example.com/groups/h5bp",
   "request_access_enabled": false,
+  "full_name": "Foobar Group",
+  "full_path": "foo-bar",
   "projects": [
     {
       "id": 9,
diff --git a/doc/api/users.md b/doc/api/users.md
index 28b6c7bd491cb9881b2f091cc5e2c8f575f0072f..fea9bdf9639dd37da8112eb89f7ed95ba7c1741d 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -271,6 +271,7 @@ Parameters:
 - `can_create_group` (optional) - User can create groups - true or false
 - `external` (optional)         - Flags the user as external - true or false(default)
 
+On password update, user will be forced to change it upon next login.
 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.
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 630207ffa09a4f0b6df542343dcaab2e5a21053f..c4c4d95b68a25e57cc3e3a930ec84aa924de621c 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -1,6 +1,6 @@
 # Auto deploy
 
-> [Introduced][mr-8135] in GitLab 8.15.
+> [Introduced][mr-8135] in GitLab 8.15. Currently requires a [Public project][project-settings].
 
 Auto deploy is an easy way to configure GitLab CI for the deployment of your
 application. GitLab Community maintains a list of `.gitlab-ci.yml`
@@ -33,6 +33,7 @@ enable [Kubernetes service][kubernetes-service].
 created automatically for you.
 
 [mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135
+[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html
 [project-services]: ../../project_services/project_services.md
 [auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
 [kubernetes-service]: ../../project_services/kubernetes.md
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 98cd29c956786c46e06efc83da4a447cb161388a..ef04c53736743427ae7391ad776b29ee0b5969ee 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -297,7 +297,7 @@ deploy_review:
     - echo "Deploy a review app"
   environment:
     name: review/$CI_BUILD_REF_NAME
-    url: https://$CI_BUILD_REF_SLUG.review.example.com
+    url: https://$CI_ENVIRONMENT_SLUG.example.com
   only:
     - branches
   except:
@@ -318,15 +318,15 @@ also contain `/`, or other characters that would be invalid in a domain name or
 URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the
 environment can get a specific and distinct URL for each branch. In this case,
 given a `$CI_BUILD_REF_NAME` of `100-Do-The-Thing`, the URL will be something
-like `https://review-100-do-the-4f99a2.example.com`. Again, the way you set up
+like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
 the web server to serve these requests is based on your setup.
 
 You could also use `$CI_BUILD_REF_SLUG` in `environment:url`, e.g.:
-`https://$CI_BUILD_REF_SLUG.review.example.com`. We use `$CI_ENVIRONMENT_SLUG`
+`https://$CI_BUILD_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
 here because it is guaranteed to be unique, but if you're using a workflow like
 [GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer
 environment names to be more closely based on the branch name - the example
-above would give you an URL like `https://100-do-the-thing.review.example.com`
+above would give you an URL like `https://100-do-the-thing.example.com`
 
 Last but not least, we tell the job to run [`only`][only] on branches
 [`except`][only] master.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index ffc310ec8c719092778dd3b1544f8afbabeb8a7b..5377bf9ee80dcf345e47973e3747d585c5a89475 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -14,6 +14,12 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
 - [Test a Phoenix application](test-phoenix-application.md)
 - [Using `dpl` as deployment tool](deployment/README.md)
 - [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/)
+- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
+- Help your favorite programming language and GitLab by sending a merge request
+  with a guide for that language.
+
+## Outside the documentation
+
 - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
 - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
 - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
new file mode 100644
index 0000000000000000000000000000000000000000..5334a73e1f583a1d9007b1327b5b9c4124d11a8d
--- /dev/null
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -0,0 +1,156 @@
+## Running Composer and NPM scripts with deployment via SCP
+
+This guide covers the building dependencies of a PHP project while compiling assets via an NPM script.
+
+While is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed.
+
+
+```yaml
+image: tetraweb/php
+```
+
+The next step is to install zip/unzip packages and make composer available. We will place these in the `before_script` section:
+
+```yaml
+before_script:
+  - apt-get update
+  - apt-get install zip unzip
+  - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+  - php composer-setup.php
+  - php -r "unlink('composer-setup.php');"
+```
+
+This will make sure we have all requirements ready. Next, we want to run `composer update` to fetch all PHP dependencies  and `npm install` to load node packages, then run the `npm` script. We need to append them  into `before_script` section:
+
+```yaml
+before_script:
+  # ...
+  - php composer.phar update
+  - npm install
+  - npm run deploy
+```
+
+In this particular case, the `npm deploy` script is a Gulp script that does the following:
+
+1. Compile CSS & JS
+2. Create sprites
+3. Copy various assets (images, fonts) around
+4. Replace some strings
+
+All these operations will put all files into a `build` folder, which is ready to be deployed to a live server.
+
+### How to transfer files to a live server?
+
+You have multiple options: rsync, scp, sftp and so on. For now, we will use scp.
+
+To make this work, you need to add a GitLab Secret Variable (accessible on _gitlab.example/your-project-name/variables_). That variable will be called `STAGING_PRIVATE_KEY` and it's the  **private** ssh key of your server.
+
+#### Security tip
+
+Create a user that has access **only** to the folder that needs to be updated!
+
+After you create that variable, you need to make sure that key will be added to the docker container on run:
+
+```yaml
+before_script:
+  # - ....
+  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+  - mkdir -p ~/.ssh
+  - eval $(ssh-agent -s)
+  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+```
+
+In order, this means that:
+
+1. We check if the `ssh-agent` is available and we install it if it's not;
+2. We create the `~/.ssh` folder;
+3. We make sure we're running bash;
+4. We disable host checking (we don't ask for user accept when we first connect to a server; and since every build will equal a first connect, we kind of need this)
+
+And this is basically all you need in the `before_script` section.
+
+## How to deploy things?
+
+As we stated above, we need to deploy the `build` folder from the docker image to our server. To do so, we create a new job:
+
+```yaml
+stage_deploy:
+  artifacts:
+    paths:
+      - build/
+  only:
+    - dev
+  script:
+    - ssh-add <(echo "$STAGING_PRIVATE_KEY")
+    - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp"
+    - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp
+    - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live"
+    - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old"
+```
+
+### What's going on here?
+
+1. `only:dev` means that this build will run only when something is pushed to the `dev` branch. You can remove this block completely and have everything be ran on every push (but probably this is something you don't want)
+2. `ssh-add ...` we will add that private key you added on the web UI to the docker container
+3. We will connect via `ssh` and create a new `_tmp` folder
+4. We will connect via `scp` and upload the `build` folder (which was generated by a `npm` script) to our previously created `_tmp` folder
+5. We will connect again to `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`.
+6. We connect to ssh and remove the `_old` folder
+
+What's the deal with the artifacts? We just tell GitLab CI to keep the `build` directory (later on, you can download that as needed).
+
+#### Why we do it this way?
+
+If you're using this only for stage server, you could do this in two steps:
+
+```yaml
+- ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/live/*"
+- scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/live
+```
+
+The problem is that there will be a small period of time when you won't have the app on your server.
+
+So we use so many steps because we want to make sure that at any given time we have a functional app in place.
+
+## Where to go next?
+
+Since this was a WordPress project, I gave real life code snippets. Some ideas you can pursuit:
+
+- Having a slightly different script for `master` branch will allow you to deploy to a production server from that branch and to a stage server from any other branches;
+- Instead of pushing it live, you can push it to WordPress official repo (with creating a SVN commit & stuff);
+- You could generate i18n text domains on the fly.
+
+---
+
+Our final `.gitlab-ci.yml` will look like this:
+
+```yaml
+image: tetraweb/php
+
+before_script:
+  - apt-get update
+  - apt-get install zip unzip
+  - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+  - php composer-setup.php
+  - php -r "unlink('composer-setup.php');"
+  - php composer.phar update
+  - npm install
+  - npm run deploy
+  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+  - mkdir -p ~/.ssh
+  - eval $(ssh-agent -s)
+  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+
+stage_deploy:
+  artifacts:
+    paths:
+    - build/
+  only:
+    - dev
+  script:
+    - ssh-add <(echo "$STAGING_PRIVATE_KEY")
+    - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp"
+    - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp
+    - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live"
+    - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old"
+```
\ No newline at end of file
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 82ffb84172977193bf85d06e51de8d0131e7b48b..5eeec92d976cfbcca54760c3a2bdadc12070cc6b 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -235,7 +235,11 @@ cache:
 
 before_script:
 # Install composer dependencies
-- curl --silent --show-error https://getcomposer.org/installer | php
+- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
+- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+- php composer-setup.php
+- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
 - php composer.phar install
 
 ...
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 75a0897eb15de087c08ea84a13d7cff596ab8cef..06810898cfef10cdb898dc05abeea487d24c82ed 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
 | after_script  | no | Define commands that run after each job's script |
 | variables     | no | Define build variables |
 | cache         | no | Define list of files that should be cached between subsequent runs |
+| coverage      | no | Define coverage settings for all jobs |
 
 ### image and services
 
@@ -86,7 +87,7 @@ used for time of the build. The configuration of this feature is covered in
 ### before_script
 
 `before_script` is used to define the command that should be run before all
-builds, including deploy builds. This can be an array or a multi-line string.
+builds, including deploy builds, but after the restoration of artifacts. This can be an array or a multi-line string.
 
 ### after_script
 
@@ -278,6 +279,23 @@ cache:
   untracked: true
 ```
 
+### coverage
+
+`coverage` allows you to configure how coverage will be filtered out from the
+build outputs. Setting this up globally will make all the jobs to use this
+setting for output filtering and extracting the coverage information from your
+builds.
+
+Regular expressions are the only valid kind of value expected here. So, using
+surrounding `/` is mandatory in order to consistently and explicitly represent
+a regular expression string. You must escape special characters if you want to
+match them literally.
+
+A simple example:
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+```
+
 ## Jobs
 
 `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
@@ -319,6 +337,7 @@ job_name:
 | before_script | no | Override a set of commands that are executed before build |
 | after_script  | no | Override a set of commands that are executed after build |
 | environment   | no | Defines a name of environment to which deployment is done by this build |
+| coverage      | no | Define coverage settings for a given job |
 
 ### script
 
@@ -993,6 +1012,25 @@ job:
   - execute this after my script
 ```
 
+### job coverage
+
+This entry is pretty much the same as described in the global context in
+[`coverage`](#coverage). The only difference is that, by setting it inside
+the job level, whatever is set in there will take precedence over what has
+been defined in the global level. A quick example of one overriding the
+other would be:
+
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+
+job1:
+  coverage: /Code coverage: \d+\.\d+/
+```
+
+In the example above, considering the context of the job `job1`, the coverage
+regex that would be used is `/Code coverage: \d+\.\d+/` instead of
+`/\(\d+\.\d+\) covered\./`.
+
 ## Git Strategy
 
 > Introduced in GitLab 8.9 as an experimental feature.  May change or be removed
diff --git a/doc/development/README.md b/doc/development/README.md
index 6f2ca7b8590d2b3673064454e2ca6528efd50dd5..265df98fb873cc5fdf98106c0ac34c83d56e19fe 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -18,6 +18,7 @@
 - [Frontend guidelines](frontend.md)
 - [SQL guidelines](sql.md) for working with SQL queries
 - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
+- [`Gemfile` guidelines](gemfile.md)
 
 ## Process
 
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 1ef34c79971b9629be492caeb1515a52fdedd029..e4a0e0b92bc43be875f23cb38b90297280dafb87 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -9,7 +9,7 @@ code is effective, understandable, and maintainable.
 
 Any developer can, and is encouraged to, perform code review on merge requests
 of colleagues and contributors. However, the final decision to accept a merge
-request is up to one of our merge request "endbosses", denoted on the
+request is up to one the project's maintainers, denoted on the
 [team page](https://about.gitlab.com/team).
 
 ## Everyone
@@ -81,15 +81,15 @@ 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.
+  reviewers that become maintainers 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.
+  of the contributed code. It's usually a good idea to ask another maintainer or
+  reviewer 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
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index f79bd23dc90e1a409127fadd46df934b776cdec7..75fdf3d8e63ce20594ee6661c4cfd4d9c5a78856 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -23,6 +23,69 @@ some ideas with React.js as well as Angular.
 
 To get started with Vue, read through [their documentation][vue-docs].
 
+#### How to build a new feature with Vue.js
+**Components, Stores and Services**
+
+In some features implemented with Vue.js, like the [issue board][issue-boards]
+or [environments table][environments-table]
+you can find a clear separation of concerns:
+
+```
+new_feature
+├── components
+│   └── component.js.es6
+│   └── ...
+├── store
+│  └── new_feature_store.js.es6
+├── service
+│  └── new_feature_service.js.es6
+├── new_feature_bundle.js.es6
+```
+_For consistency purposes, we recommend you to follow the same structure._
+
+Let's look into each of them:
+
+**A `*_bundle.js` file**
+
+This is the index file of your new feature. This is where the root Vue instance
+of the new feature should be.
+
+Don't forget to follow [these steps.][page-specific-javascript]
+
+**A folder for Components**
+
+This folder holds all components that are specific of this new feature.
+If you need to use or create a component that will probably be used somewhere
+else, please refer to `vue_shared/components`.
+
+A good thumb rule to know when you should create a component is to think if
+it will be reusable elsewhere.
+
+For example, tables are used in a quite amount of places across GitLab, a table
+would be a good fit for a component.
+On the other hand, a table cell used only in on table, would not be a good use
+of this pattern.
+
+You can read more about components in Vue.js site, [Component System][component-system]
+
+**A folder for the Store**
+
+The Store is a simple object that allows us to manage the state in a single
+source of truth.
+
+The concept we are trying to follow is better explained by Vue documentation
+itself, please read this guide: [State Management][state-management]
+
+**A folder for the Service**
+
+The Service is used only to communicate with the server.
+It does not store or manipulate any data.
+We use [vue-resource][vue-resource-repo] to
+communicate with the server.
+
+The [issue boards service][issue-boards-service]
+is a good example of this pattern.
+
 ## Performance
 
 ### Resources
@@ -198,8 +261,8 @@ As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
 
 If you need to debug your tests and/or application code while they're
 running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon)
-in your browser, open DevTools, and run tests for individual files by clicking 
-on them. This is also much faster than setting up and running tests from the 
+in your browser, open DevTools, and run tests for individual files by clicking
+on them. This is also much faster than setting up and running tests from the
 command line.
 
 Please note: Not all of the frontend fixtures are generated. Some are still static
@@ -294,20 +357,27 @@ For our currently-supported browsers, see our [requirements][requirements].
 [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
 [scss-style-guide]: scss_styleguide.md
 [requirements]: ../install/requirements.md#supported-web-browsers
+[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
+[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
+[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
+[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
+[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
+[vue-resource-repo]: https://github.com/pagekit/vue-resource
+[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
 
 ## Gotchas
 
 ### Spec errors due to use of ES6 features in `.js` files
 
-If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being 
-thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually, 
-you may have included `ES6`-style JavaScript in files that don't have the 
-`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file 
-you're working in (`git mv <file.js> <file.js.es6>`). 
+If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being
+thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually,
+you may have included `ES6`-style JavaScript in files that don't have the
+`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file
+you're working in (`git mv <file.js> <file.js.es6>`).
 
 ### Spec errors due to use of unsupported JavaScript
 
-Similar errors will be thrown if you're using JavaScript features not yet 
+Similar errors will be thrown if you're using JavaScript features not yet
 supported by our test runner's version of webkit, whether or not you've updated
 the file extension. Examples of unsupported JavaScript features are:
 
@@ -322,20 +392,20 @@ the file extension. Examples of unsupported JavaScript features are:
 - Symbol/Symbol.iterator
 - Spread
 
-Until these are polyfilled or transpiled appropriately, they should not be used. 
-Please update this list with additional unsupported features or when any of 
+Until these are polyfilled or transpiled appropriately, they should not be used.
+Please update this list with additional unsupported features or when any of
 these are made usable.
 
 ### Spec errors due to JavaScript not enabled
 
-If, as a result of a change you've made, a feature now depends on JavaScript to 
+If, as a result of a change you've made, a feature now depends on JavaScript to
 run correctly, you need to make sure a JavaScript web driver is enabled when
-specs are run. If you don't you'll see vague error messages from the spec 
-runner, and an explosion of vague console errors in the HTML snapshot. 
+specs are run. If you don't you'll see vague error messages from the spec
+runner, and an explosion of vague console errors in the HTML snapshot.
 
-To enable a JavaScript driver in an `rspec` test, add `js: true` to the 
-individual spec or the context block containing multiple specs that need 
-JavaScript enabled: 
+To enable a JavaScript driver in an `rspec` test, add `js: true` to the
+individual spec or the context block containing multiple specs that need
+JavaScript enabled:
 
 ```ruby
 
@@ -354,8 +424,8 @@ describe "Admin::AbuseReports", js: true do
 end
 ```
 
-In Spinach, the JavaScript driver is enabled differently. In the `*.feature` 
-file for the failing spec, add the `@javascript` flag above the Scenario: 
+In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
+file for the failing spec, add the `@javascript` flag above the Scenario:
 
 ```
 @javascript
diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md
new file mode 100644
index 0000000000000000000000000000000000000000..ec9718cea719309dfee7a5d4c61465fa4818f1d2
--- /dev/null
+++ b/doc/development/gemfile.md
@@ -0,0 +1,14 @@
+# `Gemfile` guidelines
+
+When adding a new entry to `Gemfile` or upgrading an existing dependency pay
+attention to the following rules.
+
+## No gems fetched from git repositories
+
+We do not allow gems that are fetched from git repositories. All gems have
+to be available in the RubyGems index. We want to minimize external build
+dependencies and build times.
+
+## License compliance
+
+Refer to [licensing guidelines](licensing.md) for ensuring license compliance.
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
index 0363bf8c1d54b1f5c3d377e6e38e0b5343ae4dfd..8232a0a113cbdde742186e5265fe6fe1b24b6c31 100644
--- a/doc/development/merge_request_performance_guidelines.md
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -3,7 +3,7 @@
 To ensure a merge request does not negatively impact performance of GitLab
 _every_ merge request **must** adhere to the guidelines outlined in this
 document. There are no exceptions to this rule unless specifically discussed
-with and agreed upon by merge request endbosses and performance specialists.
+with and agreed upon by backend maintainers and performance specialists.
 
 To measure the impact of a merge request you can use
 [Sherlock](profiling.md#sherlock). It's also highly recommended that you read
@@ -40,9 +40,9 @@ section below for more information.
 about the impact.
 
 Sometimes it's hard to assess the impact of a merge request. In this case you
-should ask one of the merge request (mini) endbosses to review your changes. You
-can find a list of these endbosses at <https://about.gitlab.com/team/>. An
-endboss in turn can request a performance specialist to review the changes.
+should ask one of the merge request reviewers to review your changes. You can
+find a list of these reviewers at <https://about.gitlab.com/team/>. A reviewer
+in turn can request a performance specialist to review the changes.
 
 ## Query Counts
 
diff --git a/doc/development/performance.md b/doc/development/performance.md
index f936a49a2aa6210f25f1264b47d7c2a8b6acc934..c1f129e576ce5dbf4802f6e735ecdb1b5482b8f2 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -211,6 +211,41 @@ suite first. See the
 [StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md)
 for details.
 
+## RSpec profiling
+
+GitLab's development environment also includes the
+[rspec_profiling](https://github.com/foraker/rspec_profiling) gem, which is used
+to collect data on spec execution times. This is useful for analyzing the
+performance of the test suite itself, or seeing how the performance of a spec
+may have changed over time.
+
+To activate profiling in your local environment, run the following:
+
+```
+$ export RSPEC_PROFILING=yes
+$ rake rspec_profiling:install
+```
+
+This creates an SQLite3 database in `tmp/rspec_profiling`, into which statistics
+are saved every time you run specs with the `RSPEC_PROFILING` environment
+variable set.
+
+Ad-hoc investigation of the collected results can be performed in an interactive
+shell:
+
+```
+$ rake rspec_profiling:console
+irb(main):001:0> results.count
+=> 231
+irb(main):002:0> results.last.attributes.keys
+=> ["id", "commit", "date", "file", "line_number", "description", "time", "status", "exception", "query_count", "query_time", "request_count", "request_time", "created_at", "updated_at"]
+irb(main):003:0> results.where(status: "passed").average(:time).to_s
+=> "0.211340155844156"
+```
+These results can also be placed into a PostgreSQL database by setting the
+`RSPEC_PROFILING_POSTGRES_URL` variable. This is used to profile the test suite
+when running in the CI environment.
+
 ## Importance of Changes
 
 When working on performance improvements, it's important to always ask yourself
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index 903e54bf9dc1cccf0b5031f906fc55629e998fb6..5dae4bcc905f4fe1a748bb924b5df10ed542fc80 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net](
 
 ### Hover
 
-Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `200ms linear` transition for a color hover effect.
+Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `100ms - 150ms linear` transition for a color hover effect.
 
 View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here.
 
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index 706bb180912306ba4df513961b0ba0a8a6cd07e6..1b19587a0b818aa68fe44287f7d6460d49bae9ca 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -109,7 +109,7 @@ Dropdowns are used to allow users to choose one (or many) options from a list of
 
 ### 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.
+The max height for dropdowns should target **10-15** single line items, or **7-10** multi-line 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.
 
 ---
 
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index 31cc9dd2a534793fbdd6245b3b2b2a7e48bc4acd..5b65d531e545479a9352ab8984a138a232f21f76 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -102,6 +102,12 @@ When using the <kbd>Alt</kbd> keystrokes in Windows, use the numeric keypad, not
 ## Terminology
 Only use the terms in the tables below.
 
+### Projects and Groups
+
+| Term | Use | :no_entry_sign: Don't |
+| ---- | --- | ----- |
+| Members | When discussing the people who are a part of a project or a group. | Don't use `users`. |
+
 ### Issues
 
 #### Adjectives (states)
@@ -117,7 +123,7 @@ Use `5 open issues` and don’t use `5 pending issues`.
 
 #### Verbs (actions)
 
-| Term | Use | Don’t |
+| Term | Use | :no_entry_sign: Don’t |
 | ---- | --- | --- |
 | Add | Add an issue | Don’t use `create` or `new` |
 | View | View an open or closed issue ||
@@ -158,7 +164,7 @@ The form should be titled `Edit issue`. The submit button should be labeled `Sav
 
 #### Verbs (actions)
 
-| Term | Use | Don’t |
+| Term | Use | :no_entry_sign: Don’t |
 | ---- | --- | --- |
 | Add | Add a merge request | Do not use `create` or `new` |
 | View | View an open or merged merge request ||
diff --git a/doc/gitlab-basics/add-image.md b/doc/gitlab-basics/add-image.md
index 476b48a217c26071ead94bae32269f91106b8cb4..1a44123aa8150437ed1266519040858c47f23ef7 100644
--- a/doc/gitlab-basics/add-image.md
+++ b/doc/gitlab-basics/add-image.md
@@ -1,62 +1,56 @@
 # How to add an image
 
-The following are the steps to add images to your repository in
-GitLab:
+Using your standard tool for copying files (e.g. Finder in Mac OS, or Explorer
+in Windows, or...), put the image file into the GitLab project. You can find the
+project as a regular folder in your files.
 
-Find the image that you’d like to add.
+Go to your [shell](command-line-commands.md), and move into the folder of your
+Gitlab project. This usually means running the following command until you get
+to the desired destination:
 
-In your computer files, find the GitLab project to which you'd like to add the image
-(you'll find it as a regular file). Click on every file until you find exactly where you'd
-like to add the image. There, paste the image.
-
-Go to your [shell](command-line-commands.md), and add the following commands:
-
-Add this command for every directory that you'd like to open:
 ```
-cd NAME-OF-FILE-YOU'D-LIKE-TO-OPEN
+cd NAME-OF-FOLDER-YOU'D-LIKE-TO-OPEN
 ```
 
-Create a new branch:
-```
-git checkout -b NAME-OF-BRANCH
-```
+Check if your image is actually present in the directory (if you are in Windows,
+use `dir` instead):
 
-Check if your image was correctly added to the directory:
 ```
 ls
 ```
 
 You should see the name of the image in the list shown.
 
-Move up the hierarchy through directories:
-```
-cd ../
-```
+Check the status:
 
-Check the status and you should see your image’s name in red:
 ```
 git status
 ```
 
-Add your changes:
+Your image's name should appear in red, so `git` took notice of it! Now add it
+to the repository:
+
 ```
 git add NAME-OF-YOUR-IMAGE
 ```
 
-Check the status and you should see your image’s name in green:
+Check the status again, your image's name should have turned green:
+
 ```
 git status
 ```
 
-Add the commit:
+Commit:
+
 ```
-git commit -m “DESCRIBE COMMIT IN A FEW WORDS”
+git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
 ```
 
-Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab (the git remote named 'origin'):
+Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab
+(the git remote named 'origin'):
+
 ```
 git push origin NAME-OF-BRANCH
 ```
 
-Your image will be added to your branch in your repository in GitLab. Create a [Merge Request](add-merge-request.md)
-to integrate your changes to your project.
+Your image will be added to your branch in your repository in GitLab.
diff --git a/doc/install/README.md b/doc/install/README.md
index 239f5f301ec209eb76170ca4ffa54b22d3ac1539..2d2fd8cb38073748189604cc36c8b2014282179e 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -4,3 +4,6 @@
 - [Requirements](requirements.md)
 - [Structure](structure.md)
 - [Database MySQL](database_mysql.md)
+- [Digital Ocean and Docker](digitaloceandocker.md)
+- [Docker](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/docker)
+- [All installation methods](https://about.gitlab.com/installation/)
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 322680f0cf457cb88db12e171ada8acfd5e256d4..65bfb0f7d6e7349999ab17b091f9ca6b778da9a5 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -4,7 +4,7 @@
 
 We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
 
-## MySQL
+## Initial database setup
 
     # Install the database packages
     sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
@@ -32,8 +32,11 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
     # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
     mysql> SET storage_engine=INNODB;
 
+    # If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
+    mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
+
     # Create the GitLab production database
-    mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
+    mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
 
     # Grant the GitLab user necessary permissions on the database
     mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
@@ -51,7 +54,203 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
     # Quit the database session
     mysql> \q
 
-    # You are done installing the database and can go back to the rest of the installation.
+    # You are done installing the database for now and can go back to the rest of the installation.
+
+Please proceed to the rest of the installation before running through the utf8mb4 support section.
+
+
+### MySQL utf8mb4 support
+
+After installation or upgrade, remember to [convert any new tables](#convert) to `utf8mb4`/`utf8mb4_general_ci`.
+
+---
+
+GitLab 8.14 has introduced [a feature](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7420) requiring `utf8mb4` encoding to be supported in your GitLab MySQL Database, which is not the case if you have setup your database before GitLab 8.16.
+
+Follow the below instructions to ensure you use the most up to date requirements for your GitLab MySQL Database.
+
+**We are about to do the following:**
+- Ensure you can enable `utf8mb4` encoding and `utf8mb4_general_ci` collation for your GitLab DB, tables and data.
+- Convert your GitLab tables and data from `utf8`/`utf8_general_ci` to `utf8mb4`/`utf8mb4_general_ci`
+
+### Check for utf8mb4 support
+
+#### Check for InnoDB File-Per-Table Tablespaces
+
+We need to check, enable and maybe convert your existing GitLab DB tables to the [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) as a prerequise for supporting **utfb8mb4 with long indexes** required by recent GitLab databases.
+
+    # Login to MySQL
+    mysql -u root -p
+
+    # Type the MySQL root password
+    mysql > use gitlabhq_production;
+
+    # Check your MySQL version is >= 5.5.3 (GitLab requires 5.5.14+)
+    mysql > SHOW VARIABLES LIKE 'version';
+    +---------------+-----------------+
+    | Variable_name | Value           |
+    +---------------+-----------------+
+    | version       | 5.5.53-0+deb8u1 |
+    +---------------+-----------------+
+
+    # Note where is your MySQL data dir for later:
+    mysql > select @@datadir;
+    +----------------+
+    | @@datadir      |
+    +----------------+
+    | /var/lib/mysql |
+    +----------------+
+
+    # Note whether your MySQL server runs with innodb_file_per_table ON or OFF:
+    mysql> SELECT @@innodb_file_per_table;
+    +-------------------------+
+    | @@innodb_file_per_table |
+    +-------------------------+
+    |                       1 |
+    +-------------------------+
+
+    # You can now quit the database session
+    mysql> \q
+
+> You need **MySQL 5.5.3 or later** to perform this update.
+
+Whatever the results of your checks above, we now need to check if your GitLab database has been created using [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) (i.e. `innodb_file_per_table` was set to **1** at initial setup time).
+
+> Note: This setting is [enabled by default](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_file_per_table) since MySQL 5.6.6.
+
+    # Run this command with root privileges, replace the data dir if different:
+    sudo ls -lh /var/lib/mysql/gitlabhq_production/*.ibd | wc -l
+
+    # Run this command with root privileges, replace the data dir if different:
+    sudo ls -lh /var/lib/mysql/gitlabhq_production/*.frm | wc -l
+
+
+- **Case 1: a result > 0 for both commands**
+
+Congrats, your GitLab database uses the right InnoDB tablespace format.
+
+However, you must still ensure that any **future tables** created by GitLab will still use the right format:
+
+- If `SELECT @@innodb_file_per_table` returned **1** previously, your server is running correctly.
+> It's however a requirement to check *now* that this setting is indeed persisted in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file!
+
+- If `SELECT @@innodb_file_per_table` returned **0** previously, your server is not running correctly.
+> [Enable innodb_file_per_table](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) by running in a MySQL session as root the command `SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda;` and persist the two settings in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file
+
+Now, if you have a **different result** returned by the 2 commands above, it means you have a **mix of tables format** uses in your GitLab database. This can happen if your MySQL server had different values for `innodb_file_per_table` in its life and you updated GitLab at different moments with those inconsistent values. So keep reading.
+
+- **Case 2: a result equals to "0" OR not the same result for both commands**
+
+Unfortunately, none or only some of your GitLab database tables use the GitLab requirement of [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html).
+
+Let's enable what we need on the running server:
+
+    # Login to MySQL
+    mysql -u root -p
+
+    # Type the MySQL root password
+
+    # Enable innodb_file_per_table and set innodb_file_format on the running server:
+    mysql > SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda;
+
+    # You can now quit the database session
+    mysql> \q
+
+> Now, **persist** [innodb_file_per_table](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format-enabling.html) in your `my.cnf` file.
+
+Ensure at this stage that your GitLab instance is indeed **stopped**.
+
+Now, let's convert all the GitLab database tables to the new tablespace format:
+
+    # Login to MySQL
+    mysql -u root -p
+
+    # Type the MySQL root password
+    mysql > use gitlabhq_production;
+
+    # Safety check: you should still have those values set as follow:
+    mysql> SELECT @@innodb_file_per_table, @@innodb_file_format;
+    +-------------------------+----------------------+
+    | @@innodb_file_per_table | @@innodb_file_format |
+    +-------------------------+----------------------+
+    |                       1 | Barracuda            |
+    +-------------------------+----------------------+
+
+    mysql > SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_TYPE="BASE TABLE";
+
+    # If previous query returned results, copy & run all shown SQL statements
+
+    # You can now quit the database session
+    mysql> \q
+
+---
+
+#### Check for proper InnoDB File Format, Row Format, Large Prefix and tables conversion
+
+We need to check, enable and probably convert your existing GitLab DB tables to use the [Barracuda InnoDB file format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format.html), the [DYNAMIC row format](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_dynamic_row_format) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) as a second prerequisite for supporting **utfb8mb4 with long indexes** used by recent GitLab databases.
+
+    # Login to MySQL
+    mysql -u root -p
+
+    # Type the MySQL root password
+    mysql > use gitlabhq_production;
+
+    # Set innodb_file_format and innodb_large_prefix on the running server:
+    # Note: These are the default settings only for MySQL 5.7.7 and later.
+
+    mysql > SET GLOBAL innodb_file_format=Barracuda, innodb_large_prefix=1;
+
+    # Your DB must be (still) using utf8/utf8_general_ci as default encoding and collation.
+    # We will NOT change the default encoding and collation on the DB in order to support future GitLab migrations creating tables
+    # that require "long indexes support" on installations using MySQL <= 5.7.9.
+    # However, when such migrations occur, you will have to follow this guide again to convert the newly created tables to the proper encoding/collation.
+
+    # This should return the following:
+    mysql> SELECT @@character_set_database, @@collation_database;
+    +--------------------------+----------------------+
+    | @@character_set_database | @@collation_database |
+    +--------------------------+----------------------+
+    | utf8                     | utf8_general_ci      |
+    +--------------------------+----------------------+
+
+> Now, ensure that [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) are **persisted** in your `my.cnf` file.
+
+#### Tables and data conversion to utf8mb4
+<a name="convert"></a>
+
+Now that you have a persistent MySQL setup, you can safely upgrade tables after setup or upgrade time:
+
+    # Convert tables not using ROW_FORMAT DYNAMIC:
+
+    mysql> SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` ROW_FORMAT=DYNAMIC;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_TYPE="BASE TABLE" AND ROW_FORMAT!="Dynamic";
+
+    # !! If previous query returned results, copy & run all shown SQL statements
+
+    # Convert tables/columns not using utf8mb4/utf8mb4_general_ci as encoding/collation:
+
+    mysql > SET foreign_key_checks = 0;
+
+    mysql > SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlabhq_production" AND TABLE_COLLATION != "utf8mb4_general_ci" AND TABLE_TYPE="BASE TABLE";
+
+    # !! If previous query returned results, copy & run all shown SQL statements
+
+    # Turn foreign key checks back on
+    mysql > SET foreign_key_checks = 1;
+
+    # You can now quit the database session
+    mysql> \q
+
+Ensure your GitLab database configuration file uses a proper connection encoding and collation:
+
+```sudo -u git -H editor config/database.yml```
+
+    production:
+      adapter: mysql2
+      encoding: utf8mb4
+      collation: utf8mb4_general_ci
+
+[Restart your GitLab instance](../administration/restart_gitlab.md).
+
 
 ## MySQL strings limits
 
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
new file mode 100644
index 0000000000000000000000000000000000000000..820060a489b566017ea441a289d045f3c11ab23a
--- /dev/null
+++ b/doc/install/digitaloceandocker.md
@@ -0,0 +1,136 @@
+# Digital Ocean and Docker
+
+## Initial setup
+
+In this guide you'll configure a Digital Ocean droplet and set up Docker
+locally on either macOS or Linux.
+
+### On macOS
+
+#### Install Docker Toolbox
+
+1. [https://www.docker.com/products/docker-toolbox](https://www.docker.com/products/docker-toolbox)
+
+### On Linux
+
+#### Install Docker Engine
+
+1. [https://docs.docker.com/engine/installation/linux](https://docs.docker.com/engine/installation/linux/)
+
+#### Install Docker Machine
+
+1. [https://docs.docker.com/machine/install-machine](https://docs.docker.com/machine/install-machine/)
+
+_The rest of the steps are identical for macOS and Linux_
+
+### Create new docker host
+
+1. Login to Digital Ocean
+1. Generate a new API token at https://cloud.digitalocean.com/settings/api/tokens
+
+
+This command will create a new DO droplet called `gitlab-test-env-do` that will act as a docker host.
+
+**Note: 4GB is the minimum requirement for a Docker host that will run more then one GitLab instance**
+
++ RAM: 4GB
++ Name: `gitlab-test-env-do`
++ Driver: `digitalocean`
+
+
+**Set the DO token** - Replace the string below with your generated token
+
+```
+export DOTOKEN=cf3dfd0662933203005c4a73396214b7879d70aabc6352573fe178d340a80248
+```
+
+**Create the machine**
+
+```
+docker-machine create \
+  --driver digitalocean \
+  --digitalocean-access-token=$DOTOKEN \
+  --digitalocean-size "4gb" \
+    gitlab-test-env-do
+```
+
++ Resource: https://docs.docker.com/machine/drivers/digital-ocean/
+
+
+### Creating GitLab test instance
+
+
+#### Connect your shell to the new machine
+
+
+In this example we'll create a GitLab EE 8.10.8 instance.
+
+
+First connect the docker client to the docker host you created previously.
+
+```
+eval "$(docker-machine env gitlab-test-env-do)"
+```
+
+You can add this to your `~/.bash_profile` file to ensure the `docker` client uses the `gitlab-test-env-do` docker host
+
+
+#### Create new GitLab container
+
++ HTTP port: `8888`
++ SSH port: `2222`
+   + Set `gitlab_shell_ssh_port` using `--env GITLAB_OMNIBUS_CONFIG `
++ Hostname: IP of docker host
++ Container name: `gitlab-test-8.10`
++ GitLab version: **EE** `8.10.8-ee.0`
+
+#####  Setup container settings
+
+```
+export SSH_PORT=2222
+export HTTP_PORT=8888
+export VERSION=8.10.8-ee.0
+export NAME=gitlab-test-8.10
+```
+
+#####  Create container
+```
+docker run --detach \
+--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$(docker-machine ip gitlab-test-env-do):$HTTP_PORT'; gitlab_rails['gitlab_shell_ssh_port'] = $SSH_PORT;" \
+--hostname $(docker-machine ip gitlab-test-env-do) \
+-p $HTTP_PORT:$HTTP_PORT -p $SSH_PORT:22 \
+--name $NAME \
+gitlab/gitlab-ee:$VERSION
+```
+
+#### Connect to the GitLab container
+
+##### Retrieve the docker host IP
+
+```
+docker-machine ip gitlab-test-env-do
+# example output: 192.168.151.134
+```
+
+
++ Browse to: http://192.168.151.134:8888/
+
+
+##### Execute interactive shell/edit configuration
+
+
+```
+docker exec -it $NAME /bin/bash
+```
+
+```
+# example commands
+root@192:/# vi /etc/gitlab/gitlab.rb
+root@192:/# gitlab-ctl reconfigure
+```
+
+#### Resources
+
++ [https://docs.gitlab.com/omnibus/docker/](https://docs.gitlab.com/omnibus/docker/)
++ [https://docs.docker.com/machine/get-started/](https://docs.docker.com/machine/get-started/)
++ [https://docs.docker.com/machine/reference/ip/](https://docs.docker.com/machine/reference/ip/)+
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 9cebed34b7e2f2e8a0012941d0a0a2f5bbaead0e..b2d5d51d37d840e2bfc8ab209e5334e9ce494684 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -124,7 +124,7 @@ Download Ruby and compile it:
 
     mkdir /tmp/ruby && cd /tmp/ruby
     curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz
-    echo 'a8db9ce7f9110320f33b8325200e3ecfbd2b534b ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
+    echo '1014ee699071aa2ddd501907d18cbe15399c997d  ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
     cd ruby-2.3.3
     ./configure --disable-install-rdoc
     make
@@ -448,7 +448,7 @@ Check if GitLab and its environment are configured correctly:
 
 ### Compile Assets
 
-    sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+    sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production
 
 ### Start Your GitLab Instance
 
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 44d2a14f3666edb6636fc5eeb522e8ea795de4e5..713d11b75e456b03aeac4c02c27ce87ded0349c4 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -113,14 +113,6 @@ Make sure to follow all steps below:
     If you are using a custom init script, make sure to edit the above
     gitlab-workhorse setting as needed.
 
-1.  After all the above changes recompile the assets. This is an important task
-    and will take some time to complete depending on the server resources:
-
-    ```
-    cd /home/git/gitlab
-    sudo -u git -H bundle exec rake assets:clean assets:precompile RAILS_ENV=production
-    ```
-
 1. [Restart GitLab][] for the changes to take effect.
 
 ### Disable relative URL in GitLab
diff --git a/doc/profile/2fa_u2f_authenticate.png b/doc/profile/2fa_u2f_authenticate.png
deleted file mode 100644
index b224ab141955b4a4988f3d9b78c9cbe8b57ccce0..0000000000000000000000000000000000000000
Binary files a/doc/profile/2fa_u2f_authenticate.png and /dev/null differ
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index 3f6dfe03d14aec74c01e2eb1b3d6e79d30293fab..60918a0339c4019cb2207de28a5225b9e28852cc 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -1,143 +1 @@
-# Two-factor Authentication (2FA)
-
-Two-factor Authentication (2FA) provides an additional level of security to your
-GitLab account. Once enabled, in addition to supplying your username and
-password to login, you'll be prompted for a code generated by an application on
-your phone.
-
-By enabling 2FA, the only way someone other than you can log into your account
-is to know your username and password *and* have access to your phone.
-
-> **Note:**
-When you enable 2FA, don't forget to back up your recovery codes. For your safety, if you
-lose your codes for GitLab.com, we can't disable or recover them.  
-
-In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as
-the second factor of authentication. Once enabled, in addition to supplying your username and
-password to login, you'll be prompted to activate your U2F device (usually by pressing
-a button on it), and it will perform secure authentication on your behalf.
-
-> **Note:** Support for U2F devices was added in version 8.8
-
-The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend 
-that you set up both methods of two-factor authentication, so you can still access your account 
-from other browsers.
-
-> **Note:** GitLab officially only supports [Yubikey] U2F devices.
-
-## Enabling 2FA
-
-### Enable 2FA via mobile application
-
-**In GitLab:**
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Account**.
-1. Click **Enable Two-factor Authentication**.
-
-![Two-factor setup](2fa.png)
-
-**On your phone:**
-
-1. Install a compatible application. We recommend [Google Authenticator]
-\(proprietary\) or [FreeOTP] \(open source\).
-1. In the application, add a new entry in one of two ways:
-    * Scan the code with your phone's camera to add the entry automatically.
-    * Enter the details provided to add the entry manually.
-
-**In GitLab:**
-
-1. Enter the six-digit pin number from the entry on your phone into the **Pin
-   code** field.
-1. Click **Submit**.
-
-If the pin you entered was correct, you'll see a message indicating that
-Two-Factor Authentication has been enabled, and you'll be presented with a list
-of recovery codes.
-
-### Enable 2FA via U2F device
-
-**In GitLab:**
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Account**.
-1. Click **Enable Two-Factor Authentication**.
-1. Plug in your U2F device.
-1. Click on **Setup New U2F Device**.
-1. A light will start blinking on your device. Activate it by pressing its button.
-
-You will see a message indicating that your device was successfully set up. 
-Click on **Register U2F Device** to complete the process.
-
-![Two-Factor U2F Setup](2fa_u2f_register.png)
-
-## Recovery Codes
-
-Should you ever lose access to your phone, you can use one of the ten provided
-backup codes to login to your account. We suggest copying or printing them for
-storage in a safe place. **Each code can be used only once** to log in to your
-account.
-
-If you lose the recovery codes or just want to generate new ones, you can do so
-from the **Profile Settings** > **Account** page where you first enabled 2FA.
-
-> **Note:** Recovery codes are not generated for U2F devices.
-
-## Logging in with 2FA Enabled
-
-Logging in with 2FA enabled is only slightly different than a normal login.
-Enter your username and password credentials as you normally would, and you'll
-be presented with a second prompt, depending on which type of 2FA you've enabled.
-
-### Log in via mobile application
-
-Enter the pin from your phone's application or a recovery code to log in.
-
-![Two-Factor Authentication on sign in via OTP](2fa_auth.png)
-
-### Log in via U2F device
-
-1. Click **Login via U2F Device**
-1. A light will start blinking on your device. Activate it by pressing its button.
-
-You will see a message indicating that your device responded to the authentication request.
-Click on **Authenticate via U2F Device** to complete the process.
-
-![Two-Factor Authentication on sign in via U2F device](2fa_u2f_authenticate.png)
-
-## Disabling 2FA
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Account**.
-1. Click **Disable**, under **Two-Factor Authentication**.
-
-This will clear all your two-factor authentication registrations, including mobile
-applications and U2F devices.
-
-## Personal access tokens
-
-When 2FA is enabled, you can no longer use your normal account password to
-authenticate with Git over HTTPS on the command line, you must use a personal
-access token instead.
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Access Tokens**.
-1. Choose a name and expiry date for the token.
-1. Click on **Create Personal Access Token**. 
-1. Save the personal access token somewhere safe.
-
-When using git over HTTPS on the command line, enter the personal access token
-into the password field.
-
-## Note to GitLab administrators
-
-You need to take special care to that 2FA keeps working after
-[restoring a GitLab backup](../raketasks/backup_restore.md).
-
-[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
-[FreeOTP]: https://fedorahosted.org/freeotp/
-[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
+This document was moved to [user/profile/account](../user/profile/account/two_factor_authentication.md).
diff --git a/doc/project_services/kubernetes.md b/doc/project_services/kubernetes.md
index 59d5da702f81e2d14024cd31fde01a37f1eba84a..99aa9e44bdb8f312f4b35d1ab2f364f46e50948c 100644
--- a/doc/project_services/kubernetes.md
+++ b/doc/project_services/kubernetes.md
@@ -8,7 +8,7 @@ the [configuration](#configuration) section.
 
 If you have a single cluster that you want to use for all your projects,
 you can pre-fill the settings page with a default template. To configure the
-template, see the [Services Templates](services-templates.md) document.
+template, see the [Services Templates](services_templates.md) document.
 
 ## Configuration
 
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
index 0b682b43810853a9fd1f868f4162563fa0a42b9e..eaceb2be13761ff8e878d0b8d8b01c8eab78a4ae 100644
--- a/doc/project_services/slack.md
+++ b/doc/project_services/slack.md
@@ -15,7 +15,7 @@ Slack:
 
 After you set up Slack, it's time to set up GitLab.
 
-Go to your project's **Settings > Services > Slack Notifications** and you will see a
+Go to your project's **Settings > Integrations > Slack Notifications** and you will see a
 checkbox with the following events that can be triggered:
 
 - Push
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9803937fcf9d5e77a8a960c7a5120052f934e9b5..9e391d647a86494b93cf7be3d21e49301f319974 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -4,10 +4,12 @@ Git is a distributed version control system, which means you can work locally
 but you can also share or "push" your changes to other servers.
 Before you can push your changes to a GitLab server
 you need a secure communication channel for sharing information.
-GitLab uses Public-key or asymmetric cryptography
-which encrypts a communication channel by locking it with your "private key"
-and allows trusted parties to unlock it with your "public key".
-If someone does not have your public key they cannot access the unencrypted message.
+
+The SSH protocol provides this security and allows you to authenticate to the
+GitLab remote server without supplying your username or password each time.
+
+For a more detailed explanation of how the SSH protocol works, we advise you to
+read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process).
 
 ## Locating an existing SSH key pair
 
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index c44930a4ceb9f428a71bf753fcec4512bbaf59e1..ec13c2446efddd38d5abc360d3f281180a035989 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,6 +1,6 @@
 # System hooks
 
-Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
+Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `project_update`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
 
 System hooks can be used, e.g. for logging or changing information in a LDAP server.
 
@@ -88,6 +88,23 @@ X-Gitlab-Event: System Hook
 }
 ```
 
+**Project updated:**
+
+```json
+{
+          "created_at": "2012-07-21T07:30:54Z",
+          "updated_at": "2012-07-21T07:38:22Z",
+          "event_name": "project_update",
+                "name": "StoreCloud",
+         "owner_email": "johnsmith@gmail.com",
+          "owner_name": "John Smith",
+                "path": "storecloud",
+ "path_with_namespace": "jsmith/storecloud",
+          "project_id": 74,
+  "project_visibility": "private",
+}
+```
+
 **New Team Member:**
 
 ```json
diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md
index 3d68fe201a7ddac94f5d5279f7546865c159941f..2695a16ac0b289924f0095c6bb432b1842fd3c39 100644
--- a/doc/update/8.15-to-8.16.md
+++ b/doc/update/8.15-to-8.16.md
@@ -36,7 +36,7 @@ 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.3.tar.gz
-echo 'a8db9ce7f9110320f33b8325200e3ecfbd2b534b ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
+echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
 cd ruby-2.3.3
 ./configure --disable-install-rdoc
 make
@@ -97,6 +97,8 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
 sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
 ```
 
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
 ### 6. Update gitlab-workhorse
 
 Install and compile gitlab-workhorse. This requires
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 54d523b59fd16e77f7b5779ce3d097a0a7ea0771..154a0f817da4ce32c6492199d5525bb778b51567 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -57,7 +57,7 @@ sudo -u git -H bundle clean
 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
+sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production
 ```
 
 ### 4. Update gitlab-workhorse to the corresponding version
diff --git a/doc/user/account/security.md b/doc/user/account/security.md
index 816094bf8d2e33135b680511c21cc786b8eac29a..9336dee74511fff3e71d13c9cdbd4c77fc376ea1 100644
--- a/doc/user/account/security.md
+++ b/doc/user/account/security.md
@@ -1,3 +1 @@
-# Account Security
-
-- [Two-Factor Authentication](two_factor_authentication.md)
+This document was moved to [profile](../profile/index.md#security).
diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md
index 881358ed94dd9156913b57f9597172b09e3dd89e..ea2c8307860d58e3fff28ce4bbd32e32ccd6e120 100644
--- a/doc/user/account/two_factor_authentication.md
+++ b/doc/user/account/two_factor_authentication.md
@@ -1,68 +1 @@
-# Two-Factor Authentication
-
-## Recovery options
-
-If you lose your code generation device (such as your mobile phone) and you need
-to disable two-factor authentication on your account, you have several options.
-
-### Use a saved recovery code
-
-When you enabled two-factor authentication for your account, a series of
-recovery codes were generated. If you saved those codes somewhere safe, you
-may use one to sign in.
-
-First, enter your username/email and password on the GitLab sign in page. When
-prompted for a two-factor code, enter one of the recovery codes you saved
-previously.
-
-> **Note:** Once a particular recovery code has been used, it cannot be used again.
-  You may still use the other saved recovery codes at a later time.
-
-### Generate new recovery codes using SSH
-
-It's not uncommon for users to forget to save the recovery codes when enabling
-two-factor authentication. If you have an SSH key added to your GitLab account,
-you can generate a new set of recovery codes using SSH.
-
-Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to
-confirm that you wish to generate new codes. If you choose to continue, any
-previously saved codes will be invalidated.
-
-```bash
-$ ssh git@gitlab.example.com 2fa_recovery_codes
-Are you sure you want to generate new two-factor recovery codes?
-Any existing recovery codes you saved will be invalidated. (yes/no)
-yes
-
-Your two-factor authentication recovery codes are:
-
-119135e5a3ebce8e
-11f6v2a498810dcd
-3924c7ab2089c902
-e79a3398bfe4f224
-34bd7b74adbc8861
-f061691d5107df1a
-169bf32a18e63e7f
-b510e7422e81c947
-20dbed24c5e74663
-df9d3b9403b9c9f0
-
-During sign in, use one of the codes above when prompted for
-your two-factor code. Then, visit your Profile Settings and add
-a new device so you do not lose access to your account again.
-```
-
-Next, go to the GitLab sign in page and enter your username/email and password.
-When prompted for a two-factor code, enter one of the recovery codes obtained
-from the command line output.
-
-> **Note:** After signing in, you should immediately visit your **Profile Settings
-  -> Account** to set up two-factor authentication with a new device.
-
-### Ask a GitLab administrator to disable two-factor on your account
-
-If the above two methods are not possible, you may ask a GitLab global
-administrator to disable two-factor authentication for your account. Please
-be aware that this will temporarily leave your account in a less secure state.
-You should sign in and re-enable two-factor authentication as soon as possible
-after the administrator disables it.
+This document was moved to [profile/account/two_factor_authentication](../profile/account/two_factor_authentication.md).
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index f64846887219288d314ea6c4b3baea1d2dc2e947..008872b59a73420abdf0bcba983a190301b129b4 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -300,6 +300,20 @@ You can add task lists to issues, merge requests and comments. To create a task
     - [x] Sub-task 2
     - [ ] Sub-task 3
 
+Tasks formatted as ordered lists are supported as well:
+
+```no-highlight
+1. [x] Completed task
+1. [ ] Incomplete task
+    1. [ ] Sub-task 1
+    1. [x] Sub-task 2
+```
+
+1. [x] Completed task
+1. [ ] Incomplete task
+    1. [ ] Sub-task 1
+    1. [x] Sub-task 2
+
 Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
 
 ### Videos
@@ -650,7 +664,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
 This line is also a separate paragraph, but...
 This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
 
-This line is also a separate paragraph, and...  
+This line is also a separate paragraph, and...
 This line is on its own line, because the previous line ends with two
 spaces.
 ```
@@ -662,7 +676,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
 This line is also begins a separate paragraph, but...
 This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
 
-This line is also a separate paragraph, and...  
+This line is also a separate paragraph, and...
 This line is on its own line, because the previous line ends with two
 spaces.
 
@@ -800,4 +814,4 @@ A link starting with a `/` is relative to the wiki root.
 [redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
 [katex]: https://github.com/Khan/KaTeX "KaTeX website"
 [katex-subset]: https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX "Macros supported by KaTeX"
-[asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual"
\ No newline at end of file
+[asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual"
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 5ada8748d853519e9f1a104650e309b8d8e38a2e..678fc3ffd1fa7ec9f09b30750ef82f256a79acad 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -19,10 +19,12 @@ The following table depicts the various user permission levels in a project.
 | Action                                | Guest   | Reporter   | Developer   | Master   | Owner  |
 |---------------------------------------|---------|------------|-------------|----------|--------|
 | Create new issue                      | ✓       | ✓          | ✓           | ✓        | ✓      |
+| Create confidential issue             | ✓       | ✓          | ✓           | ✓        | ✓      |
+| View confidential issues              | (✓) [^1] | ✓          | ✓           | ✓        | ✓      |
 | Leave comments                        | ✓       | ✓          | ✓           | ✓        | ✓      |
-| See a list of builds                  | ✓ [^1]  | ✓          | ✓           | ✓        | ✓      |
-| See a build log                       | ✓ [^1]  | ✓          | ✓           | ✓        | ✓      |
-| Download and browse build artifacts   | ✓ [^1]  | ✓          | ✓           | ✓        | ✓      |
+| See a list of builds                  | ✓ [^2]  | ✓          | ✓           | ✓        | ✓      |
+| See a build log                       | ✓ [^2]  | ✓          | ✓           | ✓        | ✓      |
+| Download and browse build artifacts   | ✓ [^2]  | ✓          | ✓           | ✓        | ✓      |
 | View wiki pages                       | ✓       | ✓          | ✓           | ✓        | ✓      |
 | Pull project code                     |         | ✓          | ✓           | ✓        | ✓      |
 | Download project                      |         | ✓          | ✓           | ✓        | ✓      |
@@ -63,11 +65,8 @@ The following table depicts the various user permission levels in a project.
 | Switch visibility level               |         |            |             |          | ✓      |
 | Transfer project to another namespace |         |            |             |          | ✓      |
 | Remove project                        |         |            |             |          | ✓      |
-| Force push to protected branches [^2] |         |            |             |          |        |
-| Remove protected branches [^2]        |         |            |             |          |        |
-
-[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
-[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
+| Force push to protected branches [^3] |         |            |             |          |        |
+| Remove protected branches [^3]        |         |            |             |          |        |
 
 ## Group
 
@@ -156,17 +155,20 @@ users:
 | Run CI build                                |                 | ✓           | ✓        | ✓      |
 | Clone source and LFS from current project   |                 | ✓           | ✓        | ✓      |
 | Clone source and LFS from public projects   |                 | ✓           | ✓        | ✓      |
-| Clone source and LFS from internal projects |                 | ✓ [^3]      | ✓ [^3]   | ✓      |
-| Clone source and LFS from private projects  |                 | ✓ [^4]      | ✓ [^4]   | ✓ [^4] |
+| Clone source and LFS from internal projects |                 | ✓ [^4]      | ✓ [^4]   | ✓      |
+| Clone source and LFS from private projects  |                 | ✓ [^5]      | ✓ [^5]   | ✓ [^5] |
 | Push source and LFS                         |                 |             |          |        |
 | Pull container images from current project  |                 | ✓           | ✓        | ✓      |
 | Pull container images from public projects  |                 | ✓           | ✓        | ✓      |
-| Pull container images from internal projects|                 | ✓ [^3]      | ✓ [^3]   | ✓      |
-| Pull container images from private projects |                 | ✓ [^4]      | ✓ [^4]   | ✓ [^4] |
+| Pull container images from internal projects|                 | ✓ [^4]      | ✓ [^4]   | ✓      |
+| Pull container images from private projects |                 | ✓ [^5]      | ✓ [^5]   | ✓ [^5] |
 | Push container images to current project    |                 | ✓           | ✓        | ✓      |
 | Push container images to other projects     |                 |             |          |        |
 
-[^3]: Only if user is not external one.
-[^4]: Only if user is a member of the project.
+[^1]: Guest users can only view the confidential issues they created themselves
+[^2]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
+[^3]: Not allowed for Guest, Reporter, Developer, Master, or Owner
+[^4]: Only if user is not external one.
+[^5]: Only if user is a member of the project.
 [ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
 [new-mod]: project/new_ci_build_permissions_model.md
diff --git a/doc/profile/2fa.png b/doc/user/profile/account/img/2fa.png
similarity index 100%
rename from doc/profile/2fa.png
rename to doc/user/profile/account/img/2fa.png
diff --git a/doc/profile/2fa_auth.png b/doc/user/profile/account/img/2fa_auth.png
similarity index 100%
rename from doc/profile/2fa_auth.png
rename to doc/user/profile/account/img/2fa_auth.png
diff --git a/doc/user/profile/account/img/2fa_u2f_authenticate.png b/doc/user/profile/account/img/2fa_u2f_authenticate.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff2e936764d9ce529cac8ee52796d79b89041496
Binary files /dev/null and b/doc/user/profile/account/img/2fa_u2f_authenticate.png differ
diff --git a/doc/profile/2fa_u2f_register.png b/doc/user/profile/account/img/2fa_u2f_register.png
similarity index 100%
rename from doc/profile/2fa_u2f_register.png
rename to doc/user/profile/account/img/2fa_u2f_register.png
diff --git a/doc/user/profile/account/index.md b/doc/user/profile/account/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..764354e1e9616f42daa5ee7593799fa89216b14d
--- /dev/null
+++ b/doc/user/profile/account/index.md
@@ -0,0 +1,5 @@
+# Profile settings
+
+## Account
+
+Set up [two-factor authentication](two_factor_authentication.md).
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
new file mode 100644
index 0000000000000000000000000000000000000000..cc688a7f99cbe56bf95f1fbe23d218258a12e91a
--- /dev/null
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -0,0 +1,217 @@
+# Two-Factor Authentication
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+GitLab account. Once enabled, in addition to supplying your username and
+password to login, you'll be prompted for a code generated by an application on
+your phone.
+
+By enabling 2FA, the only way someone other than you can log into your account
+is to know your username and password *and* have access to your phone.
+
+## Overview
+
+> **Note:**
+When you enable 2FA, don't forget to back up your recovery codes.
+
+In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as
+the second factor of authentication. Once enabled, in addition to supplying your username and
+password to login, you'll be prompted to activate your U2F device (usually by pressing
+a button on it), and it will perform secure authentication on your behalf.
+
+The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend
+that you set up both methods of two-factor authentication, so you can still access your account
+from other browsers.
+
+## Enabling 2FA
+
+There are two ways to enable two-factor authentication: via a mobile application
+or a U2F device.
+
+### Enable 2FA via mobile application
+
+**In GitLab:**
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Enable Two-factor Authentication**.
+
+![Two-factor setup](img/2fa.png)
+
+**On your phone:**
+
+1. Install a compatible application. We recommend [Google Authenticator]
+\(proprietary\) or [FreeOTP] \(open source\).
+1. In the application, add a new entry in one of two ways:
+    * Scan the code with your phone's camera to add the entry automatically.
+    * Enter the details provided to add the entry manually.
+
+**In GitLab:**
+
+1. Enter the six-digit pin number from the entry on your phone into the **Pin
+   code** field.
+1. Click **Submit**.
+
+If the pin you entered was correct, you'll see a message indicating that
+Two-Factor Authentication has been enabled, and you'll be presented with a list
+of recovery codes.
+
+### Enable 2FA via U2F device
+
+> **Notes:**
+- GitLab officially only supports [Yubikey] U2F devices.
+- Support for U2F devices was added in GitLab 8.8.
+
+**In GitLab:**
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Enable Two-Factor Authentication**.
+1. Plug in your U2F device.
+1. Click on **Setup New U2F Device**.
+1. A light will start blinking on your device. Activate it by pressing its button.
+
+You will see a message indicating that your device was successfully set up.
+Click on **Register U2F Device** to complete the process.
+
+![Two-Factor U2F Setup](img/2fa_u2f_register.png)
+
+## Recovery Codes
+
+> **Note:**
+Recovery codes are not generated for U2F devices.
+
+Should you ever lose access to your phone, you can use one of the ten provided
+backup codes to login to your account. We suggest copying or printing them for
+storage in a safe place. **Each code can be used only once** to log in to your
+account.
+
+If you lose the recovery codes or just want to generate new ones, you can do so
+from the **Profile settings ➔ Account** page where you first enabled 2FA.
+
+## Logging in with 2FA Enabled
+
+Logging in with 2FA enabled is only slightly different than a normal login.
+Enter your username and password credentials as you normally would, and you'll
+be presented with a second prompt, depending on which type of 2FA you've enabled.
+
+### Log in via mobile application
+
+Enter the pin from your phone's application or a recovery code to log in.
+
+![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
+
+### Log in via U2F device
+
+1. Click **Login via U2F Device**
+1. A light will start blinking on your device. Activate it by pressing its button.
+
+You will see a message indicating that your device responded to the authentication request.
+Click on **Authenticate via U2F Device** to complete the process.
+
+![Two-Factor Authentication on sign in via U2F device](img/2fa_u2f_authenticate.png)
+
+## Disabling 2FA
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Disable**, under **Two-Factor Authentication**.
+
+This will clear all your two-factor authentication registrations, including mobile
+applications and U2F devices.
+
+## Personal access tokens
+
+When 2FA is enabled, you can no longer use your normal account password to
+authenticate with Git over HTTPS on the command line, you must use a personal
+access token instead.
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Access Tokens**.
+1. Choose a name and expiry date for the token.
+1. Click on **Create Personal Access Token**.
+1. Save the personal access token somewhere safe.
+
+When using Git over HTTPS on the command line, enter the personal access token
+into the password field.
+
+## Recovery options
+
+To disable two-factor authentication on your account (for example, if you
+have lost your code generation device) you can:
+* [Use a saved recovery code](#use-a-saved-recovery-code)
+* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-SSH)
+* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
+
+### Use a saved recovery code
+
+Enabling two-factor authentication for your account generated several recovery
+codes. If you saved these codes, you can use one of them to sign in.
+
+To use a recovery code, enter your username/email and password on the GitLab
+sign-in page. When prompted for a two-factor code, enter the recovery code.
+
+> **Note:** Once you use a recovery code, you cannot re-use it. You can still
+ use the other recovery codes you saved.
+
+### Generate new recovery codes using SSH
+
+Users often forget to save their recovery codes when enabling two-factor
+authentication. If an SSH key is added to your GitLab account, you can generate
+a new set of recovery codes with SSH.
+
+1. Run `ssh git@gitlab.example.com 2fa_recovery_codes`.
+2. You are prompted to confirm that you want to generate new codes. Continuing this process invalidates previously saved codes.
+    ```
+    bash
+    $ ssh git@gitlab.example.com 2fa_recovery_codes
+    Are you sure you want to generate new two-factor recovery codes?
+    Any existing recovery codes you saved will be invalidated. (yes/no)
+
+    yes
+
+    Your two-factor authentication recovery codes are:
+
+    119135e5a3ebce8e
+    11f6v2a498810dcd
+    3924c7ab2089c902
+    e79a3398bfe4f224
+    34bd7b74adbc8861
+    f061691d5107df1a
+    169bf32a18e63e7f
+    b510e7422e81c947
+    20dbed24c5e74663
+    df9d3b9403b9c9f0
+
+    During sign in, use one of the codes above when prompted for your
+    two-factor code. Then, visit your Profile Settings and add a new device
+    so you do not lose access to your account again.
+    ```
+3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained
+from the command-line output.
+
+> **Note:** After signing in, visit your **Profile Settings -> Account**  immediately to set up two-factor authentication with a new
+  device.
+
+### Ask a GitLab administrator to disable two-factor authentication on your account
+
+If you cannot use a saved recovery code or generate new recovery codes, ask a
+GitLab global administrator to disable two-factor authentication for your
+account. This will temporarily leave your account in a less secure state.
+Sign in and re-enable two-factor authentication as soon as possible.
+
+## Note to GitLab administrators
+
+- You need to take special care to that 2FA keeps working after
+[restoring a GitLab backup](../raketasks/backup_restore.md).
+
+- To ensure 2FA authorizes correctly with TOTP server, you may want to ensure
+your GitLab server's time is synchronized via a service like NTP.  Otherwise,
+you may have cases where authorization always fails because of time differences.
+
+[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
+[FreeOTP]: https://fedorahosted.org/freeotp/
+[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
new file mode 100644
index 0000000000000000000000000000000000000000..1760b1821142fb1b034bc63e63757bd2b7157e25
--- /dev/null
+++ b/doc/user/project/issues/confidential_issues.md
@@ -0,0 +1,68 @@
+# Confidential issues
+
+> [Introduced][ce-3282] in GitLab 8.6.
+
+Confidential issues are issues visible only to members of a project with
+[sufficient permissions](#permissions-and-access-to-confidential-issues).
+Confidential issues can be used by open source projects and companies alike to
+keep security vulnerabilities private or prevent surprises from leaking out.
+
+## Making an issue confidential
+
+You can make an issue confidential either by creating a new issue or editing
+an existing one.
+
+When you create a new issue, a checkbox right below the text area is available
+to mark the issue as confidential. Check that box and hit the **Submit issue**
+button to create the issue. For existing issues, edit them, check the
+confidential checkbox and hit **Save changes**.
+
+![Creating a new confidential issue](img/confidential_issues_create.png)
+
+## Making an issue non-confidential
+
+To make an issue non-confidential, all you have to do is edit it and unmark
+the confidential checkbox. Once you save the issue, it will gain the default
+visibility level you have chosen for your project.
+
+Every change from regular to confidential and vice versa, is indicated by a
+system note in the issue's comments.
+
+![Confidential issues system notes](img/confidential_issues_system_notes.png)
+
+## Indications of a confidential issue
+
+>**Note:** If you don't have [enough permissions](#permissions-and-access-to-confidential-issues),
+you won't be able to see the confidential issues at all.
+
+There are a few things that visually separate a confidential issue from a
+regular one. In the issues index page view, you can see the eye-slash icon
+next to the issues that are marked as confidential.
+
+![Confidential issues index page](img/confidential_issues_index_page.png)
+
+---
+
+Likewise, while inside the issue, you can see the eye-slash icon right next to
+the issue number, but there is also an indicator in the comment area that the
+issue you are commenting on is confidential.
+
+![Confidential issue page](img/confidential_issues_issue_page.png)
+
+## Permissions and access to confidential issues
+
+There are two kinds of level access for confidential issues. The general rule
+is that confidential issues are visible only to members of a project with at
+least [Reporter access][permissions]. However, a guest user can also create
+confidential issues, but can only view the ones that they created themselves.
+
+Confidential issues are also hidden in search results for unprivileged users.
+For example, here's what a user with Master and Guest access sees in the
+project's search results respectively.
+
+| Master access | Guest access |
+| :-----------: | :----------: |
+| ![Confidential issues search master](img/confidential_issues_search_master.png) | ![Confidential issues search guest](img/confidential_issues_search_guest.png) |
+
+[permissions]: ../../permissions.md#project
+[ce-3282]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3282
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
new file mode 100644
index 0000000000000000000000000000000000000000..b516d47ffa3503a115c3de8f5c99b975e4f311a3
--- /dev/null
+++ b/doc/user/project/issues/due_dates.md
@@ -0,0 +1,37 @@
+# Due dates
+
+> [Introduced][ce-3614] in GitLab 8.7.
+
+Due dates can be used in issues to keep track of deadlines and make sure
+features are shipped on time. Due dates require at least [Reporter permissions][permissions]
+to be able to edit them. On the contrary, they can be seen by everybody.
+
+## Setting a due date
+
+When creating or editing an issue, you can see the due date field from where
+a calendar will appear to help you choose the date you want. To remove it,
+select the date text and delete it.
+
+![Create a due date](img/due_dates_create.png)
+
+A quicker way to set a due date is via the issue sidebar. Simply expand the
+sidebar and select **Edit** to pick a due date or remove the existing one.
+Changes are saved immediately.
+
+![Edit a due date via the sidebar](img/due_dates_edit_sidebar.png)
+
+## Making use of due dates
+
+Issues that have a due date can be distinctively seen in the issues index page
+with a calendar icon next to them. Issues where the date is past due will have
+the icon and the date colored red. You can sort issues by those that are
+_Due soon_ or _Due later_ from the dropdown menu in the right.
+
+![Issues with due dates in the issues index page](img/due_dates_issues_index_page.png)
+
+Due dates also appear in your [todos list](../../../workflow/todos.md).
+
+![Issues with due dates in the todos](img/due_dates_todos.png)
+
+[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
+[permissions]: ../../permissions.md#project
diff --git a/doc/user/project/issues/img/confidential_issues_create.png b/doc/user/project/issues/img/confidential_issues_create.png
new file mode 100644
index 0000000000000000000000000000000000000000..d259255599dc4dd79b591e6297059a3e6dbeb8e6
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_create.png differ
diff --git a/doc/user/project/issues/img/confidential_issues_index_page.png b/doc/user/project/issues/img/confidential_issues_index_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..042461e24518265a6d263fa0dff35a629d387316
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_index_page.png differ
diff --git a/doc/user/project/issues/img/confidential_issues_issue_page.png b/doc/user/project/issues/img/confidential_issues_issue_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..b3568e9303af52238887070d2474d6029ba93c3a
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_issue_page.png differ
diff --git a/doc/user/project/issues/img/confidential_issues_search_guest.png b/doc/user/project/issues/img/confidential_issues_search_guest.png
new file mode 100644
index 0000000000000000000000000000000000000000..b85de90b4d543472068887c1391f6ac742971999
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_search_guest.png differ
diff --git a/doc/user/project/issues/img/confidential_issues_search_master.png b/doc/user/project/issues/img/confidential_issues_search_master.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf2b9428875a32bc15efb68a324903718c34c2cc
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_search_master.png differ
diff --git a/doc/user/project/issues/img/confidential_issues_system_notes.png b/doc/user/project/issues/img/confidential_issues_system_notes.png
new file mode 100644
index 0000000000000000000000000000000000000000..4005f9350f7a08adb9225c55dd3a2d936ed30ef8
Binary files /dev/null and b/doc/user/project/issues/img/confidential_issues_system_notes.png differ
diff --git a/doc/user/project/issues/img/due_dates_create.png b/doc/user/project/issues/img/due_dates_create.png
new file mode 100644
index 0000000000000000000000000000000000000000..d2fe1172bab45097974fbb2c8f60442c4da9368d
Binary files /dev/null and b/doc/user/project/issues/img/due_dates_create.png differ
diff --git a/doc/user/project/issues/img/due_dates_edit_sidebar.png b/doc/user/project/issues/img/due_dates_edit_sidebar.png
new file mode 100644
index 0000000000000000000000000000000000000000..6b37150e7dbe49ed43c1fb8ee08ffd2bed740d12
Binary files /dev/null and b/doc/user/project/issues/img/due_dates_edit_sidebar.png differ
diff --git a/doc/user/project/issues/img/due_dates_issues_index_page.png b/doc/user/project/issues/img/due_dates_issues_index_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..defcd5eca398e89caf1564429c8c1d2701c89679
Binary files /dev/null and b/doc/user/project/issues/img/due_dates_issues_index_page.png differ
diff --git a/doc/user/project/issues/img/due_dates_todos.png b/doc/user/project/issues/img/due_dates_todos.png
new file mode 100644
index 0000000000000000000000000000000000000000..92c9fd4021b87cca207a8274642bfe29546a8115
Binary files /dev/null and b/doc/user/project/issues/img/due_dates_todos.png differ
diff --git a/doc/user/project/merge_requests/img/merge_conflict_editor.png b/doc/user/project/merge_requests/img/merge_conflict_editor.png
new file mode 100644
index 0000000000000000000000000000000000000000..6660920c1910af4d85e188f6284d93f756d016b9
Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_conflict_editor.png differ
diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md
index 4d7225bd820dd7ac334cd40d7425b30b2ddc2cc6..68c49054e475afc5539fae1173cd7ead23919386 100644
--- a/doc/user/project/merge_requests/resolve_conflicts.md
+++ b/doc/user/project/merge_requests/resolve_conflicts.md
@@ -21,6 +21,18 @@ request into the source branch, resolving the conflicts using the options
 chosen. If the source branch is `feature` and the target branch is `master`,
 this is similar to performing `git checkout feature; git merge master` locally.
 
+## Merge conflict editor
+
+> Introduced in GitLab 8.13.
+
+The merge conflict resolution editor allows for more complex merge conflicts,
+which require the user to manually modify a file in order to resolve a conflict,
+to be solved right form the GitLab interface. Use the **Edit inline** button
+to open the editor. Once you're sure about your changes, hit the
+**Commit conflict resolution** button.
+
+![Merge conflict editor](img/merge_conflict_editor.png)
+
 ## Conflicts available for resolution
 
 GitLab allows resolving conflicts in a file where all of the below are true:
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index dfc762fe1d3501874f48084ab1821093ac55aa23..cb1c1a84f8c2500867e8d01583f4a21cb0e56ab1 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -22,7 +22,8 @@ with all their related data and be moved into a new GitLab instance.
 
 | GitLab version | Import/Export version |
 | -------- | -------- |
-| 8.13.0 to current  | 0.1.5    |
+| 8.16.2 to current  | 0.1.6    |
+| 8.13.0   | 0.1.5    |
 | 8.12.0   | 0.1.4    |
 | 8.10.3   | 0.1.3    |
 | 8.10.0   | 0.1.2    |
@@ -47,6 +48,9 @@ The following items will NOT be exported:
 
 - Build traces and artifacts
 - LFS objects
+- Container registry images
+- CI variables
+- Any encrypted tokens
 
 ## Exporting a project and its data
 
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index b317bd79dedeffe2b0a34f1aeed320eb4074f258..0b6f00c6aa447afdf716e56566e83939b4e85528 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -7,6 +7,10 @@
 - [Feature branch workflow](workflow.md)
 - [GitLab Flow](gitlab_flow.md)
 - [Groups](groups.md)
+- Issues - The GitLab Issue Tracker is an advanced and complete tool for
+  tracking the evolution of a new idea or the process of solving a problem.
+  - [Confidential issues](../user/project/issues/confidential_issues.md)
+  - [Due date for issues](../user/project/issues/due_dates.md)
 - [Issue Board](../user/project/issue_board.md)
 - [Keyboard shortcuts](shortcuts.md)
 - [File finder](file_finder.md)
diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature
deleted file mode 100644
index 41d79aa6ec86d454736ebb682eddf66368380f13..0000000000000000000000000000000000000000
--- a/features/dashboard/shortcuts.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Shortcuts
-  Background:
-    Given I sign in as a user
-    And I visit dashboard page
-
-  @javascript
-  Scenario: Navigate to projects tab
-    Given I press "g" and "p"
-    Then the active main tab should be Projects
-
-  @javascript
-  Scenario: Navigate to issue tab
-    Given I press "g" and "i"
-    Then the active main tab should be Issues
-
-  @javascript
-  Scenario: Navigate to merge requests tab
-    Given I press "g" and "m"
-    Then the active main tab should be Merge Requests
-
diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb
deleted file mode 100644
index 118d27888df4619acc448ce847cd1b2e17d8035d..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/shortcuts.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-  include SharedSidebarActiveTab
-  include SharedShortcuts
-end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 344b6fda9a638f941dd44879ca33eab04f20ddf2..2bbc43b491f9fa784ed425509424ef9a7160acfe 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -25,15 +25,18 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
   end
 
   step 'I should see todos assigned to me' do
+    merge_request_reference = merge_request.to_reference(full: true)
+    issue_reference = issue.to_reference(full: true)
+
     page.within('.todos-pending-count') { expect(page).to have_content '4' }
     expect(page).to have_content 'To do 4'
     expect(page).to have_content 'Done 0'
 
     expect(page).to have_link project.name_with_namespace
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title)
-    should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?")
-    should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title)
-    should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title)
+    should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title)
+    should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?")
+    should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title)
+    should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title)
   end
 
   step 'I mark the todo as done' do
@@ -44,10 +47,13 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
     page.within('.todos-pending-count') { expect(page).to have_content '3' }
     expect(page).to have_content 'To do 3'
     expect(page).to have_content 'Done 1'
-    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
+    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
   end
 
   step 'I mark all todos as done' do
+    merge_request_reference = merge_request.to_reference(full: true)
+    issue_reference = issue.to_reference(full: true)
+
     click_link 'Mark all as done'
 
     page.within('.todos-pending-count') { expect(page).to have_content '0' }
@@ -55,27 +61,30 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
     expect(page).to have_content 'Done 4'
     expect(page).to have_content "You're all done!"
     expect('.prepend-top-default').not_to have_link project.name_with_namespace
-    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
-    should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}"
-    should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
-    should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}"
+    should_not_see_todo "John Doe assigned you merge request #{merge_request_reference}"
+    should_not_see_todo "John Doe mentioned you on issue #{issue_reference}"
+    should_not_see_todo "John Doe assigned you issue #{issue_reference}"
+    should_not_see_todo "Mary Jane mentioned you on issue #{issue_reference}"
   end
 
   step 'I should see the todo marked as done' do
     click_link 'Done 1'
 
     expect(page).to have_link project.name_with_namespace
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false)
+    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, false)
   end
 
   step 'I should see all todos marked as done' do
+    merge_request_reference = merge_request.to_reference(full: true)
+    issue_reference = issue.to_reference(full: true)
+
     click_link 'Done 4'
 
     expect(page).to have_link project.name_with_namespace
-    should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false)
-    should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?", false)
-    should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title, false)
-    should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title, false)
+    should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, false)
+    should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", false)
+    should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, false)
+    should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, false)
   end
 
   step 'I filter by "Enterprise"' do
@@ -111,16 +120,16 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
   end
 
   step 'I should not see todos related to "Mary Jane" in the list' do
-    should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}"
+    should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference(full: true)}"
   end
 
   step 'I should not see todos related to "Merge Requests" in the list' do
-    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
+    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
   end
 
   step 'I should not see todos related to "Assignments" in the list' do
-    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
-    should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
+    should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
+    should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}"
   end
 
   step 'I click on the todo' do
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index 374eb0b0e07b7fa2c6e4b6a6d789654ccca1c199..19ff92f6dc6e4dbfb7939044e6c5ab486fa8fbf2 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
 
   step 'recent build summary contains information saying that build has been erased' do
     page.within('.erased') do
-      expect(page).to have_content 'Build has been erased'
+      expect(page).to have_content 'Job has been erased'
     end
   end
 
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 7490d2bc6e72ede7c6df518217530f9797acf5a4..48ac7a98f0dd22e694c9f368ca5eada394af12bf 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -34,9 +34,9 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
 
   step 'page should have CI graphs' do
     expect(page).to have_content 'Overall'
-    expect(page).to have_content 'Builds for last week'
-    expect(page).to have_content 'Builds for last month'
-    expect(page).to have_content 'Builds for last year'
+    expect(page).to have_content 'Jobs for last week'
+    expect(page).to have_content 'Jobs for last month'
+    expect(page).to have_content 'Jobs for last year'
     expect(page).to have_content 'Commit duration in minutes for last 30 commits'
   end
 
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index f74a9b5df47c19898eb8d93e72a5e0d53bacdbff..4a35b71af2f9fc34efb319519515565ffbe66ca3 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -15,17 +15,16 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I delete all labels' do
     page.within '.labels' do
-      page.all('.remove-row').each do |remove|
-        remove.click
-        sleep 0.05
+      page.all('.remove-row').each do
+        first('.remove-row').click
       end
     end
   end
 
   step 'I should see labels help message' do
     page.within '.labels' do
-      expect(page).to have_content 'Create a label or generate a default set '\
-                                   'of labels'
+      expect(page).to have_content 'Generate a default set of labels'
+      expect(page).to have_content 'New label'
     end
   end
 
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index 3cc4fe9dafbed44e4864fd3bb5cdfd76ac31626c..31f95b524b3c46be32fb06e0dbaa5b36ad124326 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -30,14 +30,13 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I am signed in as a developer of the project' do
+    @user = create(:user) { |u| @project.add_developer(u) }
     login_as(@user)
   end
 
   step 'There is an open Merge Request' do
-    @user = create(:user)
-    @project = create(:project, :public, :repository)
-    @project_member = create(:project_member, :developer, user: @user, project: @project)
-    @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
+    @merge_request = create(:merge_request, :with_diffs, :simple)
+    @project = @merge_request.source_project
   end
 
   step 'I should see a revert error' do
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 70e6d4836b2463703c66c28d8ca36aa8be455271..d008a8a26af61cf01d2f469b045d8962480e644f 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -47,7 +47,7 @@ module SharedBuilds
   end
 
   step 'recent build has a build trace' do
-    @build.trace = 'build trace'
+    @build.trace = 'job trace'
   end
 
   step 'download of build artifacts archive starts' do
@@ -60,7 +60,7 @@ module SharedBuilds
   end
 
   step 'I see details of a build' do
-    expect(page).to have_content "Build ##{@build.id}"
+    expect(page).to have_content "Job ##{@build.id}"
   end
 
   step 'I see build trace' do
diff --git a/features/support/env.rb b/features/support/env.rb
index 8dbe3624410841dfa79af5589444d19be2de1d8d..f394c30d52f5e5ed89ce5a82036b269c95f868c9 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -4,7 +4,6 @@ SimpleCovEnv.start!
 ENV['RAILS_ENV'] = 'test'
 require './config/environment'
 require 'rspec/expectations'
-require 'sidekiq/testing/inline'
 
 require_relative 'capybara'
 require_relative 'db_cleaner'
@@ -15,7 +14,7 @@ if ENV['CI']
   Knapsack::Adapters::SpinachAdapter.bind
 end
 
-%w(select2_helper test_env repo_helpers wait_for_ajax).each do |f|
+%w(select2_helper test_env repo_helpers wait_for_ajax sidekiq).each do |f|
   require Rails.root.join('spec', 'support', f)
 end
 
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 0950c3d2e88480c19d92b794c3138b267f4bdd88..be659fa4a6ac50f17f7ec5155ee66d00b1020a9b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -129,12 +129,7 @@ module API
         end
       end
 
-      # Delete all merged branches
-      #
-      # Parameters:
-      #   id (required) - The ID of a project
-      # Example Request:
-      #   DELETE /projects/:id/repository/branches/delete_merged
+      desc 'Delete all merged branches'
       delete ":id/repository/merged_branches" do
         DeleteMergedBranchesService.new(user_project, current_user).async_execute
 
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index af61be343be555572158f561858e7b10e150dc9d..44fe0fc4a95aff85770673ae568d71d65e4fcda4 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -209,7 +209,7 @@ module API
 
         build = get_build!(params[:build_id])
 
-        bad_request!("Unplayable Build") unless build.playable?
+        bad_request!("Unplayable Job") unless build.playable?
 
         build.play(current_user)
 
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index e6d707f3c3d7cd1217a150db40b33b4060196a07..2fefe760d24c5b6c68786c3c9b85feb4b578bb4e 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -54,7 +54,7 @@ module API
         authorize! :push_code, user_project
 
         attrs = declared_params
-        attrs[:source_branch] = attrs[:branch_name]
+        attrs[:start_branch] = attrs[:branch_name]
         attrs[:target_branch] = attrs[:branch_name]
         attrs[:actions].map! do |action|
           action[:action] = action[:action].to_sym
@@ -139,8 +139,6 @@ module API
         commit_params = {
           commit: commit,
           create_merge_request: false,
-          source_project: user_project,
-          source_branch: commit.cherry_pick_branch_name,
           target_branch: params[:branch]
         }
 
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 853607308412a80e83fb0f562673b1bda4b98690..64da7d6b86fa59a439a5f9c92b9801d5a452a08b 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -38,26 +38,25 @@ module API
           present key, with: Entities::SSHKey
         end
 
-        # TODO: for 9.0 we should check if params are there with the params block
-        # grape provides, at this point we'd change behaviour so we can't
-        # Behaviour now if you don't provide all required params: it renders a
-        # validation error or two.
         desc 'Add new deploy key to currently authenticated user' do
           success Entities::SSHKey
         end
+        params do
+          requires :key, type: String, desc: 'The new deploy key'
+          requires :title, type: String, desc: 'The name of the deploy key'
+        end
         post ":id/#{path}" do
-          attrs = attributes_for_keys [:title, :key]
-          attrs[:key].strip! if attrs[:key]
+          params[:key].strip!
 
           # Check for an existing key joined to this project
-          key = user_project.deploy_keys.find_by(key: attrs[:key])
+          key = user_project.deploy_keys.find_by(key: params[:key])
           if key
             present key, with: Entities::SSHKey
             break
           end
 
           # Check for available deploy keys in other projects
-          key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
+          key = current_user.accessible_deploy_keys.find_by(key: params[:key])
           if key
             user_project.deploy_keys << key
             present key, with: Entities::SSHKey
@@ -65,7 +64,7 @@ module API
           end
 
           # Create a new deploy key
-          key = DeployKey.new attrs
+          key = DeployKey.new(declared_params(include_missing: false))
           if key.valid? && user_project.deploy_keys << key
             present key, with: Entities::SSHKey
           else
@@ -105,15 +104,19 @@ module API
           present key.deploy_key, with: Entities::SSHKey
         end
 
-        desc 'Delete existing deploy key of currently authenticated user' do
+        desc 'Delete deploy key for a project' do
           success Key
         end
         params do
           requires :key_id, type: Integer, desc: 'The ID of the deploy key'
         end
         delete ":id/#{path}/:key_id" do
-          key = user_project.deploy_keys.find(params[:key_id])
-          key.destroy
+          key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
+          if key
+            key.destroy
+          else
+            not_found!('Deploy Key')
+          end
         end
       end
     end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 9f59939e9ae041dfefd855cb3beb31e52c76446e..a07b2a9ca0fa5fa01768f3f01b1be6fabbafc71d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -137,6 +137,7 @@ module API
       expose :avatar_url
       expose :web_url
       expose :request_access_enabled
+      expose :full_name, :full_path
 
       expose :statistics, if: :statistics do
         with_options format_with: -> (value) { value.to_i } do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 2e79e22e649996fc2f55c9cc7077d0362bbb0e6b..c58472de5785bd31b8b2a1b9426536866a995ec5 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -5,7 +5,7 @@ module API
       def commit_params(attrs)
         {
           file_path: attrs[:file_path],
-          source_branch: attrs[:branch_name],
+          start_branch: attrs[:branch_name],
           target_branch: attrs[:branch_name],
           commit_message: attrs[:commit_message],
           file_content: attrs[:content],
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 49c5f0652ab58bc8921e7ed87d034e0ec7845863..eb5b947172a10f391321fbc97cd63b2c98657128 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -45,7 +45,7 @@ module API
       if id =~ /^\d+$/
         Project.find_by(id: id)
       else
-        Project.find_with_namespace(id)
+        Project.find_by_full_path(id)
       end
     end
 
@@ -90,6 +90,12 @@ module API
       MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
     end
 
+    def find_merge_request_with_access(id, access_level = :read_merge_request)
+      merge_request = user_project.merge_requests.find(id)
+      authorize! access_level, merge_request
+      merge_request
+    end
+
     def authenticate!
       unauthorized! unless current_user
     end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index e8975eb57e0f05e98d2b611cd118fd9c1732802f..080a627495700de2cba6a68d92433061f728e85d 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -30,7 +30,7 @@ module API
 
       def wiki?
         @wiki ||= project_path.end_with?('.wiki') &&
-          !Project.find_with_namespace(project_path)
+          !Project.find_by_full_path(project_path)
       end
 
       def project
@@ -41,7 +41,7 @@ module API
           # the wiki repository as well.
           project_path.chomp!('.wiki') if wiki?
 
-          Project.find_with_namespace(project_path)
+          Project.find_by_full_path(project_path)
         end
       end
 
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 07435d78468ea343777a66303fc611bba9f9feb1..bc3d69f6904b6de2cbd08f20655f15c1cf2d40b7 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -15,10 +15,8 @@ module API
       end
 
       get ":id/merge_requests/:merge_request_id/versions" do
-        merge_request = user_project.merge_requests.
-          find(params[:merge_request_id])
+        merge_request = find_merge_request_with_access(params[:merge_request_id])
 
-        authorize! :read_merge_request, merge_request
         present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
       end
 
@@ -34,10 +32,8 @@ module API
       end
 
       get ":id/merge_requests/:merge_request_id/versions/:version_id" do
-        merge_request = user_project.merge_requests.
-          find(params[:merge_request_id])
+        merge_request = find_merge_request_with_access(params[:merge_request_id])
 
-        authorize! :read_merge_request, merge_request
         present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
       end
     end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index e77af4b7a0d377d0b13170960187ad013d8874c0..7ffb38e62daa3f761fd381244b0aa1da1be295f7 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -118,8 +118,8 @@ module API
           success Entities::MergeRequest
         end
         get path do
-          merge_request = find_project_merge_request(params[:merge_request_id])
-          authorize! :read_merge_request, merge_request
+          merge_request = find_merge_request_with_access(params[:merge_request_id])
+
           present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
         end
 
@@ -127,8 +127,8 @@ module API
           success Entities::RepoCommit
         end
         get "#{path}/commits" do
-          merge_request = find_project_merge_request(params[:merge_request_id])
-          authorize! :read_merge_request, merge_request
+          merge_request = find_merge_request_with_access(params[:merge_request_id])
+
           present merge_request.commits, with: Entities::RepoCommit
         end
 
@@ -136,8 +136,8 @@ module API
           success Entities::MergeRequestChanges
         end
         get "#{path}/changes" do
-          merge_request = find_project_merge_request(params[:merge_request_id])
-          authorize! :read_merge_request, merge_request
+          merge_request = find_merge_request_with_access(params[:merge_request_id])
+
           present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
         end
 
@@ -155,8 +155,7 @@ module API
                           :remove_source_branch
         end
         put path do
-          merge_request = find_project_merge_request(params.delete(:merge_request_id))
-          authorize! :update_merge_request, merge_request
+          merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
 
           mr_params = declared_params(include_missing: false)
           mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
@@ -235,10 +234,7 @@ module API
           use :pagination
         end
         get "#{path}/comments" do
-          merge_request = find_project_merge_request(params[:merge_request_id])
-
-          authorize! :read_merge_request, merge_request
-
+          merge_request = find_merge_request_with_access(params[:merge_request_id])
           present paginate(merge_request.notes.fresh), with: Entities::MRNote
         end
 
@@ -250,8 +246,7 @@ module API
           requires :note, type: String, desc: 'The text of the comment'
         end
         post "#{path}/comments" do
-          merge_request = find_project_merge_request(params[:merge_request_id])
-          authorize! :create_note, merge_request
+          merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
 
           opts = {
             note: params[:note],
@@ -275,7 +270,7 @@ module API
           use :pagination
         end
         get "#{path}/closes_issues" do
-          merge_request = find_project_merge_request(params[:merge_request_id])
+          merge_request = find_merge_request_with_access(params[:merge_request_id])
           issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
           present paginate(issues), with: issue_entity(user_project), current_user: current_user
         end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 284e4cf549ad9fb7920046c7dee90eaced1116fa..4d2a8f482670c35bd5ea1a5208de01918af8a8da 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -70,21 +70,27 @@ module API
         end
         post ":id/#{noteables_str}/:noteable_id/notes" do
           opts = {
-           note: params[:body],
-           noteable_type: noteables_str.classify,
-           noteable_id: params[:noteable_id]
+            note: params[:body],
+            noteable_type: noteables_str.classify,
+            noteable_id: params[:noteable_id]
           }
 
-          if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
-            opts[:created_at] = params[:created_at]
-          end
+          noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+
+          if can?(current_user, noteable_read_ability_name(noteable), noteable)
+            if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
+              opts[:created_at] = params[:created_at]
+            end
 
-          note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+            note = ::Notes::CreateService.new(user_project, current_user, opts).execute
 
-          if note.valid?
-            present note, with: Entities::const_get(note.class.name)
+            if note.valid?
+              present note, with: Entities::const_get(note.class.name)
+            else
+              not_found!("Note #{note.errors.messages}")
+            end
           else
-            not_found!("Note #{note.errors.messages}")
+            not_found!("Note")
           end
         end
 
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 9d8c5b6368574c9ce317105f4b6a3ea8512f2ee2..dcc0c82ee27abb7524cd0c840ab33161c95c85fa 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -58,7 +58,7 @@ module API
       end
       post ":id/snippets" do
         authorize! :create_project_snippet, user_project
-        snippet_params = declared_params
+        snippet_params = declared_params.merge(request: request, api: true)
         snippet_params[:content] = snippet_params.delete(:code)
 
         snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 3a9dfbb237c3e501b6b58b6e478a1be09bd8d74b..1456fe4688ba88a5f368604945617e7c74f6cb8a 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -145,7 +145,7 @@ module API
           name: :room,
           type: String,
           desc: 'Campfire room'
-        },
+        }
       ],
       'custom-issue-tracker' => [
         {
@@ -534,7 +534,36 @@ module API
           desc: 'The password of the user'
         }
       ]
-    }.freeze
+    }
+
+    service_classes = [
+      AsanaService,
+      AssemblaService,
+      BambooService,
+      BugzillaService,
+      BuildkiteService,
+      BuildsEmailService,
+      CampfireService,
+      CustomIssueTrackerService,
+      DroneCiService,
+      EmailsOnPushService,
+      ExternalWikiService,
+      FlowdockService,
+      GemnasiumService,
+      HipchatService,
+      IrkerService,
+      JiraService,
+      KubernetesService,
+      MattermostSlashCommandsService,
+      SlackSlashCommandsService,
+      PipelinesEmailService,
+      PivotaltrackerService,
+      PushoverService,
+      RedmineService,
+      SlackService,
+      MattermostService,
+      TeamcityService,
+    ].freeze
 
     trigger_services = {
       'mattermost-slash-commands' => [
@@ -568,6 +597,19 @@ module API
       services.each do |service_slug, settings|
         desc "Set #{service_slug} service for project"
         params do
+          service_classes.each do |service|
+            event_names = service.try(:event_names) || []
+            event_names.each do |event_name|
+              services[service.to_param.tr("_", "-")] << {
+                required: false,
+                name: event_name.to_sym,
+                type: String,
+                desc: ServicesHelper.service_event_description(event_name)
+              }
+            end
+          end
+          services.freeze
+
           settings.each do |setting|
             if setting[:required]
               requires setting[:name], type: setting[:type], desc: setting[:desc]
@@ -581,7 +623,7 @@ module API
           service_params = declared_params(include_missing: false).merge(active: true)
 
           if service.update_attributes(service_params)
-            true
+            present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
           else
             render_api_error!('400 Bad Request', 400)
           end
@@ -619,6 +661,14 @@ module API
     end
 
     trigger_services.each do |service_slug, settings|
+      helpers do
+        def chat_command_service(project, service_slug, params)
+          project.services.active.where(template: false).find do |service|
+            service.try(:token) == params[:token] && service.to_param == service_slug.underscore
+          end
+        end
+      end
+
       params do
         requires :id, type: String, desc: 'The ID of a project'
       end
@@ -637,9 +687,8 @@ module API
           # This is not accurate, but done to prevent leakage of the project names
           not_found!('Service') unless project
 
-          service = project.find_or_initialize_service(service_slug.underscore)
-
-          result = service.try(:active?) && service.try(:trigger, params)
+          service = chat_command_service(project, service_slug, params)
+          result = service.try(:trigger, params)
 
           if result
             status result[:status] || 200
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index e096e6368061f5c8677f6e1543c1e327f7b553a7..eb9ece49e7febc2e7e51985e58743e84c9919c63 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -64,7 +64,7 @@ module API
                                     desc: 'The visibility level of the snippet'
       end
       post do
-        attrs = declared_params(include_missing: false)
+        attrs = declared_params(include_missing: false).merge(request: request, api: true)
         snippet = CreateSnippetService.new(nil, current_user, attrs).execute
 
         if snippet.persisted?
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 10749b34004fe8e0e62dde7f09722cb4c08c07a4..e11d7537cc9c9fbeb90a71d6a376b7ce3dd5f548 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -3,8 +3,8 @@ module API
     before { authenticate! }
 
     subscribable_types = {
-      'merge_request' => proc { |id| user_project.merge_requests.find(id) },
-      'merge_requests' => proc { |id| user_project.merge_requests.find(id) },
+      'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
+      'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
       'issues' => proc { |id| find_project_issue(id) },
       'labels' => proc { |id| find_project_label(id) },
     }
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index ed8f48aa1e3b3a55726a091fc96d28bdeca2ee1f..9bd077263a7e013b7d567da13718394b657c5ab4 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -5,7 +5,7 @@ module API
     before { authenticate! }
 
     ISSUABLE_TYPES = {
-      'merge_requests' => ->(id) { user_project.merge_requests.find(id) },
+      'merge_requests' => ->(id) { find_merge_request_with_access(id) },
       'issues' => ->(id) { find_project_issue(id) }
     }
 
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 11a7368b4c0f4a6872be41764d1724725a59c4c0..0ed468626b7c13bdbe393320bda05f1caf62ef2e 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -160,6 +160,8 @@ module API
           end
         end
 
+        user_params.merge!(password_expires_at: Time.now) if user_params[:password].present?
+
         if user.update_attributes(user_params.except(:extern_uid, :provider))
           present user, with: Entities::UserPublic
         else
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index 0257848b6bc1ce8647b8f9af0f57758ee68c91cc..e2b57adf61147d11e9da37c5d62008b2ea219a93 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -14,7 +14,7 @@ module Banzai
     def project_from_ref(ref)
       return context[:project] unless ref
 
-      Project.find_with_namespace(ref)
+      Project.find_by_full_path(ref)
     end
   end
 end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 6d04f68c8f9b5cd2f13c2a57f5c9dc47d97b63bf..a3d495a5da08d11cb3bafaafe2d2a34799cb99a5 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -153,7 +153,7 @@ module Banzai
             title = object_link_title(object)
             klass = reference_class(object_sym)
 
-            data = data_attributes_for(link_content || match, project, object)
+            data = data_attributes_for(link_content || match, project, object, link: !!link_content)
 
             if matches.names.include?("url") && matches[:url]
               url = matches[:url]
@@ -172,9 +172,10 @@ module Banzai
         end
       end
 
-      def data_attributes_for(text, project, object)
+      def data_attributes_for(text, project, object, link: false)
         data_attribute(
           original:     text,
+          link:         link,
           project:      project.id,
           object_sym => object.id
         )
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 4d1bc687696f993eda7e6b77337af1cc2d447b2b..fd6b970413218d6dbb7017eb3d91cb86141b1dde 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -62,7 +62,7 @@ module Banzai
         end
       end
 
-      def data_attributes_for(text, project, object)
+      def data_attributes_for(text, project, object, link: false)
         if object.is_a?(ExternalIssue)
           data_attribute(
             project: project.id,
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e194cf59275f82ba6417e45c900d9ae7bf8082e8
--- /dev/null
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -0,0 +1,39 @@
+require "nokogiri"
+require "asciidoctor-plantuml/plantuml"
+
+module Banzai
+  module Filter
+    # HTML that replaces all `code plantuml` tags with PlantUML img tags.
+    #
+    class PlantumlFilter < HTML::Pipeline::Filter
+      def call
+        return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled
+
+        plantuml_setup
+
+        doc.css('pre.plantuml').each do |el|
+          img_tag = Nokogiri::HTML::DocumentFragment.parse(
+            Asciidoctor::PlantUml::Processor.plantuml_content(el.content, {}))
+          el.replace img_tag
+        end
+
+        doc
+      end
+
+      private
+
+      def settings
+        ApplicationSetting.current || ApplicationSetting.create_from_defaults
+      end
+
+      def plantuml_setup
+        Asciidoctor::PlantUml.configure do |conf|
+          conf.url = settings.plantuml_url
+          conf.png_enable = settings.plantuml_enabled
+          conf.svg_enable = false
+          conf.txt_enable = false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index ab7af1cad2134d9c5958477c49050020a6f77f09..6640168bfa26508ff35f450cc29b409a99d88bce 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -53,6 +53,10 @@ module Banzai
         context[:project]
       end
 
+      def skip_project_check?
+        context[:skip_project_check]
+      end
+
       def reference_class(type)
         "gfm gfm-#{type} has-tooltip"
       end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 026b81ac17549c1e4ca53fd3785555372b684036..a447e2b8bff1332869775c3168d1e18f26f39084 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -20,17 +20,19 @@ module Banzai
         code = node.text
         css_classes = "code highlight"
         lexer = lexer_for(language)
+        lang = lexer.tag
 
         begin
           code = format(lex(lexer, code))
 
-          css_classes << " js-syntax-highlight #{lexer.tag}"
+          css_classes << " js-syntax-highlight #{lang}"
         rescue
+          lang = nil
           # Gracefully handle syntax highlighter bugs/errors to ensure
           # users can still access an issue/comment/etc.
         end
 
-        highlighted = %(<pre class="#{css_classes}" v-pre="true"><code>#{code}</code></pre>)
+        highlighted = %(<pre class="#{css_classes}" lang="#{lang}" v-pre="true"><code>#{code}</code></pre>)
 
         # Extracted to a method to measure it
         replace_parent_pre_element(node, highlighted)
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index f842b1fb779d7b79a9ade9f80814b7eb54ed3847..1aa9355b256c439bb0efaa946e14cea70aa5ed6f 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -24,7 +24,7 @@ module Banzai
       end
 
       def call
-        return doc if project.nil?
+        return doc if project.nil? && !skip_project_check?
 
         ref_pattern = User.reference_pattern
         ref_pattern_start = /\A#{ref_pattern}\z/
@@ -58,7 +58,7 @@ module Banzai
       # have `gfm` and `gfm-project_member` class names attached for styling.
       def user_link_filter(text, link_content: nil)
         self.class.references_in(text) do |match, username|
-          if username == 'all'
+          if username == 'all' && !skip_project_check?
             link_to_all(link_content: link_content)
           elsif namespace = namespaces[username]
             link_to_namespace(namespace, link_content: link_content) || match
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
index ac7bbcb0d10a7c5119af269138cabda389495e53..b64a1287d4ded2337b3aa436d81e19f935aab4ec 100644
--- a/lib/banzai/filter/video_link_filter.rb
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -35,7 +35,8 @@ module Banzai
           src: element['src'],
           width: '400',
           controls: true,
-          'data-setup' => '{}')
+          'data-setup' => '{}',
+          'data-title' => element['title'] || element['alt'])
 
         link = doc.document.create_element(
           'a',
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5a1f873496cec2c8659f550844a687d05c95d408..b25d6f18d599a0d5ae9feaac6c9f69102a53fcc6 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -1,9 +1,16 @@
 module Banzai
   module Pipeline
     class GfmPipeline < BasePipeline
+      # These filters convert GitLab Flavored Markdown (GFM) to HTML.
+      # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6
+      # consequently convert that same HTML to GFM to be copied to the clipboard.
+      # Every filter that generates HTML from GFM should have a handler in
+      # app/assets/javascripts/copy_as_gfm.js.es6, in reverse order.
+      # The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
       def self.filters
         @filters ||= FilterArray[
           Filter::SyntaxHighlightFilter,
+          Filter::PlantumlFilter,
           Filter::SanitizationFilter,
 
           Filter::MathFilter,
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index f31fb6c3f71371783be9460dd71dca08ac887497..74663556cbb5fcf278f3d3e2b22e73d6adf1da98 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -52,9 +52,9 @@ module Banzai
     end
 
     # Same as +render_field+, but without consulting or updating the cache field
-    def cacheless_render_field(object, field)
+    def cacheless_render_field(object, field, options = {})
       text = object.__send__(field)
-      context = object.banzai_render_context(field)
+      context = object.banzai_render_context(field).merge(options)
 
       cacheless_render(text, context)
     end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index c4bdef781f7070402e5c4f0769d6f04dd1868704..8b939663ffd972360a4cdda9dceb273515120ea8 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -18,24 +18,31 @@ module Ci
 
           if current_runner.is_runner_queue_value_latest?(params[:last_update])
             header 'X-GitLab-Last-Update', params[:last_update]
+            Gitlab::Metrics.add_event(:build_not_found_cached)
             return build_not_found!
           end
 
           new_update = current_runner.ensure_runner_queue_value
 
-          build = Ci::RegisterBuildService.new.execute(current_runner)
+          result = Ci::RegisterBuildService.new(current_runner).execute
 
-          if build
-            Gitlab::Metrics.add_event(:build_found,
-                                      project: build.project.path_with_namespace)
+          if result.valid?
+            if result.build
+              Gitlab::Metrics.add_event(:build_found,
+                                        project: result.build.project.path_with_namespace)
 
-            present build, with: Entities::BuildDetails
-          else
-            Gitlab::Metrics.add_event(:build_not_found)
+              present result.build, with: Entities::BuildDetails
+            else
+              Gitlab::Metrics.add_event(:build_not_found)
 
-            header 'X-GitLab-Last-Update', new_update
+              header 'X-GitLab-Last-Update', new_update
 
-            build_not_found!
+              build_not_found!
+            end
+          else
+            # We received build that is invalid due to concurrency conflict
+            Gitlab::Metrics.add_event(:build_invalid)
+            conflict!
           end
         end
 
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 7463bd719d5bb14398d4f516d648ebde9b95593c..649ee4d018b4db4e5e793151678274fba79c22e5 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -61,6 +61,7 @@ module Ci
         allow_failure: job[:allow_failure] || false,
         when: job[:when] || 'on_success',
         environment: job[:environment_name],
+        coverage_regex: job[:coverage],
         yaml_variables: yaml_variables(name),
         options: {
           image: job[:image],
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index 730b05bed9700b31ef0f68485d300b6fb319a6e7..a10b4657d7d32c130cb2401d97eb476a7f2f240d 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -8,6 +8,6 @@ class ProjectUrlConstrainer
       return false
     end
 
-    Project.find_with_namespace(full_path).present?
+    Project.find_by_full_path(full_path).present?
   end
 end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 8dda65c71ef61644b1b1127598f81da5642b693b..f638905a1e07ef515af68b589e47470899da48a6 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -10,13 +10,16 @@ module Gitlab
       def find_for_git_client(login, password, project:, ip:)
         raise "Must provide an IP for rate limiting" if ip.nil?
 
+        # `user_with_password_for_git` should be the last check
+        # because it's the most expensive, especially when LDAP
+        # is enabled.
         result =
           service_request_check(login, password, project) ||
           build_access_token_check(login, password) ||
-          user_with_password_for_git(login, password) ||
-          oauth_access_token_check(login, password) ||
           lfs_token_check(login, password) ||
+          oauth_access_token_check(login, password) ||
           personal_access_token_check(login, password) ||
+          user_with_password_for_git(login, password) ||
           Gitlab::Auth::Result.new
 
         rate_limit!(ip, success: result.success?, login: login)
@@ -143,7 +146,9 @@ module Gitlab
             read_authentication_abilities
           end
 
-        Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password)
+        if Devise.secure_compare(token_handler.token, password)
+          Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
+        end
       end
 
       def build_access_token_check(login, password)
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb
index 4fe53ce93a99791d3be0a9e8fea635e81db5c451..25da8474e95a0f7c43c507b3f18be238ef849c63 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/chat_commands/base_command.rb
@@ -42,10 +42,6 @@ module Gitlab
       def find_by_iid(iid)
         collection.find_by(iid: iid)
       end
-
-      def presenter
-        Gitlab::ChatCommands::Presenter.new
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 145086755e4a2240ef123b9097fecd88d75fdc6a..f34ed0f4cf29c3574b78720a1f3530b8e80f9aa6 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -3,7 +3,7 @@ module Gitlab
     class Command < BaseCommand
       COMMANDS = [
         Gitlab::ChatCommands::IssueShow,
-        Gitlab::ChatCommands::IssueCreate,
+        Gitlab::ChatCommands::IssueNew,
         Gitlab::ChatCommands::IssueSearch,
         Gitlab::ChatCommands::Deploy,
       ].freeze
@@ -13,51 +13,32 @@ module Gitlab
 
         if command
           if command.allowed?(project, current_user)
-            present command.new(project, current_user, params).execute(match)
+            command.new(project, current_user, params).execute(match)
           else
-            access_denied
+            Gitlab::ChatCommands::Presenters::Access.new.access_denied
           end
         else
-          help(help_messages)
+          Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
         end
       end
 
       def match_command
         match = nil
-        service = available_commands.find do |klass|
-          match = klass.match(command)
-        end
+        service =
+          available_commands.find do |klass|
+            match = klass.match(params[:text])
+          end
 
         [service, match]
       end
 
       private
 
-      def help_messages
-        available_commands.map(&:help_message)
-      end
-
       def available_commands
         COMMANDS.select do |klass|
           klass.available?(project)
         end
       end
-
-      def command
-        params[:text]
-      end
-
-      def help(messages)
-        presenter.help(messages, params[:command])
-      end
-
-      def access_denied
-        presenter.access_denied
-      end
-
-      def present(resource)
-        presenter.present(resource)
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
index 7127d2f6d047cac40f779fb609c3e20521b417d2..458d90f84e8f066535b4b2790f92d82e7cd73b2c 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -1,8 +1,6 @@
 module Gitlab
   module ChatCommands
     class Deploy < BaseCommand
-      include Gitlab::Routing.url_helpers
-
       def self.match(text)
         /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
       end
@@ -24,35 +22,29 @@ module Gitlab
         to = match[:to]
 
         actions = find_actions(from, to)
-        return unless actions.present?
 
-        if actions.one?
-          play!(from, to, actions.first)
+        if actions.none?
+          Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
+        elsif actions.one?
+          action = play!(from, to, actions.first)
+          Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
         else
-          Result.new(:error, 'Too many actions defined')
+          Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
         end
       end
 
       private
 
       def play!(from, to, action)
-        new_action = action.play(current_user)
-
-        Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.")
+        action.play(current_user)
       end
 
       def find_actions(from, to)
         environment = project.environments.find_by(name: from)
-        return unless environment
+        return [] unless environment
 
         environment.actions_for(to).select(&:starts_environment?)
       end
-
-      def url(subject)
-        polymorphic_url(
-          [subject.project.namespace.becomes(Namespace), subject.project, subject]
-        )
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c0e4d304a4f79f6f9943b8d61a3dd953895512d
--- /dev/null
+++ b/lib/gitlab/chat_commands/help.rb
@@ -0,0 +1,28 @@
+module Gitlab
+  module ChatCommands
+    class Help < BaseCommand
+      # This class has to be used last, as it always matches. It has to match
+      # because other commands were not triggered and we want to show the help
+      # command
+      def self.match(_text)
+        true
+      end
+
+      def self.help_message
+        'help'
+      end
+
+      def self.allowed?(_project, _user)
+        true
+      end
+
+      def execute(commands, text)
+        Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
+      end
+
+      def trigger
+        params[:command]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_new.rb
similarity index 63%
rename from lib/gitlab/chat_commands/issue_create.rb
rename to lib/gitlab/chat_commands/issue_new.rb
index cefb6775db854becff484cc09f75073489a1611f..016054ecd465e73aea59d3954663c20a67a11678 100644
--- a/lib/gitlab/chat_commands/issue_create.rb
+++ b/lib/gitlab/chat_commands/issue_new.rb
@@ -1,8 +1,8 @@
 module Gitlab
   module ChatCommands
-    class IssueCreate < IssueCommand
+    class IssueNew < IssueCommand
       def self.match(text)
-        # we can not match \n with the dot by passing the m modifier as than 
+        # we can not match \n with the dot by passing the m modifier as than
         # the title and description are not seperated
         /\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text)
       end
@@ -19,8 +19,24 @@ module Gitlab
         title = match[:title]
         description = match[:description].to_s.rstrip
 
+        issue = create_issue(title: title, description: description)
+
+        if issue.persisted?
+          presenter(issue).present
+        else
+          presenter(issue).display_errors
+        end
+      end
+
+      private
+
+      def create_issue(title:, description:)
         Issues::CreateService.new(project, current_user, title: title, description: description).execute
       end
+
+      def presenter(issue)
+        Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
+      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb
index 51bf80c800b73f4fe09292859b8f68e91b16ac29..3491b53093ecf6db66fd9f19483520b213bfd51c 100644
--- a/lib/gitlab/chat_commands/issue_search.rb
+++ b/lib/gitlab/chat_commands/issue_search.rb
@@ -10,7 +10,13 @@ module Gitlab
       end
 
       def execute(match)
-        collection.search(match[:query]).limit(QUERY_LIMIT)
+        issues = collection.search(match[:query]).limit(QUERY_LIMIT)
+
+        if issues.present?
+          Presenters::IssueSearch.new(issues).present
+        else
+          Presenters::Access.new(issues).not_found
+        end
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb
index 2a45d49cf6b30824f5672ccfcebb1e9172ced818..d6013f4d10cb0fdef8deb5b29bb0b46b8ddecffa 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/chat_commands/issue_show.rb
@@ -10,7 +10,13 @@ module Gitlab
       end
 
       def execute(match)
-        find_by_iid(match[:iid])
+        issue = find_by_iid(match[:iid])
+
+        if issue
+          Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
+        else
+          Gitlab::ChatCommands::Presenters::Access.new.not_found
+        end
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb
deleted file mode 100644
index 8930a21f4065b231029d2ee986a2676211413c60..0000000000000000000000000000000000000000
--- a/lib/gitlab/chat_commands/presenter.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-module Gitlab
-  module ChatCommands
-    class Presenter
-      include Gitlab::Routing
-
-      def authorize_chat_name(url)
-        message = if url
-                    ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})."
-                  else
-                    ":sweat_smile: Couldn't identify you, nor can I autorize you!"
-                  end
-
-        ephemeral_response(message)
-      end
-
-      def help(commands, trigger)
-        if commands.none?
-          ephemeral_response("No commands configured")
-        else
-          commands.map! { |command| "#{trigger} #{command}" }
-          message = header_with_list("Available commands", commands)
-
-          ephemeral_response(message)
-        end
-      end
-
-      def present(subject)
-        return not_found unless subject
-
-        if subject.is_a?(Gitlab::ChatCommands::Result)
-          show_result(subject)
-        elsif subject.respond_to?(:count)
-          if subject.none?
-            not_found
-          elsif subject.one?
-            single_resource(subject.first)
-          else
-            multiple_resources(subject)
-          end
-        else
-          single_resource(subject)
-        end
-      end
-
-      def access_denied
-        ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
-      end
-
-      private
-
-      def show_result(result)
-        case result.type
-        when :success
-          in_channel_response(result.message)
-        else
-          ephemeral_response(result.message)
-        end
-      end
-
-      def not_found
-        ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
-      end
-
-      def single_resource(resource)
-        return error(resource) if resource.errors.any? || !resource.persisted?
-
-        message = "#{title(resource)}:"
-        message << "\n\n#{resource.description}" if resource.try(:description)
-
-        in_channel_response(message)
-      end
-
-      def multiple_resources(resources)
-        titles = resources.map { |resource| title(resource) }
-
-        message = header_with_list("Multiple results were found:", titles)
-
-        ephemeral_response(message)
-      end
-
-      def error(resource)
-        message = header_with_list("The action was not successful, because:", resource.errors.messages)
-
-        ephemeral_response(message)
-      end
-
-      def title(resource)
-        reference = resource.try(:to_reference) || resource.try(:id)
-        title = resource.try(:title) || resource.try(:name)
-
-        "[#{reference} #{title}](#{url(resource)})"
-      end
-
-      def header_with_list(header, items)
-        message = [header]
-
-        items.each do |item|
-          message << "- #{item}"
-        end
-
-        message.join("\n")
-      end
-
-      def url(resource)
-        url_for(
-          [
-            resource.project.namespace.becomes(Namespace),
-            resource.project,
-            resource
-          ]
-        )
-      end
-
-      def ephemeral_response(message)
-        {
-          response_type: :ephemeral,
-          text: message,
-          status: 200
-        }
-      end
-
-      def in_channel_response(message)
-        {
-          response_type: :in_channel,
-          text: message,
-          status: 200
-        }
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..92f4fa17f78d32a66b04a9bd116382304a11c1c3
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/access.rb
@@ -0,0 +1,40 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class Access < Presenters::Base
+        def access_denied
+          ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
+        end
+
+        def not_found
+          ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
+        end
+
+        def authorize
+          message =
+            if @resource
+              ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})."
+            else
+              ":sweat_smile: Couldn't identify you, nor can I autorize you!"
+            end
+
+          ephemeral_response(text: message)
+        end
+
+        def unknown_command(commands)
+          ephemeral_response(text: help_message(trigger))
+        end
+
+        private
+
+        def help_message(trigger)
+          header_with_list("Command not found, these are the commands you can use", full_commands(trigger))
+        end
+
+        def full_commands(trigger)
+          @resource.map { |command| "#{trigger} #{command.help_message}" }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2700a5a2ad5004e6d09595353882d5c73d4d1a3e
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/base.rb
@@ -0,0 +1,77 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class Base
+        include Gitlab::Routing.url_helpers
+
+        def initialize(resource = nil)
+          @resource = resource
+        end
+
+        def display_errors
+          message = header_with_list("The action was not successful, because:", @resource.errors.full_messages)
+
+          ephemeral_response(text: message)
+        end
+
+        private
+
+        def header_with_list(header, items)
+          message = [header]
+
+          items.each do |item|
+            message << "- #{item}"
+          end
+
+          message.join("\n")
+        end
+
+        def ephemeral_response(message)
+          response = {
+            response_type: :ephemeral,
+            status: 200
+          }.merge(message)
+
+          format_response(response)
+        end
+
+        def in_channel_response(message)
+          response = {
+            response_type: :in_channel,
+            status: 200
+          }.merge(message)
+
+          format_response(response)
+        end
+
+        def format_response(response)
+          response[:text] = format(response[:text]) if response.has_key?(:text)
+
+          if response.has_key?(:attachments)
+            response[:attachments].each do |attachment|
+              attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext]
+              attachment[:text] = format(attachment[:text]) if attachment[:text]
+            end
+          end
+
+          response
+        end
+
+        # Convert Markdown to slacks format
+        def format(string)
+          Slack::Notifier::LinkFormatter.format(string)
+        end
+
+        def resource_url
+          url_for(
+            [
+              @resource.project.namespace.becomes(Namespace),
+              @resource.project,
+              @resource
+            ]
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..863d0bf99ca2fc9f3216d3c2a668b9efeda41031
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/deploy.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class Deploy < Presenters::Base
+        def present(from, to)
+          message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})."
+
+          in_channel_response(text: message)
+        end
+
+        def no_actions
+          ephemeral_response(text: "No action found to be executed")
+        end
+
+        def too_many_actions
+          ephemeral_response(text: "Too many actions defined")
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cd47b7f4c6ab2fa4559f2868e3abd7a7d16c0039
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/help.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class Help < Presenters::Base
+        def present(trigger, text)
+          ephemeral_response(text: help_message(trigger, text))
+        end
+
+        private
+
+        def help_message(trigger, text)
+          return "No commands available :thinking_face:" unless @resource.present?
+
+          if text.start_with?('help')
+            header_with_list("Available commands", full_commands(trigger))
+          else
+            header_with_list("Unknown command, these commands are available", full_commands(trigger)) 
+          end
+        end
+
+        def full_commands(trigger)
+          @resource.map { |command| "#{trigger} #{command.help_message}" }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfb1c8f6616daec6d3071eabd511a04820208caf
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issuable.rb
@@ -0,0 +1,43 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      module Issuable
+        def color(issuable)
+          issuable.open? ? '#38ae67' : '#d22852'
+        end
+
+        def status_text(issuable)
+          issuable.open? ? 'Open' : 'Closed'
+        end
+
+        def project
+          @resource.project
+        end
+
+        def author
+          @resource.author
+        end
+
+        def fields
+          [
+            {
+              title: "Assignee",
+              value: @resource.assignee ? @resource.assignee.name : "_None_",
+              short: true
+            },
+            {
+              title: "Milestone",
+              value: @resource.milestone ? @resource.milestone.title : "_None_",
+              short: true
+            },
+            {
+              title: "Labels",
+              value: @resource.labels.any? ? @resource.label_names : "_None_",
+              short: true
+            }
+          ]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1a3add56c9ed1d675df2388d0cc1c19514e8190
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_new.rb
@@ -0,0 +1,50 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class IssueNew < Presenters::Base
+        include Presenters::Issuable
+
+        def present
+          in_channel_response(new_issue)
+        end
+
+        private
+
+        def new_issue 
+          {
+            attachments: [
+              {
+                title:        "#{@resource.title} · #{@resource.to_reference}",
+                title_link:   resource_url,
+                author_name:  author.name,
+                author_icon:  author.avatar_url,
+                fallback:     "New issue #{@resource.to_reference}: #{@resource.title}",
+                pretext:      pretext,
+                color:        color(@resource),
+                fields:       fields,
+                mrkdwn_in: [
+                  :title,
+                  :pretext,
+                  :text,
+                  :fields
+                ]
+              }
+            ]
+          }
+        end
+
+        def pretext
+          "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
+        end
+
+        def project_link
+          "[#{project.name_with_namespace}](#{projects_url(project)})"
+        end
+
+        def author_profile_link
+          "[#{author.to_reference}](#{url_for(author)})"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3478359b91d07c595b493bf0a8a9d53dcf659626
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_search.rb
@@ -0,0 +1,47 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class IssueSearch < Presenters::Base
+        include Presenters::Issuable
+
+        def present
+          text = if @resource.count >= 5
+                   "Here are the first 5 issues I found:"
+                 elsif @resource.one?
+                   "Here is the only issue I found:"
+                 else
+                   "Here are the #{@resource.count} issues I found:"
+                 end
+
+          ephemeral_response(text: text, attachments: attachments)
+        end
+
+        private
+
+        def attachments
+          @resource.map do |issue|
+            url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})"
+
+            {
+              color: color(issue),
+              fallback: "#{issue.to_reference} #{issue.title}",
+              text: "#{url} · #{issue.title} (#{status_text(issue)})",
+
+              mrkdwn_in: [
+                :text
+              ]
+            }
+          end
+        end
+
+        def project
+          @project ||= @resource.first.project
+        end
+
+        def namespace
+          @namespace ||= project.namespace.becomes(Namespace)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fe5847ccd157ce59e3fbdf175a1ec2d798e8bd8d
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_show.rb
@@ -0,0 +1,61 @@
+module Gitlab
+  module ChatCommands
+    module Presenters
+      class IssueShow < Presenters::Base
+        include Presenters::Issuable
+
+        def present
+          if @resource.confidential?
+            ephemeral_response(show_issue)
+          else
+            in_channel_response(show_issue)
+          end
+        end
+
+        private
+
+        def show_issue
+          {
+            attachments: [
+              {
+                title:        "#{@resource.title} · #{@resource.to_reference}",
+                title_link:   resource_url,
+                author_name:  author.name,
+                author_icon:  author.avatar_url,
+                fallback:     "Issue #{@resource.to_reference}: #{@resource.title}",
+                pretext:      pretext,
+                text:         text,
+                color:        color(@resource),
+                fields:       fields,
+                mrkdwn_in: [
+                  :pretext,
+                  :text,
+                  :fields
+                ]
+              }
+            ]
+          }
+        end
+
+        def text
+          message = "**#{status_text(@resource)}**"
+
+          if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero?
+            return message
+          end
+
+          message << " · "
+          message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero?
+          message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero?
+          message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero?
+
+          message
+        end
+
+        def pretext
+          "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..12a063059cb8aaada9404ffc217cb8a76a154629
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/coverage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    class Config
+      module Entry
+        ##
+        # Entry that represents Coverage settings.
+        #
+        class Coverage < Node
+          include Validatable
+
+          validations do
+            validates :config, regexp: true
+          end
+
+          def value
+            @config[1...-1]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb
index a4ec8f0ff2ffb493502f0989e180a9184b67c41e..ede97cc05044d6096bb1cdfbffc37fe11610764a 100644
--- a/lib/gitlab/ci/config/entry/global.rb
+++ b/lib/gitlab/ci/config/entry/global.rb
@@ -33,8 +33,11 @@ module Gitlab
           entry :cache, Entry::Cache,
             description: 'Configure caching between build jobs.'
 
+          entry :coverage, Entry::Coverage,
+               description: 'Coverage configuration for this pipeline.'
+
           helpers :before_script, :image, :services, :after_script,
-                  :variables, :stages, :types, :cache, :jobs
+                  :variables, :stages, :types, :cache, :coverage, :jobs
 
           def compose!(_deps = nil)
             super(self) do
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index a55362f0b6b76a8d36c71d2765fdb3215b6ffd94..69a5e6f433d119d6bdf92283d7c039e951688ec4 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
 
           ALLOWED_KEYS = %i[tags script only except type image services allow_failure
                             type stage when artifacts cache dependencies before_script
-                            after_script variables environment]
+                            after_script variables environment coverage]
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS
@@ -71,9 +71,12 @@ module Gitlab
           entry :environment, Entry::Environment,
                description: 'Environment configuration for this job.'
 
+          entry :coverage, Entry::Coverage,
+               description: 'Coverage configuration for this job.'
+
           helpers :before_script, :script, :stage, :type, :after_script,
                   :cache, :image, :services, :only, :except, :variables,
-                  :artifacts, :commands, :environment
+                  :artifacts, :commands, :environment, :coverage
 
           attributes :script, :tags, :allow_failure, :when, :dependencies
 
@@ -130,6 +133,7 @@ module Gitlab
               variables: variables_defined? ? variables_value : nil,
               environment: environment_defined? ? environment_value : nil,
               environment_name: environment_defined? ? environment_value[:name] : nil,
+              coverage: coverage_defined? ? coverage_value : nil,
               artifacts: artifacts_value,
               after_script: after_script_value }
           end
diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
index f01975aab5c16858c2312aca11291e2f26f6e33f..9b9a0a8125a68af29224c969ebbb8d4dbd49e6ce 100644
--- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
+++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
@@ -28,17 +28,21 @@ module Gitlab
             value.is_a?(String) || value.is_a?(Symbol)
           end
 
+          def validate_regexp(value)
+            !value.nil? && Regexp.new(value.to_s) && true
+          rescue RegexpError, TypeError
+            false
+          end
+
           def validate_string_or_regexp(value)
             return true if value.is_a?(Symbol)
             return false unless value.is_a?(String)
 
             if value.first == '/' && value.last == '/'
-              Regexp.new(value[1...-1])
+              validate_regexp(value[1...-1])
             else
               true
             end
-          rescue RegexpError
-            false
           end
 
           def validate_boolean(value)
diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb
index 28b0a9ffe014280ebd3e3a0d248a52e32dff6a9b..16b234e6c591c016bbd4ac6ee475dc5cf8d8e344 100644
--- a/lib/gitlab/ci/config/entry/trigger.rb
+++ b/lib/gitlab/ci/config/entry/trigger.rb
@@ -9,15 +9,7 @@ module Gitlab
           include Validatable
 
           validations do
-            include LegacyValidationHelpers
-
-            validate :array_of_strings_or_regexps
-
-            def array_of_strings_or_regexps
-              unless validate_array_of_strings_or_regexps(config)
-                errors.add(:config, 'should be an array of strings or regexps')
-              end
-            end
+            validates :config, array_of_strings_or_regexps: true
           end
         end
       end
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index 8632dd0e2333105c8ef87b50afa0f2775e5f003e..bd7428b1272bc414d7e776590d94e4dc2d5870a0 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -54,6 +54,51 @@ module Gitlab
             end
           end
 
+          class RegexpValidator < ActiveModel::EachValidator
+            include LegacyValidationHelpers
+
+            def validate_each(record, attribute, value)
+              unless validate_regexp(value)
+                record.errors.add(attribute, 'must be a regular expression')
+              end
+            end
+
+            private
+
+            def look_like_regexp?(value)
+              value.is_a?(String) && value.start_with?('/') &&
+                value.end_with?('/')
+            end
+
+            def validate_regexp(value)
+              look_like_regexp?(value) &&
+                Regexp.new(value.to_s[1...-1]) &&
+                true
+            rescue RegexpError
+              false
+            end
+          end
+
+          class ArrayOfStringsOrRegexpsValidator < RegexpValidator
+            def validate_each(record, attribute, value)
+              unless validate_array_of_strings_or_regexps(value)
+                record.errors.add(attribute, 'should be an array of strings or regexps')
+              end
+            end
+
+            private
+
+            def validate_array_of_strings_or_regexps(values)
+              values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp))
+            end
+
+            def validate_string_or_regexp(value)
+              return false unless value.is_a?(String)
+              return validate_regexp(value) if look_like_regexp?(value)
+              true
+            end
+          end
+
           class TypeValidator < ActiveModel::EachValidator
             def validate_each(record, attribute, value)
               type = options[:with]
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index a979fe7d573939eee5fcba65c199ea295b40276e..67bbc3c484932aae0eef91da8b82fee70b64ec2b 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -10,7 +10,7 @@ module Gitlab
           end
 
           def action_icon
-            'ban'
+            'icon_action_cancel'
           end
 
           def action_path
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index eee9a64120b287ad29724d425d1432717e75074b..38ac6edc9f188ece3d76b7a273e37cd07982f9d9 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -4,8 +4,11 @@ module Gitlab
       module Build
         class Factory < Status::Factory
           def self.extended_statuses
-            [Status::Build::Stop, Status::Build::Play,
-             Status::Build::Cancelable, Status::Build::Retryable]
+            [[Status::Build::Cancelable,
+              Status::Build::Retryable],
+             [Status::Build::FailedAllowed,
+              Status::Build::Play,
+              Status::Build::Stop]]
           end
 
           def self.common_helpers
diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb
new file mode 100644
index 0000000000000000000000000000000000000000..807afe24bd5765f695cd3cf88d8c6b618a4930c8
--- /dev/null
+++ b/lib/gitlab/ci/status/build/failed_allowed.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class FailedAllowed < SimpleDelegator
+          include Status::Extended
+
+          def label
+            'failed (allowed to fail)'
+          end
+
+          def icon
+            'icon_status_warning'
+          end
+
+          def group
+            'failed_with_warnings'
+          end
+
+          def self.matches?(build, user)
+            build.failed? && build.allow_failure?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index 1bf949c96dd9db9ae9d2dd1644089ac74b0f1265..0f4b7b24cefadee97dd3f048c378e25ddb86474f 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -26,17 +26,13 @@ module Gitlab
           end
 
           def action_icon
-            'play'
+            'icon_action_play'
           end
 
           def action_title
             'Play'
           end
 
-          def action_class
-            'ci-play-icon'
-          end
-
           def action_path
             play_namespace_project_build_path(subject.project.namespace,
                                               subject.project,
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 8e38d6a8523c19e506fea4537f3db2d011cde202..6b362af76343e9e97a5fa5de5bf563b31d4f78b6 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -10,7 +10,7 @@ module Gitlab
           end
 
           def action_icon
-            'refresh'
+            'icon_action_retry'
           end
 
           def action_title
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index e1dfdb76d414777a7575140b2b7b2fb1c475c518..90401cad0d27398b986751bcbc0892546e5d2516 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -26,7 +26,7 @@ module Gitlab
           end
 
           def action_icon
-            'stop'
+            'icon_action_stop'
           end
 
           def action_title
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index 73b6ab5a635e305eb830f506a0df9e857eebedf0..3dd2b9e01f6d24efd4a05ba495c89c1a8080baf9 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -42,9 +42,6 @@ module Gitlab
           raise NotImplementedError
         end
 
-        def action_class
-        end
-
         def action_path
           raise NotImplementedError
         end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index ae9ef895df49d27e3bb9c81ac02df667fee58e1d..15836c699c7ce68e598d16a2046cefa68add68c0 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -5,41 +5,46 @@ module Gitlab
         def initialize(subject, user)
           @subject = subject
           @user = user
+          @status = subject.status || HasStatus::DEFAULT_STATUS
         end
 
         def fabricate!
-          if extended_status
-            extended_status.new(core_status)
-          else
+          if extended_statuses.none?
             core_status
+          else
+            compound_extended_status
           end
         end
 
-        def self.extended_statuses
-          []
+        def core_status
+          Gitlab::Ci::Status
+            .const_get(@status.capitalize)
+            .new(@subject, @user)
+            .extend(self.class.common_helpers)
         end
 
-        def self.common_helpers
-          Module.new
+        def compound_extended_status
+          extended_statuses.inject(core_status) do |status, extended|
+            extended.new(status)
+          end
         end
 
-        private
+        def extended_statuses
+          return @extended_statuses if defined?(@extended_statuses)
 
-        def simple_status
-          @simple_status ||= @subject.status || :created
+          groups = self.class.extended_statuses.map do |group|
+            Array(group).find { |status| status.matches?(@subject, @user) }
+          end
+
+          @extended_statuses = groups.flatten.compact
         end
 
-        def core_status
-          Gitlab::Ci::Status
-            .const_get(simple_status.capitalize)
-            .new(@subject, @user)
-            .extend(self.class.common_helpers)
+        def self.extended_statuses
+          []
         end
 
-        def extended_status
-          @extended ||= self.class.extended_statuses.find do |status|
-            status.matches?(@subject, @user)
-          end
+        def self.common_helpers
+          Module.new
         end
       end
     end
diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb
index 16dcb326be9c46503d0fcd6a19ee6fc1057159b0..13c8343b12a2b6170fab96d8b9323f2d3eded876 100644
--- a/lib/gitlab/ci/status/pipeline/factory.rb
+++ b/lib/gitlab/ci/status/pipeline/factory.rb
@@ -4,7 +4,7 @@ module Gitlab
       module Pipeline
         class Factory < Status::Factory
           def self.extended_statuses
-            [Pipeline::SuccessWithWarnings]
+            [Status::SuccessWarning]
           end
 
           def self.common_helpers
diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
deleted file mode 100644
index 24bf8b869e04cb45900ace17f9e42402dac14792..0000000000000000000000000000000000000000
--- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Gitlab
-  module Ci
-    module Status
-      module Pipeline
-        class SuccessWithWarnings < SimpleDelegator
-          include Status::Extended
-
-          def text
-            'passed'
-          end
-
-          def label
-            'passed with warnings'
-          end
-
-          def icon
-            'icon_status_warning'
-          end
-
-          def group
-            'success_with_warnings'
-          end
-
-          def self.matches?(pipeline, user)
-            pipeline.success? && pipeline.has_warnings?
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb
index 689a5dd45bc3911e9d323c0fde6c02ec60a4afce..4c37f084d07d2c6d8d997483a7d83ddd794e8e30 100644
--- a/lib/gitlab/ci/status/stage/factory.rb
+++ b/lib/gitlab/ci/status/stage/factory.rb
@@ -3,6 +3,10 @@ module Gitlab
     module Status
       module Stage
         class Factory < Status::Factory
+          def self.extended_statuses
+            [Status::SuccessWarning]
+          end
+
           def self.common_helpers
             Status::Stage::Common
           end
diff --git a/lib/gitlab/ci/status/success_warning.rb b/lib/gitlab/ci/status/success_warning.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4cdab6957a00c835c7996b02aac7f62bb14e88a
--- /dev/null
+++ b/lib/gitlab/ci/status/success_warning.rb
@@ -0,0 +1,33 @@
+module Gitlab
+  module Ci
+    module Status
+      ##
+      # Extended status used when pipeline or stage passed conditionally.
+      # This means that failed jobs that are allowed to fail were present.
+      #
+      class SuccessWarning < SimpleDelegator
+        include Status::Extended
+
+        def text
+          'passed'
+        end
+
+        def label
+          'passed with warnings'
+        end
+
+        def icon
+          'icon_status_warning'
+        end
+
+        def group
+          'success_with_warnings'
+        end
+
+        def self.matches?(subject, user)
+          subject.success? && subject.has_warnings?
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/trace_reader.rb b/lib/gitlab/ci/trace_reader.rb
index 37e51536e8fc1b21ddd94a2f9712f801c18e3f04..1d7ddeb3e0f02277e6ef9c2ffa20e0373e4c42dd 100644
--- a/lib/gitlab/ci/trace_reader.rb
+++ b/lib/gitlab/ci/trace_reader.rb
@@ -42,6 +42,7 @@ module Gitlab
           end
 
           chunks.join.lines.last(max_lines).join
+            .force_encoding(Encoding.default_external)
         end
       end
     end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 2ff27e46d646766a129e0dea025ecb4a18b3c7e2..e20f5f6f5143f747e483a51844a37520fd3357d5 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -9,7 +9,9 @@ module Gitlab
     end
 
     def ensure_application_settings!
-      if connect_to_db?
+      return fake_application_settings unless connect_to_db?
+
+      unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
         begin
           settings = ::ApplicationSetting.current
         # In case Redis isn't running or the Redis UNIX socket file is not available
@@ -20,43 +22,23 @@ module Gitlab
         settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
       end
 
-      settings || fake_application_settings
+      settings || in_memory_application_settings
     end
 
     def sidekiq_throttling_enabled?
       current_application_settings.sidekiq_throttling_enabled?
     end
 
+    def in_memory_application_settings
+      @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults)
+    # In case migrations the application_settings table is not created yet,
+    # we fallback to a simple OpenStruct
+    rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError
+      fake_application_settings
+    end
+
     def fake_application_settings
-      OpenStruct.new(
-        default_projects_limit: Settings.gitlab['default_projects_limit'],
-        default_branch_protection: Settings.gitlab['default_branch_protection'],
-        signup_enabled: Settings.gitlab['signup_enabled'],
-        signin_enabled: Settings.gitlab['signin_enabled'],
-        gravatar_enabled: Settings.gravatar['enabled'],
-        koding_enabled: false,
-        plantuml_enabled: false,
-        sign_in_text: nil,
-        after_sign_up_text: nil,
-        help_page_text: nil,
-        shared_runners_text: nil,
-        restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
-        max_attachment_size: Settings.gitlab['max_attachment_size'],
-        session_expire_delay: Settings.gitlab['session_expire_delay'],
-        default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
-        default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
-        domain_whitelist: Settings.gitlab['domain_whitelist'],
-        import_sources: %w[gitea github bitbucket gitlab google_code fogbugz git gitlab_project],
-        shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
-        max_artifacts_size: Settings.artifacts['max_size'],
-        require_two_factor_authentication: false,
-        two_factor_grace_period: 48,
-        akismet_enabled: false,
-        repository_checks_enabled: true,
-        container_registry_token_expire_delay: 5,
-        user_default_external: false,
-        sidekiq_throttling_enabled: false,
-      )
+      OpenStruct.new(::ApplicationSetting.defaults)
     end
 
     private
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index 74bbcdcb3dd1ff9255dce4754d9bc4cdba671b1a..559e3939da6416139a922c6580a6cf439d0aa12b 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -13,7 +13,7 @@ module Gitlab
       end
 
       def as_json
-        AnalyticsStageSerializer.new.represent(self).as_json
+        AnalyticsStageSerializer.new.represent(self)
       end
 
       def title
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 5245b9ca8fc3cee18148710420a6bb890e48619e..d5bf6149749859ed4dfa840bbb8baf0525aad072 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
       private
 
       def serialize(event)
-        AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
+        AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
       end
     end
   end
diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
index 0d8da99455eff72f75db8edd9dcbcd4cc808599f..3df9cbdcfce15fdb71083b2461a53a809638a85c 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -16,7 +16,7 @@ module Gitlab
       private
 
       def serialize(event)
-        AnalyticsIssueSerializer.new(project: @project).represent(event).as_json
+        AnalyticsIssueSerializer.new(project: @project).represent(event)
       end
     end
   end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 88a8710dbe670e4f303f7c509407098dc4dc57d8..7d342a2d2cbd8bcc924092fa05084f538f040185 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -37,7 +37,7 @@ module Gitlab
       def serialize_commit(event, st_commit, query)
         commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
 
-        AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json
+        AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
       end
     end
   end
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index 4df0bd06393e4140ffd71c8f5dddddb73b1d7bf6..4c7b3f4467f5c31f2d5c50a1293d5b5bf057a0fa 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -15,7 +15,7 @@ module Gitlab
       end
 
       def serialize(event)
-        AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
+        AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
       end
     end
   end
diff --git a/lib/gitlab/cycle_analytics/stage_summary.rb b/lib/gitlab/cycle_analytics/stage_summary.rb
index b34baf5b081dd1299a3d8ccd28c2ab154a52897d..fc77bd86097243db5849a4c44f6f04bd54b098f7 100644
--- a/lib/gitlab/cycle_analytics/stage_summary.rb
+++ b/lib/gitlab/cycle_analytics/stage_summary.rb
@@ -16,7 +16,7 @@ module Gitlab
       private
 
       def serialize(summary_object)
-        AnalyticsSummarySerializer.new.represent(summary_object).as_json
+        AnalyticsSummarySerializer.new.represent(summary_object)
       end
     end
   end
diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
index a34731a5fcdb9ced2a6b42a584f3b1995fcff152..36c0260dbfe72c3eaeca392d4150342e41f9cf1a 100644
--- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
@@ -23,7 +23,7 @@ module Gitlab
       private
 
       def serialize(event)
-        AnalyticsBuildSerializer.new.represent(event['build']).as_json
+        AnalyticsBuildSerializer.new.represent(event['build'])
       end
     end
   end
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index bd3267e2a80ba54f31753d732b664b19e8838583..bd2f5d3615eba9c75e6087e4a9666e7e4f906edb 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -1,10 +1,11 @@
 require 'gitlab/email/handler/create_note_handler'
 require 'gitlab/email/handler/create_issue_handler'
+require 'gitlab/email/handler/unsubscribe_handler'
 
 module Gitlab
   module Email
     module Handler
-      HANDLERS = [CreateNoteHandler, CreateIssueHandler]
+      HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler]
 
       def self.for(mail, mail_key)
         HANDLERS.find do |klass|
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
index 7cccf465334f5ceacfc68b2809ca704e73ac7aab..3f6ace0311a6e54b39ff04215d25ca0a2255958b 100644
--- a/lib/gitlab/email/handler/base_handler.rb
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -9,52 +9,13 @@ module Gitlab
           @mail_key = mail_key
         end
 
-        def message
-          @message ||= process_message
-        end
-
-        def author
+        def can_execute?
           raise NotImplementedError
         end
 
-        def project
+        def execute
           raise NotImplementedError
         end
-
-        private
-
-        def validate_permission!(permission)
-          raise UserNotFoundError unless author
-          raise UserBlockedError if author.blocked?
-          raise ProjectNotFound unless author.can?(:read_project, project)
-          raise UserNotAuthorizedError unless author.can?(permission, project)
-        end
-
-        def process_message
-          message = ReplyParser.new(mail).execute.strip
-          add_attachments(message)
-        end
-
-        def add_attachments(reply)
-          attachments = Email::AttachmentUploader.new(mail).execute(project)
-
-          reply + attachments.map do |link|
-            "\n\n#{link[:markdown]}"
-          end.join
-        end
-
-        def verify_record!(record:, invalid_exception:, record_name:)
-          return if record.persisted?
-          return if record.errors.key?(:commands_only)
-
-          error_title = "The #{record_name} could not be created for the following reasons:"
-
-          msg = error_title + record.errors.full_messages.map do |error|
-            "\n\n- #{error}"
-          end.join
-
-          raise invalid_exception, msg
-        end
       end
     end
   end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 9f90a3ec2b2f70645ae27db6dbabf5557d31cfb8..b8ec9138c105deb984cd5ea03b8313dde86bd62b 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -5,6 +5,7 @@ module Gitlab
   module Email
     module Handler
       class CreateIssueHandler < BaseHandler
+        include ReplyProcessing
         attr_reader :project_path, :incoming_email_token
 
         def initialize(mail, mail_key)
@@ -33,7 +34,7 @@ module Gitlab
         end
 
         def project
-          @project ||= Project.find_with_namespace(project_path)
+          @project ||= Project.find_by_full_path(project_path)
         end
 
         private
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index 447c7a6a6b9465e63ea3c8ada822cef1d13b0233..d87ba427f4b7c06b658dbeaa8389aa72d9323367 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -1,10 +1,13 @@
 
 require 'gitlab/email/handler/base_handler'
+require 'gitlab/email/handler/reply_processing'
 
 module Gitlab
   module Email
     module Handler
       class CreateNoteHandler < BaseHandler
+        include ReplyProcessing
+
         def can_handle?
           mail_key =~ /\A\w+\z/
         end
@@ -24,6 +27,8 @@ module Gitlab
             record_name: 'comment')
         end
 
+        private
+
         def author
           sent_notification.recipient
         end
@@ -36,8 +41,6 @@ module Gitlab
           @sent_notification ||= SentNotification.for(mail_key)
         end
 
-        private
-
         def create_note
           Notes::CreateService.new(
             project,
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32c5caf93e8118af01dbe3b41962b4301b4d910f
--- /dev/null
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -0,0 +1,54 @@
+module Gitlab
+  module Email
+    module Handler
+      module ReplyProcessing
+        private
+
+        def author
+          raise NotImplementedError
+        end
+
+        def project
+          raise NotImplementedError
+        end
+
+        def message
+          @message ||= process_message
+        end
+
+        def process_message
+          message = ReplyParser.new(mail).execute.strip
+          add_attachments(message)
+        end
+
+        def add_attachments(reply)
+          attachments = Email::AttachmentUploader.new(mail).execute(project)
+
+          reply + attachments.map do |link|
+            "\n\n#{link[:markdown]}"
+          end.join
+        end
+
+        def validate_permission!(permission)
+          raise UserNotFoundError unless author
+          raise UserBlockedError if author.blocked?
+          raise ProjectNotFound unless author.can?(:read_project, project)
+          raise UserNotAuthorizedError unless author.can?(permission, project)
+        end
+
+        def verify_record!(record:, invalid_exception:, record_name:)
+          return if record.persisted?
+          return if record.errors.key?(:commands_only)
+
+          error_title = "The #{record_name} could not be created for the following reasons:"
+
+          msg = error_title + record.errors.full_messages.map do |error|
+            "\n\n- #{error}"
+          end.join
+
+          raise invalid_exception, msg
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..97d7a8d65ff95e168e8d684708c18c75167355ff
--- /dev/null
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -0,0 +1,32 @@
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+  module Email
+    module Handler
+      class UnsubscribeHandler < BaseHandler
+        def can_handle?
+          mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/
+        end
+
+        def execute
+          raise SentNotificationNotFoundError unless sent_notification
+          return unless sent_notification.unsubscribable?
+
+          noteable = sent_notification.noteable
+          raise NoteableNotFoundError unless noteable
+          noteable.unsubscribe(sent_notification.recipient)
+        end
+
+        private
+
+        def sent_notification
+          @sent_notification ||= SentNotification.for(reply_key)
+        end
+
+        def reply_key
+          mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '')
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 3cd515e4a3ab742fedafa36320aadee1167fa1fc..d3df3f1bca1875664beea61d7873d461b0845253 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -6,7 +6,7 @@ module Gitlab
 
     class << self
       def ref_name(ref)
-        ref.gsub(/\Arefs\/(tags|heads)\//, '')
+        ref.sub(/\Arefs\/(tags|heads)\//, '')
       end
 
       def branch_name(ref)
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index d32bdd86427cf963d17b23fd45a06e2b5ac931f9..6babea144c790fa5b5020f984b3a3320dcf581d2 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -30,11 +30,11 @@ module Gitlab
 
     def retrieve_project_and_type
       @type = :project
-      @project = Project.find_with_namespace(@repo_path)
+      @project = Project.find_by_full_path(@repo_path)
 
       if @repo_path.end_with?('.wiki') && !@project
         @type = :wiki
-        @project = Project.find_with_namespace(@repo_path.gsub(/\.wiki\z/, ''))
+        @project = Project.find_by_full_path(@repo_path.gsub(/\.wiki\z/, ''))
       end
     end
 
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
index 3f635be22ba0bf7739cb5615cec2a486f290243a..a55adc9b1c8accaee12a602322a0be9eb08bc551 100644
--- a/lib/gitlab/github_import/project_creator.rb
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -1,6 +1,8 @@
 module Gitlab
   module GithubImport
     class ProjectCreator
+      include Gitlab::CurrentSettings
+
       attr_reader :repo, :name, :namespace, :current_user, :session_data, :type
 
       def initialize(repo, name, namespace, current_user, session_data, type: 'github')
@@ -34,7 +36,7 @@ module Gitlab
       end
 
       def visibility_level
-        repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility
+        repo.private ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility
       end
 
       #
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index eb667a85b78fc390709d38613295515934272624..d679edec36b621c05875c5be34d135e5f305bade 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
     extend self
 
     # For every version update, the version history in import_export.md has to be kept up to date.
-    VERSION = '0.1.5'
+    VERSION = '0.1.6'
     FILENAME_LIMIT = 50
 
     def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 08ad3274b3803f4155a8df3efa72e14deb01511f..416194e57d7fd7a4dc26127c25edb1acc6ee98ac 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -39,7 +39,6 @@ project_tree:
       - :author
       - :events
     - :statuses
-  - :variables
   - :triggers
   - :deploy_keys
   - :services
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index b790733f4a75a3bba78db43504aaadd1bd1420bd..a09577ae48d3f4748aab1fe41d8eddb62bc6c031 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -1,13 +1,10 @@
 module Gitlab
   module ImportExport
     class MembersMapper
-      attr_reader :missing_author_ids
-
       def initialize(exported_members:, user:, project:)
-        @exported_members = exported_members
+        @exported_members = user.admin? ? exported_members : []
         @user = user
         @project = project
-        @missing_author_ids = []
 
         # This needs to run first, as second call would be from #map
         # which means project members already exist.
@@ -39,12 +36,13 @@ module Gitlab
 
       def missing_keys_tracking_hash
         Hash.new do |_, key|
-          @missing_author_ids << key
           default_user_id
         end
       end
 
       def ensure_default_member!
+        @project.project_members.destroy_all
+
         ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true)
       end
 
@@ -64,7 +62,7 @@ module Gitlab
       end
 
       def find_project_user_query(member)
-        user_arel[:username].eq(member['user']['username']).or(user_arel[:email].eq(member['user']['email']))
+        user_arel[:email].eq(member['user']['email']).or(user_arel[:username].eq(member['user']['username']))
       end
 
       def user_arel
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 7a649f28340f848fa1a9479767d4fb235f3ea780..0319d7707a8bc417eda04cbc7baf63193a43b750 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -4,7 +4,6 @@ module Gitlab
       OVERRIDES = { snippets: :project_snippets,
                     pipelines: 'Ci::Pipeline',
                     statuses: 'commit_status',
-                    variables: 'Ci::Variable',
                     triggers: 'Ci::Trigger',
                     builds: 'Ci::Build',
                     hooks: 'ProjectHook',
@@ -14,7 +13,7 @@ module Gitlab
                     priorities: :label_priorities,
                     label: :project_label }.freeze
 
-      USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id].freeze
+      USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id resolved_by_id].freeze
 
       PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze
 
@@ -24,6 +23,8 @@ module Gitlab
 
       EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels].freeze
 
+      TOKEN_RESET_MODELS = %w[Ci::Trigger Ci::Build ProjectHook].freeze
+
       def self.create(*args)
         new(*args).create
       end
@@ -61,7 +62,9 @@ module Gitlab
         update_project_references
 
         handle_group_label if group_label?
-        reset_ci_tokens if @relation_name == 'Ci::Trigger'
+        reset_tokens!
+        remove_encrypted_attributes!
+
         @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
         set_st_diffs if @relation_name == :merge_request_diff
       end
@@ -80,17 +83,13 @@ module Gitlab
       # is left.
       def set_note_author
         old_author_id = @relation_hash['author_id']
-
-        # Users with admin access can map users
-        @relation_hash['author_id'] = admin_user? ? @members_mapper.map[old_author_id] : @members_mapper.default_user_id
-
         author = @relation_hash.delete('author')
 
-        update_note_for_missing_author(author['name']) if missing_author?(old_author_id)
+        update_note_for_missing_author(author['name']) unless has_author?(old_author_id)
       end
 
-      def missing_author?(old_author_id)
-        !admin_user? || @members_mapper.missing_author_ids.include?(old_author_id)
+      def has_author?(old_author_id)
+        admin_user? && @members_mapper.map.keys.include?(old_author_id)
       end
 
       def missing_author_note(updated_at, author_name)
@@ -144,11 +143,22 @@ module Gitlab
         end
       end
 
-      def reset_ci_tokens
-        return unless Gitlab::ImportExport.reset_tokens?
+      def reset_tokens!
+        return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name.to_s)
 
         # If we import/export a project to the same instance, tokens will have to be reset.
-        @relation_hash['token'] = nil
+        # We also have to reset them to avoid issues when the gitlab secrets file cannot be copied across.
+        relation_class.attribute_names.select { |name| name.include?('token') }.each do |token|
+          @relation_hash[token] = nil
+        end
+      end
+
+      def remove_encrypted_attributes!
+        return unless relation_class.respond_to?(:encrypted_attributes) && relation_class.encrypted_attributes.any?
+
+        relation_class.encrypted_attributes.each_key do |key|
+          @relation_hash[key.to_s] = nil
+        end
       end
 
       def relation_class
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 45958710c13d26410a9c1351c580a9f98387dbc9..52276cbcd9a02f6654677edbcd1b99c2a02fd67f 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -5,8 +5,6 @@
 #
 module Gitlab
   module ImportSources
-    extend CurrentSettings
-
     ImportSource = Struct.new(:name, :title, :importer)
 
     ImportTable = [
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 801dfde9a368f90edefa2f7d20331c6ec670e2cf..b91012d6405b182ec41ccc16b3772b0b14040748 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,5 +1,6 @@
 module Gitlab
   module IncomingEmail
+    UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
     WILDCARD_PLACEHOLDER = '%{key}'.freeze
 
     class << self
@@ -18,7 +19,11 @@ module Gitlab
       end
 
       def reply_address(key)
-        config.address.gsub(WILDCARD_PLACEHOLDER, key)
+        config.address.sub(WILDCARD_PLACEHOLDER, key)
+      end
+
+      def unsubscribe_address(key)
+        config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}")
       end
 
       def key_from_address(address)
@@ -49,7 +54,7 @@ module Gitlab
         return nil unless wildcard_address
 
         regex = Regexp.escape(wildcard_address)
-        regex = regex.gsub(Regexp.escape('%{key}'), "(.+)")
+        regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)')
         Regexp.new(regex).freeze
       end
     end
diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8db91d25a4bc586b97072eff1f411fb235cc087c
--- /dev/null
+++ b/lib/gitlab/job_waiter.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  # JobWaiter can be used to wait for a number of Sidekiq jobs to complete.
+  class JobWaiter
+    # The sleep interval between checking keys, in seconds.
+    INTERVAL = 0.1
+
+    # jobs - The job IDs to wait for.
+    def initialize(jobs)
+      @jobs = jobs
+    end
+
+    # Waits for all the jobs to be completed.
+    #
+    # timeout - The maximum amount of seconds to block the caller for. This
+    #           ensures we don't indefinitely block a caller in case a job takes
+    #           long to process, or is never processed.
+    def wait(timeout = 60)
+      start = Time.current
+
+      while (Time.current - start) <= timeout
+        break if SidekiqStatus.all_completed?(@jobs)
+
+        sleep(INTERVAL) # to not overload Redis too much.
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 6bdf3db9cb88892b01053fd001bf750e8a4b6fab..db325c00705777f824bed733a2e43201782be28c 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -71,6 +71,14 @@ module Gitlab
       )
     end
 
+    def single_commit_result?
+      commits_count == 1 && total_result_count == 1
+    end
+
+    def total_result_count
+      issues_count + merge_requests_count + milestones_count + notes_count + blobs_count + wiki_blobs_count + commits_count
+    end
+
     private
 
     def blobs
@@ -114,7 +122,25 @@ module Gitlab
     end
 
     def commits
-      @commits ||= project.repository.find_commits_by_message(query)
+      @commits ||= find_commits(query)
+    end
+
+    def find_commits(query)
+      return [] unless Ability.allowed?(@current_user, :download_code, @project)
+
+      commits = find_commits_by_message(query)
+      commit_by_sha = find_commit_by_sha(query)
+      commits |= [commit_by_sha] if commit_by_sha
+      commits
+    end
+
+    def find_commits_by_message(query)
+      project.repository.find_commits_by_message(query)
+    end
+
+    def find_commit_by_sha(query)
+      key = query.strip
+      project.repository.commit(key) if Commit.valid_hash?(key)
     end
 
     def project_ids_relation
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
index 786e1d49f5e048da7fa2a79df2abdf3f36d314aa..ef42b0557e02e2547bca09c3140bd305b1ea0fb6 100644
--- a/lib/gitlab/request_profiler/middleware.rb
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -1,5 +1,4 @@
 require 'ruby-prof'
-require_dependency 'gitlab/request_profiler'
 
 module Gitlab
   module RequestProfiler
@@ -20,7 +19,7 @@ module Gitlab
         header_token = env['HTTP_X_PROFILE_TOKEN']
         return unless header_token.present?
 
-        profile_token = RequestProfiler.profile_token
+        profile_token = Gitlab::RequestProfiler.profile_token
         return unless profile_token.present?
 
         header_token == profile_token
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 35212992698ccad552511de4961c24d0c616810e..c9c65f76f4bb263724dab2d7736160b921f3fd9b 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -43,6 +43,10 @@ module Gitlab
       @milestones_count ||= milestones.count
     end
 
+    def single_commit_result?
+      false
+    end
+
     private
 
     def projects
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/shell.rb
similarity index 100%
rename from lib/gitlab/backend/shell.rb
rename to lib/gitlab/shell.rb
diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/shell_adapter.rb
similarity index 100%
rename from lib/gitlab/backend/shell_adapter.rb
rename to lib/gitlab/shell_adapter.rb
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aadc401ff8d3f7288efa1ae6617d02495b6238fb
--- /dev/null
+++ b/lib/gitlab/sidekiq_status.rb
@@ -0,0 +1,66 @@
+module Gitlab
+  # The SidekiqStatus module and its child classes can be used for checking if a
+  # Sidekiq job has been processed or not.
+  #
+  # To check if a job has been completed, simply pass the job ID to the
+  # `completed?` method:
+  #
+  #     job_id = SomeWorker.perform_async(...)
+  #
+  #     if Gitlab::SidekiqStatus.completed?(job_id)
+  #       ...
+  #     end
+  #
+  # For each job ID registered a separate key is stored in Redis, making lookups
+  # much faster than using Sidekiq's built-in job finding/status API. These keys
+  # expire after a certain period of time to prevent storing too many keys in
+  # Redis.
+  module SidekiqStatus
+    STATUS_KEY = 'gitlab-sidekiq-status:%s'.freeze
+
+    # The default time (in seconds) after which a status key is expired
+    # automatically. The default of 30 minutes should be more than sufficient
+    # for most jobs.
+    DEFAULT_EXPIRATION = 30.minutes.to_i
+
+    # Starts tracking of the given job.
+    #
+    # jid - The Sidekiq job ID
+    # expire - The expiration time of the Redis key.
+    def self.set(jid, expire = DEFAULT_EXPIRATION)
+      Sidekiq.redis do |redis|
+        redis.set(key_for(jid), 1, ex: expire)
+      end
+    end
+
+    # Stops the tracking of the given job.
+    #
+    # jid - The Sidekiq job ID to remove.
+    def self.unset(jid)
+      Sidekiq.redis do |redis|
+        redis.del(key_for(jid))
+      end
+    end
+
+    # Returns true if all the given job have been completed.
+    #
+    # jids - The Sidekiq job IDs to check.
+    #
+    # Returns true or false.
+    def self.all_completed?(jids)
+      keys = jids.map { |jid| key_for(jid) }
+
+      responses = Sidekiq.redis do |redis|
+        redis.pipelined do
+          keys.each { |key| redis.exists(key) }
+        end
+      end
+
+      responses.all? { |value| !value }
+    end
+
+    def self.key_for(jid)
+      STATUS_KEY % jid
+    end
+  end
+end
diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..779a9998b224024220a5881d7197cce0dee83f9c
--- /dev/null
+++ b/lib/gitlab/sidekiq_status/client_middleware.rb
@@ -0,0 +1,10 @@
+module Gitlab
+  module SidekiqStatus
+    class ClientMiddleware
+      def call(_, job, _, _)
+        SidekiqStatus.set(job['jid'])
+        yield
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/sidekiq_status/server_middleware.rb b/lib/gitlab/sidekiq_status/server_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31dfa46ff9da75f500dfb89f02678d761a0fa01a
--- /dev/null
+++ b/lib/gitlab/sidekiq_status/server_middleware.rb
@@ -0,0 +1,13 @@
+module Gitlab
+  module SidekiqStatus
+    class ServerMiddleware
+      def call(worker, job, queue)
+        ret = yield
+
+        SidekiqStatus.unset(job['jid'])
+
+        ret
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index f3567f3ef8505d4dd44219ab1f3e76cf7bd4a846..e78d0c34a02e5972ceafc7c43dd3e77311d4d01c 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -61,7 +61,7 @@ module Gitlab
         "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}),
         "Install gems" => %W(bundle),
         "Migrate DB" => %W(bundle exec rake db:migrate),
-        "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile),
+        "Recompile assets" => %W(bundle exec rake gitlab:assets:clean gitlab:assets:compile),
         "Clear cache" => %W(bundle exec rake cache:clear)
       }
     end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 6c7e673fb9fbf332714a07d18a4c26fba75c0edb..6ce9b22929435512b83b58d317e0a890ac5fed43 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -35,7 +35,9 @@ module Gitlab
         return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
 
         access_levels = project.protected_branches.matching(ref).map(&:push_access_levels).flatten
-        access_levels.any? { |access_level| access_level.check_access(user) }
+        has_access = access_levels.any? { |access_level| access_level.check_access(user) }
+
+        has_access || !project.repository.branch_exists?(ref) && can_merge_to_branch?(ref)
       else
         user.can?(:push_code, project)
       end
diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb
index 83c8ba5c1cfa8553b6791fece235beb27ed8e92e..dbfe0941e4d5390a71c1264b3b22415f4bf416bc 100644
--- a/lib/gitlab/view/presenter/base.rb
+++ b/lib/gitlab/view/presenter/base.rb
@@ -1,6 +1,8 @@
 module Gitlab
   module View
     module Presenter
+      CannotOverrideMethodError = Class.new(StandardError)
+
       module Base
         extend ActiveSupport::Concern
 
diff --git a/lib/gitlab/view/presenter/delegated.rb b/lib/gitlab/view/presenter/delegated.rb
index f4d330c590e338f6f67e7f80f7c5776807b865b4..387ff0f5d43e8029dbd030be392b0ada9693253a 100644
--- a/lib/gitlab/view/presenter/delegated.rb
+++ b/lib/gitlab/view/presenter/delegated.rb
@@ -8,6 +8,10 @@ module Gitlab
           @subject = subject
 
           attributes.each do |key, value|
+            if subject.respond_to?(key)
+              raise CannotOverrideMethodError.new("#{subject} already respond to #{key}!")
+            end
+
             define_singleton_method(key) { value }
           end
 
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 9462f3368e672ae348bcbc2df6a6cf62936c3e79..c7953af29ddb3815d3fa76dd28ea84e62ae0c3d1 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -11,6 +11,7 @@ module Gitlab
     included do
       scope :public_only,               -> { where(visibility_level: PUBLIC) }
       scope :public_and_internal_only,  -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
+      scope :non_public_only,           -> { where.not(visibility_level: PUBLIC) }
 
       scope :public_to_user, -> (user) { user && !user.external ? public_and_internal_only : public_only }
     end
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index ec2903b7ec6c1daf7bdeea3320155da2c8149c81..e55c0d6ac49c6f7f837282e79f83682df9d93dd6 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -8,21 +8,31 @@ module Mattermost
       @user = user
     end
 
-    private
-
     def with_session(&blk)
       Mattermost::Session.new(user).with_session(&blk)
     end
 
-    def json_get(path, options = {})
+    private
+
+    # Should be used in a session manually
+    def get(session, path, options = {})
+      json_response session.get(path, options)
+    end
+
+    # Should be used in a session manually
+    def post(session, path, options = {})
+      json_response session.post(path, options)
+    end
+
+    def session_get(path, options = {})
       with_session do |session|
-        json_response session.get(path, options)
+        get(session, path, options)  
       end
     end
 
-    def json_post(path, options = {})
+    def session_post(path, options = {})
       with_session do |session|
-        json_response session.post(path, options)
+        post(session, path, options)
       end
     end
 
diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb
index d1e4bb0eccf83cc0ff4d1af3f4be6e1edbd19ee3..33e450d7f0a99dc8b390936a3f781578a7079da0 100644
--- a/lib/mattermost/command.rb
+++ b/lib/mattermost/command.rb
@@ -1,7 +1,7 @@
 module Mattermost
   class Command < Client
     def create(params)
-      response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create",
+      response = session_post("/api/v3/teams/#{params[:team_id]}/commands/create",
         body: params.to_json)
 
       response['token']
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index 784eca6ab5a61e5f048515ac296873f970039ba7..09dfd082b3a5e4fb211219a13a7445de1105310f 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -1,7 +1,7 @@
 module Mattermost
   class Team < Client
     def all
-      json_get('/api/v3/teams/all')
+      session_get('/api/v3/teams/all')
     end
   end
 end
diff --git a/lib/rouge/lexers/plantuml.rb b/lib/rouge/lexers/plantuml.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d5700b7f6dbd0b028908b78096d16011389a4e0
--- /dev/null
+++ b/lib/rouge/lexers/plantuml.rb
@@ -0,0 +1,21 @@
+module Rouge
+  module Lexers
+    class Plantuml < Lexer
+      title "A passthrough lexer used for PlantUML input"
+      desc "A boring lexer that doesn't highlight anything"
+
+      tag 'plantuml'
+      mimetypes 'text/plain'
+
+      default_options token: 'Text'
+
+      def token
+        @token ||= Token[option :token]
+      end
+
+      def stream_tokens(string, &b)
+        yield self.token, string
+      end
+    end
+  end
+end
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
index adea4c7a747db27ca2b3f03cff55ece93e0506dc..ab46c47d8f5912b9df27a3f4585554342be09897 100755
--- a/lib/support/deploy/deploy.sh
+++ b/lib/support/deploy/deploy.sh
@@ -31,8 +31,8 @@ echo 'Deploy: Bundle and migrate'
 sudo -u git -H bundle --without aws development test mysql --deployment
 
 sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:assets:clean RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production
 sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
 
 # return stashed changes (if necessary)
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
new file mode 100644
index 0000000000000000000000000000000000000000..5d884bf9f667c8ee1a9c5255bd6ec6d5c5980108
--- /dev/null
+++ b/lib/tasks/gitlab/assets.rake
@@ -0,0 +1,47 @@
+namespace :gitlab do
+  namespace :assets do
+    desc 'GitLab | Assets | Compile all frontend assets'
+    task :compile do
+      Rake::Task['assets:precompile'].invoke
+      Rake::Task['gitlab:assets:fix_urls'].invoke
+    end
+
+    desc 'GitLab | Assets | Clean up old compiled frontend assets'
+    task :clean do
+      Rake::Task['assets:clean'].invoke
+    end
+
+    desc 'GitLab | Assets | Remove all compiled frontend assets'
+    task :purge do
+      Rake::Task['assets:clobber'].invoke
+    end
+
+    desc 'GitLab | Assets | Fix all absolute url references in CSS'
+    task :fix_urls do
+      css_files = Dir['public/assets/*.css']
+      css_files.each do | file |
+        # replace url(/assets/*) with url(./*)
+        puts "Fixing #{file}"
+        system "sed", "-i", "-e", 's/url(\([\"\']\?\)\/assets\//url(\1.\//g', file
+
+        # rewrite the corresponding gzip file (if it exists)
+        gzip = "#{file}.gz"
+        if File.exist?(gzip)
+          puts "Fixing #{gzip}"
+
+          FileUtils.rm(gzip)
+          mtime = File.stat(file).mtime
+
+          File.open(gzip, 'wb+') do |f|
+            gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
+            gz.mtime = mtime
+            gz.write IO.binread(file)
+            gz.close
+
+            File.utime(mtime, mtime, f.path)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 4a696a52b4d190ac19e6a0e8669407fb940eceb4..967f630ef203379e282525c287268f21f8bd5704 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -58,7 +58,7 @@ namespace :gitlab do
               sub(%r{^/*}, '').
               chomp('.git').
               chomp('.wiki')
-            next if Project.find_with_namespace(repo_with_namespace)
+            next if Project.find_by_full_path(repo_with_namespace)
             new_path = path + move_suffix
             puts path.inspect + ' -> ' + new_path.inspect
             File.rename(path, new_path)
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index a2eca74a3c833bddde99d6b9a2e704eb65759182..b4015f5238e548f7d0ed128402ce52b0ad3cb8ff 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -29,7 +29,7 @@ namespace :gitlab do
             next
           end
 
-          project = Project.find_with_namespace(path)
+          project = Project.find_by_full_path(path)
 
           if project
             puts " * #{project.name} (#{repo_path}) exists"
@@ -63,7 +63,7 @@ namespace :gitlab do
 
             if project.persisted?
               puts " * Created #{project.name} (#{repo_path})".color(:green)
-              ProjectCacheWorker.perform(project.id)
+              ProjectCacheWorker.perform_async(project.id)
             else
               puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
               puts "   Errors: #{project.errors.messages}".color(:red)
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index dffea8ed155e635e51abf339c695570931f52b8a..f7c831892ee28e89eb51ffcc9c37450c5559640e 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -11,8 +11,10 @@ namespace :gitlab do
       gem_version = run_command(%W(gem --version))
       # check Bundler version
       bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
-      # check Bundler version
+      # check Rake version
       rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
+      # check redis version
+      redis_version = run_and_match(%W(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
 
       puts ""
       puts "System information".color(:yellow)
@@ -24,6 +26,7 @@ namespace :gitlab do
       puts "Gem Version:\t#{gem_version || "unknown".color(:red)}"
       puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
       puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
+      puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
       puts "Sidekiq Version:#{Sidekiq::VERSION}"
 
 
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
index 7e2a6668e592d48a6766eda9fa10412a15d55462..f2e12d850454472a4be54e7e8d4cbcf53e1c2eaf 100644
--- a/lib/tasks/gitlab/sidekiq.rake
+++ b/lib/tasks/gitlab/sidekiq.rake
@@ -7,7 +7,7 @@ namespace :gitlab do
       unless args.project.present?
         abort "Please specify the project you want to drop PostReceive jobs for:\n  rake gitlab:sidekiq:drop_post_receive[group/project]"
       end
-      project_path = Project.find_with_namespace(args.project).repository.path_to_repo
+      project_path = Project.find_by_full_path(args.project).repository.path_to_repo
 
       Sidekiq.redis do |redis|
         unless redis.exists(QUEUE)
diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4a63c760744cf245d35c14bddbff535731a91c55
--- /dev/null
+++ b/rubocop/cop/gem_fetcher.rb
@@ -0,0 +1,28 @@
+module RuboCop
+  module Cop
+    # Cop that checks for all gems specified in the Gemfile, and will
+    # alert if any gem is to be fetched not from the RubyGems index.
+    # This enforcement is done so as to minimize external build
+    # dependencies and build times.
+    class GemFetcher < RuboCop::Cop::Cop
+      MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
+
+      GIT_KEYS = [:git, :github]
+
+      def on_send(node)
+        file_path = node.location.expression.source_buffer.name
+        return unless file_path.end_with?("Gemfile")
+
+        func_name = node.children[1]
+        return unless func_name == :gem
+
+        node.children.last.each_node(:pair) do |pair|
+          key_name = pair.children[0].children[0].to_sym
+          if GIT_KEYS.include?(key_name)
+            add_offense(node, :selector)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 7922e19768b6a75d3e788e5e4f4ad026f15469bc..7f20754ee51652c1b0ce7dc7f702d0c39961ce32 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,3 +1,4 @@
 require_relative 'migration_helpers'
 require_relative 'cop/migration/add_index'
 require_relative 'cop/migration/column_with_default'
+require_relative 'cop/gem_fetcher'
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 602de72d23f23daafbc85bf6268da7cf97f76d89..84db26a958a165ea5cf8d1f5e6e7ebee64c6315e 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Admin::GroupsController do
   let(:group) { create(:group) }
-  let(:project) { create(:project, namespace: group) }
+  let(:project) { create(:empty_project, namespace: group) }
   let(:admin) { create(:admin) }
 
   before do
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 8eaacef2024c1ea7a527d950181b6fd402b6bb4f..2c35d394b74f90bcf1f46e01c228d1dd9512ea48 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Admin::ProjectsController do
-  let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+  let!(:project) { create(:empty_project, :public) }
 
   before do
     sign_in(create(:admin))
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index ea2fd90a9b013d02983d24cc7135794709a10b87..7d2f6dd9d0a5f0eadb682441f1ad87afb1247ab7 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe AutocompleteController do
-  let!(:project) { create(:project) }
+  let!(:project) { create(:empty_project) }
   let!(:user) { create(:user) }
 
   context 'GET users' do
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
index 465013231f9de7b3ca1596601f2f7ce13bb1ddbd..2fcb4a6a528db118be19a0f3c35ae62aa2a66eeb 100644
--- a/spec/controllers/blob_controller_spec.rb
+++ b/spec/controllers/blob_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::BlobController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
index 5022a3e2c80fc31fd81a95a3975172de6de065a0..86f01f437a2b071adcc0036239fbb0ebdc214e63 100644
--- a/spec/controllers/ci/projects_controller_spec.rb
+++ b/spec/controllers/ci/projects_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Ci::ProjectsController do
   let(:visibility) { :public }
-  let!(:project) { create(:project, visibility, ci_id: 1) }
+  let!(:project) { create(:empty_project, visibility, ci_id: 1) }
   let(:ci_id) { project.ci_id }
 
   describe '#index' do
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 19fbc2f7748d297a80f266166ebdde89e08c9e37..79ef3a1adadab1c60ed30520d93af697e1a1c6b3 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Dashboard::TodosController do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:todo_service) { TodoService.new }
 
   describe 'GET #index' do
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9dceeca168d047dd5e2db0df5fd2b142c34bf07c
--- /dev/null
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Explore::ProjectsController do
+  describe 'GET #trending' do
+    context 'sorting by update date' do
+      let(:project1) { create(:empty_project, :public, updated_at: 3.days.ago) }
+      let(:project2) { create(:empty_project, :public, updated_at: 1.day.ago) }
+
+      before do
+        create(:trending_project, project: project1)
+        create(:trending_project, project: project2)
+      end
+
+      it 'sorts by last updated' do
+        get :trending, sort: 'updated_desc'
+
+        expect(assigns(:projects)).to eq [project2, project1]
+      end
+
+      it 'sorts by oldest updated' do
+        get :trending, sort: 'updated_asc'
+
+        expect(assigns(:projects)).to eq [project1, project2]
+      end
+    end
+  end
+end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 8c52f615b8ba06076e11ba44d56882bbe39a47ae..6e4b5f78e334773ace89d943e7a5449cdb7c3df3 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Groups::MilestonesController do
   let(:group) { create(:group) }
-  let(:project) { create(:project, group: group) }
+  let(:project) { create(:empty_project, group: group) }
   let(:project2) { create(:empty_project, group: group) }
   let(:user)    { create(:user) }
   let(:title) { '肯定不是中文的问题' }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 98dfb3e52165fe35777f509171c4f7194de66989..cad82a34fb0e92d1e127dda7361b6fed9773f25f 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe GroupsController do
   let(:user) { create(:user) }
   let(:group) { create(:group) }
-  let(:project) { create(:project, namespace: group) }
+  let(:project) { create(:empty_project, namespace: group) }
   let!(:group_member) { create(:group_member, group: group, user: user) }
 
   describe 'GET #index' do
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 56ecf2bb644c4164a718a03afcabd2c158fdb912..cfe18dd4b6c86c859bee9628aff0eb4cc5fab8b1 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -1,10 +1,16 @@
 require 'spec_helper'
 
 describe HealthCheckController do
+  include StubENV
+
   let(:token) { current_application_settings.health_check_access_token }
   let(:json_response) { JSON.parse(response.body) }
   let(:xml_response) { Hash.from_xml(response.body)['hash'] }
 
+  before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+  end
+
   describe 'GET #index' do
     context 'when services are up but NO access token' do
       it 'returns a not found page' do
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index ce7c0b334ee071358b935322165ed9874c6bdc2d..fa4cc0ebbe0ca9ff7af1f3265740e9c4ef4eaf95 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -52,7 +52,7 @@ describe Import::BitbucketController do
     end
 
     it "assigns variables" do
-      @project = create(:project, import_type: 'bitbucket', creator_id: user.id)
+      @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id)
       allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
 
       get :status
@@ -63,7 +63,7 @@ describe Import::BitbucketController do
     end
 
     it "does not show already added project" do
-      @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
+      @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
       allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
 
       get :status
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index 5f0f6dea82107ef42aec875c79fae55801284f6b..fffbc805335e39fb20302342fadb94db21c547fa 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -16,7 +16,7 @@ describe Import::FogbugzController do
     end
 
     it 'assigns variables' do
-      @project = create(:project, import_type: 'fogbugz', creator_id: user.id)
+      @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id)
       stub_client(repos: [@repo])
 
       get :status
@@ -26,7 +26,7 @@ describe Import::FogbugzController do
     end
 
     it 'does not show already added project' do
-      @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
+      @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
       stub_client(repos: [@repo])
 
       get :status
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 6f75ebb16c818f61ff5f80d45123fe55ac430b8e..3f73ea000ae6618d5984f1b20a972de7b9e22d0a 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -36,7 +36,7 @@ describe Import::GitlabController do
     end
 
     it "assigns variables" do
-      @project = create(:project, import_type: 'gitlab', creator_id: user.id)
+      @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id)
       stub_client(projects: [@repo])
 
       get :status
@@ -46,7 +46,7 @@ describe Import::GitlabController do
     end
 
     it "does not show already added project" do
-      @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
+      @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
       stub_client(projects: [@repo])
 
       get :status
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 4241db6e771a83e9ff0322e57871e7fe3161de28..c96fb90f70e81ea970a560a7ee968d0b1483118e 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -27,7 +27,7 @@ describe Import::GoogleCodeController do
     end
 
     it "assigns variables" do
-      @project = create(:project, import_type: 'google_code', creator_id: user.id)
+      @project = create(:empty_project, import_type: 'google_code', creator_id: user.id)
       stub_client(repos: [@repo], incompatible_repos: [])
 
       get :status
@@ -38,7 +38,7 @@ describe Import::GoogleCodeController do
     end
 
     it "does not show already added project" do
-      @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim')
+      @project = create(:empty_project, import_type: 'google_code', creator_id: user.id, import_source: 'vim')
       stub_client(repos: [@repo], incompatible_repos: [])
 
       get :status
diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb
index 79b819a137706e4d6584481b3319426aba6ba1c7..9e3a31e1a6b9fb2c3ec8dbda0260e797cd873674 100644
--- a/spec/controllers/notification_settings_controller_spec.rb
+++ b/spec/controllers/notification_settings_controller_spec.rb
@@ -93,7 +93,7 @@ describe NotificationSettingsController do
     end
 
     context 'not authorized' do
-      let(:private_project) { create(:project, :private) }
+      let(:private_project) { create(:empty_project, :private) }
       before { sign_in(user) }
 
       it 'returns 404' do
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index f5ea097af8b5a22fb67690d93a4150c3da0ce4f2..8b71d6518bb09eb533ea0dc03cbc19bae0b4ac9c 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::AvatarsController do
-  let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+  let(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index 4402ca43c65ccbb4bfed5d129494f2b76498eec9..addc5e7ec33c3fbe51b91214e00d45b5cbdfe146 100644
--- a/spec/controllers/projects/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::BlameController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index f35c5d992d95a64b9c28a5feca1528de36892c81..b36d0e6933091ca9e7ae4259d6a057d7ce1ae2e2 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 describe Projects::BlobController do
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index b88586b8678352b0016991ade107d9636dd4fe54..9de038767553aa035f92a2b2a27e33613aa4ecde 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::BranchesController do
-  let(:project)   { create(:project) }
+  let(:project)   { create(:project, :repository) }
   let(:user)      { create(:user) }
   let(:developer) { create(:user) }
 
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 0fa06a38d2a02b3fc2f0e3c60d22ef33aa8a8a04..ebd2d0e092b9df5ffbbe04c6d3ee697adfc05739 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,10 +1,9 @@
 require 'spec_helper'
 
 describe Projects::CommitController do
-  let(:project)  { create(:project) }
+  let(:project)  { create(:project, :repository) }
   let(:user)     { create(:user) }
   let(:commit)   { project.commit("master") }
-  let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) }
   let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
   let(:master_pickable_commit)  { project.commit(master_pickable_sha) }
 
@@ -322,11 +321,26 @@ describe Projects::CommitController do
     end
 
     context 'when the commit exists' do
-      context 'when the commit has one or more pipelines' do
-        it 'shows pipelines' do
-          get_pipelines(id: commit.id)
+      context 'when the commit has pipelines' do
+        before do
+          create(:ci_pipeline, project: project, sha: commit.id)
+        end
+
+        context 'when rendering a HTML format' do
+          it 'shows pipelines' do
+            get_pipelines(id: commit.id)
+
+            expect(response).to be_ok
+          end
+        end
 
-          expect(response).to be_ok
+        context 'when rendering a JSON format' do
+          it 'responds with serialized pipelines' do
+            get_pipelines(id: commit.id, format: :json)
+
+            expect(response).to be_ok
+            expect(JSON.parse(response.body)).not_to be_empty
+          end
         end
       end
     end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 1ac7e03a2db1022e5bdc740a160af7202a9c5df6..54b8d1108a56e773a329d3efd6d188520e11c5b5 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::CommitsController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index b03c4b52de67c451a9c6266ab0967c714570732f..e811c76fb31f21fd7cd156140f9cdb1c81df8938 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::CompareController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:ref_from) { "improve%2Fawesome" }
   let(:ref_to) { "feature" }
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index a971adf0539017c7e9ff96562e8bdc442320e99c..6a6d71a16ee77f76b65ee7a26bcf86c88bbb01cb 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::CycleAnalyticsController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index ff617fea8476f23b636c0e5c6e68fb2ecf5156fa..79ab364a6f338e1f953ef099ea583878cde45256 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
 describe Projects::DiscussionsController do
-  let(:user)    { create(:user) }
-  let(:project) { create(:project) }
-  let(:merge_request) { create(:merge_request, source_project: project) }
+  let(:user) { create(:user) }
+  let(:merge_request) { create(:merge_request) }
+  let(:project) { merge_request.source_project }
   let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
   let(:discussion) { note.discussion }
 
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 038dfeb846698ac9ceb56581e8dd095c536b53fc..a4884256c928fa0f13b7a58c4048553dcd74812f 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::FindFileController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 028ea067a97bee639765e10835d49553fcb9f18d..a867668d97b4b037feb72c81a2b39749a04e4540 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Projects::ForksController do
   let(:user) { create(:user) }
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:forked_project) { Projects::ForkService.new(project, user).execute }
   let(:group) { create(:group, owner: forked_project.creator) }
 
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 74e6603b0cb5a5bf75e4229f78b5630007a1c2b3..bbe8e4bf6b243e507d9ad010df46d9de37649e82 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::GraphsController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 17dc101b7ee63a4e9be6db33d3b77b999d2d8d64..a976a9c27abb67f246f6ab50a978b38b51aa5048 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Projects::GroupLinksController do
   let(:group) { create(:group, :private) }
   let(:group2) { create(:group, :private) }
-  let(:project) { create(:project, :private, group: group2) }
+  let(:project) { create(:empty_project, :private, group: group2) }
   let(:user) { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index b5987a83df0affcea451c4d78e8b718d97eda9af..5f27f336f7213220b61bd6950fd46f0ab2173d2a 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -98,7 +98,7 @@ describe Projects::IssuesController do
       end
 
       it 'fills in an issue for a merge request' do
-        project_with_repository = create(:project)
+        project_with_repository = create(:project, :repository)
         project_with_repository.team << [user, :developer]
         mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
 
@@ -124,7 +124,7 @@ describe Projects::IssuesController do
 
   describe 'PUT #update' do
     context 'when moving issue to another private project' do
-      let(:another_project) { create(:project, :private) }
+      let(:another_project) { create(:empty_project, :private) }
 
       before do
         sign_in(user)
@@ -466,7 +466,7 @@ describe Projects::IssuesController do
     context "when the user is owner" do
       let(:owner)     { create(:user) }
       let(:namespace) { create(:namespace, owner: owner) }
-      let(:project)   { create(:project, namespace: namespace) }
+      let(:project)   { create(:empty_project, namespace: namespace) }
 
       before { sign_in(owner) }
 
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index ec6cea5c0f4332e6d0ca2a73f16d3727537d71b9..3e0326dd47d79cfa4d12e6a633cb6d7af5f2b271 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -112,4 +112,49 @@ describe Projects::LabelsController do
       post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param
     end
   end
+
+  describe 'POST #promote' do
+    let!(:promoted_label_name) { "Promoted Label" }
+    let!(:label_1) { create(:label, title: promoted_label_name, project: project) }
+
+    context 'not group owner' do
+      it 'denies access' do
+        post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+        expect(response).to have_http_status(404)
+      end
+    end
+
+    context 'group owner' do
+      before do
+        GroupMember.add_users_to_group(group, [user], :owner)
+      end
+
+      it 'gives access' do
+        post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+        expect(response).to redirect_to(namespace_project_labels_path)
+      end
+
+      it 'promotes the label' do
+        post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+        expect(Label.where(id: label_1.id)).to be_empty
+        expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil
+      end
+
+      context 'service raising InvalidRecord' do
+        before do
+          expect_any_instance_of(Labels::PromoteService).to receive(:execute) do |label|
+            raise ActiveRecord::RecordInvalid.new(label_1)
+          end
+        end
+
+        it 'returns to label list' do
+          post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+          expect(response).to redirect_to(namespace_project_labels_path)
+        end
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 2ae635a1244b614ab0e4d5849944fccd84f5fe27..cae733f0cfb8ea9591f4bab168ae72041c8ead6d 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -13,13 +13,13 @@ describe Projects::MattermostsController do
     before do
       allow_any_instance_of(MattermostSlashCommandsService).
         to receive(:list_teams).and_return([])
+    end
 
+    it 'accepts the request' do
       get(:new,
           namespace_id: project.namespace.to_param,
           project_id: project.to_param)
-    end
 
-    it 'accepts the request' do
       expect(response).to have_http_status(200)
     end
   end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 7ea3ea4f37689a755d6428bf9d763520f5304b39..e019541e74fe2bd7173c5a0dd2f959f7c7279fdd 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe Projects::MergeRequestsController do
+  include ApiHelpers
+
   let(:project) { create(:project) }
   let(:user)    { create(:user) }
   let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -455,7 +457,7 @@ describe Projects::MergeRequestsController do
 
         it 'renders the diffs template to a string' do
           expect(response).to render_template('projects/merge_requests/show/_diffs')
-          expect(JSON.parse(response.body)).to have_key('html')
+          expect(json_response).to have_key('html')
         end
       end
 
@@ -494,7 +496,7 @@ describe Projects::MergeRequestsController do
 
         it 'renders the diffs template to a string' do
           expect(response).to render_template('projects/merge_requests/show/_diffs')
-          expect(JSON.parse(response.body)).to have_key('html')
+          expect(json_response).to have_key('html')
         end
       end
     end
@@ -662,18 +664,45 @@ describe Projects::MergeRequestsController do
         go format: 'json'
 
         expect(response).to render_template('projects/merge_requests/show/_commits')
-        expect(JSON.parse(response.body)).to have_key('html')
+        expect(json_response).to have_key('html')
       end
     end
   end
 
   describe 'GET pipelines' do
-    it_behaves_like "loads labels", :pipelines
+    before do
+      create(:ci_pipeline, project: merge_request.source_project,
+                           ref: merge_request.source_branch,
+                           sha: merge_request.diff_head_sha)
+    end
+
+    context 'when using HTML format' do
+      it_behaves_like "loads labels", :pipelines
+    end
+
+    context 'when using JSON format' do
+      before do
+        get :pipelines,
+            namespace_id: project.namespace.to_param,
+            project_id: project.to_param,
+            id: merge_request.iid,
+            format: :json
+      end
+
+      it 'responds with a rendered HTML partial' do
+        expect(response)
+          .to render_template('projects/merge_requests/show/_pipelines')
+        expect(json_response).to have_key 'html'
+      end
+
+      it 'responds with serialized pipelines' do
+        expect(json_response).to have_key 'pipelines'
+        expect(json_response['pipelines']).not_to be_empty
+      end
+    end
   end
 
   describe 'GET conflicts' do
-    let(:json_response) { JSON.parse(response.body) }
-
     context 'when the conflicts cannot be resolved in the UI' do
       before do
         allow_any_instance_of(Gitlab::Conflict::Parser).
@@ -770,8 +799,6 @@ describe Projects::MergeRequestsController do
   end
 
   describe 'GET conflict_for_path' do
-    let(:json_response) { JSON.parse(response.body) }
-
     def conflict_for_path(path)
       get :conflict_for_path,
           namespace_id: merge_request_with_conflicts.project.namespace.to_param,
@@ -826,7 +853,6 @@ describe Projects::MergeRequestsController do
   end
 
   context 'POST resolve_conflicts' do
-    let(:json_response) { JSON.parse(response.body) }
     let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
 
     def resolve_conflicts(files)
@@ -1024,7 +1050,6 @@ describe Projects::MergeRequestsController do
       let!(:forked)       { create(:project) }
       let!(:environment)  { create(:environment, project: forked) }
       let!(:deployment)   { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
-      let(:json_response) { JSON.parse(response.body) }
       let(:admin)         { create(:admin) }
 
       let(:merge_request) do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 6d30d085056de1631e006dbdd8720c23c1358a53..14207bf6b7af6690e4bee1d6dc320f817b5d90f6 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::MilestonesController do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:user)    { create(:user) }
   let(:milestone) { create(:milestone, project: project) }
   let(:issue) { create(:issue, project: project, milestone: milestone) }
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 9f6d4ec65371306fe896654272f7123412381b2e..dc597202050fe6d157bca87c97ca1f390232bd18 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Projects::NotesController do
   let(:user)    { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:issue)   { create(:issue, project: project) }
   let(:note)    { create(:note, noteable: issue, project: project) }
 
@@ -16,6 +16,7 @@ describe Projects::NotesController do
 
   describe 'POST create' do
     let(:merge_request) { create(:merge_request) }
+    let(:project) { merge_request.source_project }
     let(:request_params) do
       {
         note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
@@ -88,6 +89,7 @@ describe Projects::NotesController do
   end
 
   describe "resolving and unresolving" do
+    let(:project) { create(:project, :repository) }
     let(:merge_request) { create(:merge_request, source_project: project) }
     let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
 
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 442f81187dc2def2a9fc9046154b91f98795e834..416eaa0037ebd7d395886d7d7d23c780f2679737 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -143,7 +143,7 @@ describe Projects::ProjectMembersController do
       end
 
       context 'and is an owner' do
-        let(:project) { create(:project, namespace: user.namespace) }
+        let(:project) { create(:empty_project, namespace: user.namespace) }
 
         before { project.team << [user, :master] }
 
@@ -234,7 +234,7 @@ describe Projects::ProjectMembersController do
   end
 
   describe 'POST apply_import' do
-    let(:another_project) { create(:project, :private) }
+    let(:another_project) { create(:empty_project, :private) }
     let(:member) { create(:user) }
 
     before do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 04bd9a01f7b71f259082f7569072b90a4fe544a6..b23d6e257babee8d13f052d1aa0a7e54967af242 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::RawController do
-  let(:public_project) { create(:project, :public) }
+  let(:public_project) { create(:project, :public, :repository) }
 
   describe "#show" do
     context 'regular filename' do
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index abd45a74e2d7d85a0ad65fddb1c481ba72734f56..d8fb4667c674ebe2b88191da60f404fa701d51bc 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::RefsController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 9fd5c3b85f627ae4ecc9a9c4e59f1e9b91b3d67e..69fcc26c77e4729232f261e1e66601098d9d6692 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::ReleasesController do
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project, :repository) }
   let!(:user)    { create(:user) }
   let!(:release) { create(:release, project: project) }
   let!(:tag)     { release.tag }
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 38e02a466266be1adcc99ce7e7dadd3629ca128a..04e88879fb8403d686918b57773daeaf8e2fa09f 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -1,7 +1,7 @@
 require "spec_helper"
 
 describe Projects::RepositoriesController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   describe "GET archive" do
     context 'as a guest' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 2e44b5128b421dcbf4527167112b614eeb484fcf..16365642a34b3c8b0edfbbd58bf0896a87d34804 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::ServicesController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
   let(:service) { create(:service, project: project) }
 
@@ -54,6 +54,7 @@ describe Projects::ServicesController do
     context 'on successful update' do
       it 'sets the flash' do
         expect(service).to receive(:to_param).and_return('hipchat')
+        expect(service).to receive(:event_names).and_return(HipchatService.event_names)
 
         put :update,
           namespace_id: project.namespace.id,
diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb
index e0f9a5b24a689f13bfe95ef76fb657f0dc66ca86..65f7bb34f4a40c44590c9735dd918d307b881983 100644
--- a/spec/controllers/projects/settings/integrations_controller_spec.rb
+++ b/spec/controllers/projects/settings/integrations_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::Settings::IntegrationsController do
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
   let(:user) { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 32b0e42c3cd8c7111a4dd73c3fc79b30d00130ea..19e948d8fb8294a38e16b6c1c8f96094bbe32e20 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -6,8 +6,8 @@ describe Projects::SnippetsController do
   let(:user2)   { create(:user) }
 
   before do
-    project.team << [user, :master]
-    project.team << [user2, :master]
+    project.add_master(user)
+    project.add_master(user2)
   end
 
   describe 'GET #index' do
@@ -69,6 +69,86 @@ describe Projects::SnippetsController do
     end
   end
 
+  describe 'POST #create' do
+    def create_snippet(project, snippet_params = {})
+      sign_in(user)
+
+      project.add_developer(user)
+
+      post :create, {
+        namespace_id: project.namespace.to_param,
+        project_id: project.to_param,
+        project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+      }
+    end
+
+    context 'when the snippet is spam' do
+      before do
+        allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+      end
+
+      context 'when the project is private' do
+        let(:private_project) { create(:project_empty_repo, :private) }
+
+        context 'when the snippet is public' do
+          it 'creates the snippet' do
+            expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+              to change { Snippet.count }.by(1)
+          end
+        end
+      end
+
+      context 'when the project is public' do
+        context 'when the snippet is private' do
+          it 'creates the snippet' do
+            expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+              to change { Snippet.count }.by(1)
+          end
+        end
+
+        context 'when the snippet is public' do
+          it 'rejects the shippet' do
+            expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+              not_to change { Snippet.count }
+            expect(response).to render_template(:new)
+          end
+
+          it 'creates a spam log' do
+            expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+              to change { SpamLog.count }.by(1)
+          end
+        end
+      end
+    end
+  end
+
+  describe 'POST #mark_as_spam' do
+    let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+    before do
+      allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+      stub_application_setting(akismet_enabled: true)
+    end
+
+    def mark_as_spam
+      admin = create(:admin)
+      create(:user_agent_detail, subject: snippet)
+      project.add_master(admin)
+      sign_in(admin)
+
+      post :mark_as_spam,
+           namespace_id: project.namespace.path,
+           project_id: project.path,
+           id: snippet.id
+    end
+
+    it 'updates the snippet' do
+      mark_as_spam
+
+      expect(snippet.reload).not_to be_submittable_as_spam
+    end
+  end
+
   %w[show raw].each do |action|
     describe "GET ##{action}" do
       context 'when the project snippet is private' do
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 5e661c2c41df35d8314b7579649650eb067d97bb..c36a5fdd66cab0835c36241f26dc12801bd3443f 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::TagsController do
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let!(:release) { create(:release, project: project) }
   let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
 
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 19a152bcb055b778a2ba8358e38e50848a382387..80f84a388ceef2a55bc0c5a6d1ea6baee2bb9f81 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::TemplatesController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:file_path_1) { '.gitlab/issue_templates/bug.md' }
@@ -14,7 +14,8 @@ describe Projects::TemplatesController do
 
   before do
     project.add_user(user, Gitlab::Access::MASTER)
-    project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
+    project.repository.commit_file(user, file_path_1, 'something valid',
+      message: 'test 3', branch_name: 'master', update: false)
   end
 
   describe '#show' do
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 1cc050247c66d221a05c652eaad23dddd9e14d43..b81645a3d2d069e114122a2f7dd8e1dd83fd2696 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::TreeController do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user)    { create(:user) }
 
   before do
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 71d0e4be834fb99462cb5d9b0708647488df3f9a..f1c8891e87ab9a833adbb8691b1109c2a792c750 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -1,7 +1,7 @@
 require('spec_helper')
 
 describe Projects::UploadsController do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:user)    { create(:user) }
   let(:jpg)     { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
   let(:txt)     { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index d0a63aa9403a5e0220d65daa5ff45cccf5792e54..9323f723bdb70f485bb975c9e62767c8f7e8a981 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1,11 +1,11 @@
 require('spec_helper')
 
 describe ProjectsController do
-  let(:project) { create(:project) }
-  let(:public_project) { create(:project, :public) }
-  let(:user)    { create(:user) }
-  let(:jpg)     { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
-  let(:txt)     { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+  let(:project) { create(:empty_project) }
+  let(:public_project) { create(:empty_project, :public) }
+  let(:user) { create(:user) }
+  let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
+  let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
 
   describe 'GET index' do
     context 'as a user' do
@@ -32,7 +32,7 @@ describe ProjectsController do
       before { sign_in(user) }
 
       context "user does not have access to project" do
-        let(:private_project) { create(:project, :private) }
+        let(:private_project) { create(:empty_project, :private) }
 
         it "does not initialize notification setting" do
           get :show, namespace_id: private_project.namespace.path, id: private_project.path
@@ -146,6 +146,8 @@ describe ProjectsController do
     end
 
     context "rendering default project view" do
+      let(:public_project) { create(:project, :public, :repository) }
+
       render_views
 
       it "renders the activity view" do
@@ -190,25 +192,11 @@ describe ProjectsController do
           expect(assigns(:project)).to eq(public_project)
           expect(response).to redirect_to("/#{public_project.path_with_namespace}")
         end
-
-        # MySQL queries are case insensitive by default, so this spec would fail.
-        if Gitlab::Database.postgresql?
-          context "when there is also a match with the same casing" do
-            let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) }
-
-            it "loads the exactly matched project" do
-              get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
-
-              expect(assigns(:project)).to eq(other_project)
-              expect(response).to have_http_status(200)
-            end
-          end
-        end
       end
     end
 
     context "when the url contains .atom" do
-      let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
+      let(:public_project_with_dot_atom) { build(:empty_project, :public, name: 'my.atom', path: 'my.atom') }
 
       it 'expects an error creating the project' do
         expect(public_project_with_dot_atom).not_to be_valid
@@ -217,7 +205,7 @@ describe ProjectsController do
 
     context 'when the project is pending deletions' do
       it 'renders a 404 error' do
-        project = create(:project, pending_delete: true)
+        project = create(:empty_project, pending_delete: true)
         sign_in(user)
 
         get :show, namespace_id: project.namespace.path, id: project.path
@@ -233,6 +221,7 @@ describe ProjectsController do
     let(:admin) { create(:admin) }
 
     it "sets the repository to the right path after a rename" do
+      project = create(:project, :repository)
       new_path = 'renamed_path'
       project_params = { path: new_path }
       controller.instance_variable_set(:@project, project)
@@ -384,6 +373,8 @@ describe ProjectsController do
   end
 
   describe "GET refs" do
+    let(:public_project) { create(:project, :public) }
+
     it "gets a list of branches and tags" do
       get :refs, namespace_id: public_project.namespace.path, id: public_project.path
 
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index d76fe9f580fe95e1c211ace9254f11e73779b145..dadcb90cfc2350b362319a443a1fb97a2998d7f5 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -138,6 +138,65 @@ describe SnippetsController do
     end
   end
 
+  describe 'POST #create' do
+    def create_snippet(snippet_params = {})
+      sign_in(user)
+
+      post :create, {
+        personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+      }
+    end
+
+    context 'when the snippet is spam' do
+      before do
+        allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+      end
+
+      context 'when the snippet is private' do
+        it 'creates the snippet' do
+          expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+            to change { Snippet.count }.by(1)
+        end
+      end
+
+      context 'when the snippet is public' do
+        it 'rejects the shippet' do
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+            not_to change { Snippet.count }
+          expect(response).to render_template(:new)
+        end
+
+        it 'creates a spam log' do
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+            to change { SpamLog.count }.by(1)
+        end
+      end
+    end
+  end
+
+  describe 'POST #mark_as_spam' do
+    let(:snippet) { create(:personal_snippet, :public, author: user) }
+
+    before do
+      allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+      stub_application_setting(akismet_enabled: true)
+    end
+
+    def mark_as_spam
+      admin = create(:admin)
+      create(:user_agent_detail, subject: snippet)
+      sign_in(admin)
+
+      post :mark_as_spam, id: snippet.id
+    end
+
+    it 'updates the snippet' do
+      mark_as_spam
+
+      expect(snippet.reload).not_to be_submittable_as_spam
+    end
+  end
+
   %w(raw download).each do |action|
     describe "GET #{action}" do
       context 'when the personal snippet is private' do
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 69124ab06bfac740b15aa86cd429a3a7cafc66b9..570d9fa43f85c048c0da7024105cfe336eccd474 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -41,7 +41,7 @@ describe UploadsController do
     end
 
     context "when viewing a project avatar" do
-      let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+      let!(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
 
       context "when the project is public" do
         before do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 19a8b1fe52459db34174427a4e9644a14bef9065..bbe9aaf737fa8c860d77d9cb12ba3b04790f8c53 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -73,7 +73,7 @@ describe UsersController do
     end
 
     context 'forked project' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
       let(:forked_project) { Projects::ForkService.new(project, user).execute }
 
       before do
@@ -91,7 +91,7 @@ describe UsersController do
   end
 
   describe 'GET #calendar_activities' do
-    let!(:project) { create(:project) }
+    let!(:project) { create(:empty_project) }
     let!(:user) { create(:user) }
 
     before do
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index ed4acca23f1e8da3522b4c528703dd29397139c8..c3b4aff55ba6cfd02520d4faf862f15885159791 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -16,6 +16,10 @@ FactoryGirl.define do
       is_shared true
     end
 
+    trait :specific do
+      is_shared false
+    end
+
     trait :inactive do
       active false
     end
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index ee3b17b8bf1fe0b485b215b4a79b5ae41d5977e2..7f557b25ccbb11af99b8e5b4345cd6b6968c9843 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -3,11 +3,12 @@ FactoryGirl.define do
     transient do
       name 'test'
       status nil
+      warnings nil
       pipeline factory: :ci_empty_pipeline
     end
 
     initialize_with do
-      Ci::Stage.new(pipeline, name: name, status: status)
+      Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
     end
   end
 end
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 27cece487bd87924574545ffda60d6946b6c19bd..75f8982ecd938c1252551016e5c3c7beff64b7ed 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :deploy_keys_project do
     deploy_key
-    project
+    project factory: :empty_project
   end
 end
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 8820d527c6195601538bd354c526e148a753dac2..bfe41f71b5759985b01958c1202cc70368b46f82 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :event do
-    project
+    project factory: :empty_project
     author factory: :user
 
     factory :closed_issue_event do
diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb
index 1b36e21f2b0f842253f0fa58d680c6b45ed4019b..bc74aeecc3bcc9d199d99b11844619fa430c56ea 100644
--- a/spec/factories/file_uploader.rb
+++ b/spec/factories/file_uploader.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :file_uploader do
-    project
+    project factory: :empty_project
     secret nil
 
     transient do
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index ece6beb9fa961bccaf824e93a974eeb70ffdaf06..86f51ffca997f7c9b1bfee8d8fee6adb349e7a4e 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -1,8 +1,9 @@
 FactoryGirl.define do
-  factory :group do
+  factory :group, class: Group, parent: :namespace do
     sequence(:name) { |n| "group#{n}" }
     path { name.downcase.gsub(/\s/, '_') }
     type 'Group'
+    owner nil
 
     trait :public do
       visibility_level Gitlab::VisibilityLevel::PUBLIC
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 2b4670be4689ae6fc8c03e0d31b1c22060f82706..7e09f1ba8ea7069de242ad299f8e14ca9b25a4fd 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -6,7 +6,7 @@ FactoryGirl.define do
   factory :issue do
     title
     author
-    project
+    project factory: :empty_project
 
     trait :confidential do
       confidential true
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 3e8822faf972cff162261650b4f06f51a48ddeb6..5ba8443c62c7c51453a662fc64e251d5cadfbee6 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
   factory :label, class: ProjectLabel do
     sequence(:title) { |n| "label#{n}" }
     color "#990000"
-    project
+    project factory: :empty_project
 
     transient do
       priority nil
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 37eb49c94df104885d82cd3f1b6cf66da5163dad..22f84150bb39f15636f52df8ec45047b0596e538 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
   factory :merge_request do
     title
     author
-    source_project factory: :project
+    association :source_project, :repository, factory: :project
     target_project { source_project }
 
     # $ git log --pretty=oneline feature..master
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 84da71ed6dcce196a759e92d6e14fd73911a874a..841ab3c73b86c43593c6d890e3033f4d279b5ff1 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,7 +1,7 @@
 FactoryGirl.define do
   factory :milestone do
     title
-    project
+    project factory: :empty_project
 
     trait :active do
       state "active"
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index a10ba6297609638f510f4b8924350aabf842c0fd..a21da7074f9f1fd95ee38612b9e9e713626004b1 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -4,7 +4,7 @@ include ActionDispatch::TestProcess
 
 FactoryGirl.define do
   factory :note do
-    project
+    project factory: :empty_project
     note "Note"
     author
     on_issue
@@ -13,12 +13,19 @@ FactoryGirl.define do
     factory :note_on_issue,              traits: [:on_issue], aliases: [:votable_note]
     factory :note_on_merge_request,      traits: [:on_merge_request]
     factory :note_on_project_snippet,    traits: [:on_project_snippet]
+    factory :note_on_personal_snippet,   traits: [:on_personal_snippet]
     factory :system_note,                traits: [:system]
 
-    factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote
-    factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote
+    factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote do
+      association :project, :repository
+    end
+
+    factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do
+      association :project, :repository
+    end
 
     factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do
+      association :project, :repository
       position do
         Gitlab::Diff::Position.new(
           old_path: "files/ruby/popen.rb",
@@ -36,6 +43,7 @@ FactoryGirl.define do
     end
 
     factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
+      association :project, :repository
       position do
         Gitlab::Diff::Position.new(
           old_path: "files/ruby/popen.rb",
@@ -48,6 +56,7 @@ FactoryGirl.define do
     end
 
     trait :on_commit do
+      association :project, :repository
       noteable nil
       noteable_type 'Commit'
       noteable_id nil
@@ -70,6 +79,11 @@ FactoryGirl.define do
       noteable { create(:project_snippet, project: project) }
     end
 
+    trait :on_personal_snippet do
+      noteable { create(:personal_snippet) }
+      project nil
+    end
+
     trait :system do
       system true
     end
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
index e73cc05f9d7c0ada3af25d5ce1dc4ad0cfdf186d..50341d943f513e7cd2830f95b78344f36d0d5c4c 100644
--- a/spec/factories/project_group_links.rb
+++ b/spec/factories/project_group_links.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :project_group_link do
-    project
+    project factory: :empty_project
     group
   end
 end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index c21927640d10736e71710c3e2d50293ab8e97507..d62799a5a4737db2efba0b65f785b0b6e9c3e0e5 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -1,7 +1,7 @@
 FactoryGirl.define do
   factory :project_member do
     user
-    project
+    project factory: :empty_project
     master
 
     trait(:guest)     { access_level ProjectMember::GUEST }
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
index d681a2c8483d55ae3aa53def3c11c8dd5cd1a566..e0fe1b36fd35329cf220422f8afc58c374b7cf11 100644
--- a/spec/factories/project_snippets.rb
+++ b/spec/factories/project_snippets.rb
@@ -1,5 +1,5 @@
 FactoryGirl.define do
   factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
-    project
+    project factory: :empty_project
   end
 end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 992580a6b344414deb20cb78fd1130b96174ff30..715b2a27b306a7067cca000b2e11ff0769a6b8a8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -106,6 +106,42 @@ FactoryGirl.define do
     path { 'gitlabhq' }
 
     test_repo
+
+    transient do
+      create_template nil
+    end
+
+    after :create do |project, evaluator|
+      TestEnv.copy_repo(project)
+
+      if evaluator.create_template
+        args = evaluator.create_template
+
+        project.add_user(args[:user], args[:access])
+
+        project.repository.commit_file(
+          args[:user],
+          ".gitlab/#{args[:path]}/bug.md",
+          'something valid',
+          message: 'test 3',
+          branch_name: 'master',
+          update: false)
+        project.repository.commit_file(
+          args[:user],
+          ".gitlab/#{args[:path]}/template_test.md",
+          'template_test',
+          message: 'test 1',
+          branch_name: 'master',
+          update: false)
+        project.repository.commit_file(
+          args[:user],
+          ".gitlab/#{args[:path]}/feature_proposal.md",
+          'feature_proposal',
+          message: 'test 2',
+          branch_name: 'master',
+          update: false)
+      end
+    end
   end
 
   factory :forked_project_with_submodules, parent: :empty_project do
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 74497dc82c030bb3744ef144784910c767b8609c..6a6d6fa171f8483a813df759196b16279975ab07 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -2,6 +2,6 @@ FactoryGirl.define do
   factory :release do
     tag "v1.1.0"
     description "Awesome release"
-    project
+    project factory: :empty_project
   end
 end
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index 78eb929c6e7d2ed51fbfa073e79d929bcc393f62..6287c40afe91fe4a63a683cb05357db7679cef43 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :sent_notification do
-    project
+    project factory: :empty_project
     recipient factory: :user
     noteable factory: :issue
     reply_key "0123456789abcdef" * 2
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 9de78d68280c6bff620c09a146604d32906dc092..a14a46c803e623b0c6e786d1897fa8d6760563c8 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,5 +1,5 @@
 FactoryGirl.define do
   factory :service do
-    project
+    project factory: :empty_project
   end
 end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 85a8c2636432a70d30b097e7539ad89637d35fe5..91d6f39a5bf3f578bc541cf9b0bad45823172496 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,6 +1,6 @@
 FactoryGirl.define do
   factory :todo do
-    project
+    project factory: :empty_project
     author
     user
     target factory: :issue
diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..246176611dc42eb47dc71ba11b9c0ad616924047
--- /dev/null
+++ b/spec/factories/trending_project.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+  # TrendingProject
+  factory :trending_project, class: 'TrendingProject' do
+    project
+  end
+end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e177059d9597c2d790869dfda97c9a9abd371662..9d5ce876c29d40a828dd3d79a4101330af3bb954 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -9,8 +9,8 @@ describe 'Admin Builds' do
     let(:pipeline) { create(:ci_pipeline) }
 
     context 'All tab' do
-      context 'when have builds' do
-        it 'shows all builds' do
+      context 'when have jobs' do
+        it 'shows all jobs' do
           create(:ci_build, pipeline: pipeline, status: :pending)
           create(:ci_build, pipeline: pipeline, status: :running)
           create(:ci_build, pipeline: pipeline, status: :success)
@@ -19,26 +19,26 @@ describe 'Admin Builds' do
           visit admin_builds_path
 
           expect(page).to have_selector('.nav-links li.active', text: 'All')
-          expect(page).to have_selector('.row-content-block', text: 'All builds')
+          expect(page).to have_selector('.row-content-block', text: 'All jobs')
           expect(page.all('.build-link').size).to eq(4)
           expect(page).to have_link 'Cancel all'
         end
       end
 
-      context 'when have no builds' do
+      context 'when have no jobs' do
         it 'shows a message' do
           visit admin_builds_path
 
           expect(page).to have_selector('.nav-links li.active', text: 'All')
-          expect(page).to have_content 'No builds to show'
+          expect(page).to have_content 'No jobs to show'
           expect(page).not_to have_link 'Cancel all'
         end
       end
     end
 
     context 'Pending tab' do
-      context 'when have pending builds' do
-        it 'shows pending builds' do
+      context 'when have pending jobs' do
+        it 'shows pending jobs' do
           build1 = create(:ci_build, pipeline: pipeline, status: :pending)
           build2 = create(:ci_build, pipeline: pipeline, status: :running)
           build3 = create(:ci_build, pipeline: pipeline, status: :success)
@@ -55,22 +55,22 @@ describe 'Admin Builds' do
         end
       end
 
-      context 'when have no builds pending' do
+      context 'when have no jobs pending' do
         it 'shows a message' do
           create(:ci_build, pipeline: pipeline, status: :success)
 
           visit admin_builds_path(scope: :pending)
 
           expect(page).to have_selector('.nav-links li.active', text: 'Pending')
-          expect(page).to have_content 'No builds to show'
+          expect(page).to have_content 'No jobs to show'
           expect(page).not_to have_link 'Cancel all'
         end
       end
     end
 
     context 'Running tab' do
-      context 'when have running builds' do
-        it 'shows running builds' do
+      context 'when have running jobs' do
+        it 'shows running jobs' do
           build1 = create(:ci_build, pipeline: pipeline, status: :running)
           build2 = create(:ci_build, pipeline: pipeline, status: :success)
           build3 = create(:ci_build, pipeline: pipeline, status: :failed)
@@ -87,22 +87,22 @@ describe 'Admin Builds' do
         end
       end
 
-      context 'when have no builds running' do
+      context 'when have no jobs running' do
         it 'shows a message' do
           create(:ci_build, pipeline: pipeline, status: :success)
 
           visit admin_builds_path(scope: :running)
 
           expect(page).to have_selector('.nav-links li.active', text: 'Running')
-          expect(page).to have_content 'No builds to show'
+          expect(page).to have_content 'No jobs to show'
           expect(page).not_to have_link 'Cancel all'
         end
       end
     end
 
     context 'Finished tab' do
-      context 'when have finished builds' do
-        it 'shows finished builds' do
+      context 'when have finished jobs' do
+        it 'shows finished jobs' do
           build1 = create(:ci_build, pipeline: pipeline, status: :pending)
           build2 = create(:ci_build, pipeline: pipeline, status: :running)
           build3 = create(:ci_build, pipeline: pipeline, status: :success)
@@ -117,14 +117,14 @@ describe 'Admin Builds' do
         end
       end
 
-      context 'when have no builds finished' do
+      context 'when have no jobs finished' do
         it 'shows a message' do
           create(:ci_build, pipeline: pipeline, status: :running)
 
           visit admin_builds_path(scope: :finished)
 
           expect(page).to have_selector('.nav-links li.active', text: 'Finished')
-          expect(page).to have_content 'No builds to show'
+          expect(page).to have_content 'No jobs to show'
           expect(page).to have_link 'Cancel all'
         end
       end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 66044b444952de468dd1448ae61bbd47d8fcca89..e8e080ce3e26ba52fc3c18fd170249e4320d89ae 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,10 +1,13 @@
 require 'rails_helper'
 
 feature 'Admin disables Git access protocol', feature: true do
+  include StubENV
+
   let(:project) { create(:empty_project, :empty_repo) }
   let(:admin) { create(:admin) }
 
   background do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
     login_as(admin)
   end
 
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index dec2dedf2b519ab913576c359dbe0fb21d5b3dd8..f7e49a56deb8f77efc7424eb9bb88085fa6db0ea 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,9 +1,11 @@
 require 'spec_helper'
 
 feature "Admin Health Check", feature: true do
+  include StubENV
   include WaitForAjax
 
   before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
     login_as :admin
   end
 
@@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do
       visit admin_health_check_path
     end
 
-    it { page.has_text? 'Health Check' }
-    it { page.has_text? 'Health information can be retrieved' }
-
     it 'has a health check access token' do
+      page.has_text? 'Health Check'
+      page.has_text? 'Health information can be retrieved'
+
       token = current_application_settings.health_check_access_token
+
       expect(page).to have_content("Access token is #{token}")
       expect(page).to have_selector('#health-check-token', text: token)
     end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index a5b88812b75594e1b7f75571b3de04ef85e46049..87a8f62687aa918051731b8321e7b7862038228e 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -10,7 +10,7 @@ describe "Admin::Projects", feature: true  do
   end
 
   describe "GET /admin/projects" do
-    let!(:archived_project) { create :project, :public, archived: true }
+    let!(:archived_project) { create :project, :public, :archived }
 
     before do
       visit admin_projects_path
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index d92c66b689d363eddec190a9078930ca8ab39c4a..f05fbe3d0625d47eedc7873f54dd3cecda00e2c2 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -1,7 +1,10 @@
 require 'spec_helper'
 
 describe "Admin Runners" do
+  include StubENV
+
   before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
     login_as :admin
   end
 
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 47fa2f14307d1f35068f6a41bd67cda1ffdcb39b..de42ab81face88065cb77639461bfd66e8847987 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -1,7 +1,10 @@
 require 'spec_helper'
 
 feature 'Admin updates settings', feature: true do
-  before(:each) do
+  include StubENV
+
+  before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
     login_as :admin
     visit admin_application_settings_path
   end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 661fb7618091a73cf89bdd9d6e497e75f27cdeb1..855247de2ea73addc7948ae74dd3d4fa9f9322f9 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -1,7 +1,12 @@
 require 'rails_helper'
 
 feature 'Admin uses repository checks', feature: true do
-  before { login_as :admin }
+  include StubENV
+
+  before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+    login_as :admin
+  end
 
   scenario 'to trigger a single check' do
     project = create(:empty_project)
@@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do
 
   scenario 'to clear all repository checks', js: true do
     visit admin_application_settings_path
-    
+
     expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
 
     click_link 'Clear all repository checks'
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 188d33e8ef4338e1127cb6df1f627dd6553af5c0..c28bb0dcdaeae8998323853cb887699b80900219 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -141,6 +141,36 @@ describe 'Issue Boards', feature: true, js: true do
         end
       end
     end
+
+    it 'resets assignee dropdown' do
+      page.within(first('.board')) do
+        first('.card').click
+      end
+
+      page.within('.assignee') do
+        click_link 'Edit'
+
+        wait_for_ajax
+
+        page.within('.dropdown-menu-user') do
+          click_link user.name
+
+          wait_for_vue_resource
+        end
+
+        expect(page).to have_content(user.name)
+      end
+
+      page.within(first('.board')) do
+        find('.card:nth-child(2)').click
+      end
+
+      page.within('.assignee') do
+        click_link 'Edit'
+
+        expect(page).not_to have_selector('.is-active')
+      end
+    end
   end
 
   context 'milestone' do
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f3a5b56512292e0a1b67de7c4eb43fd5001968d8
--- /dev/null
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -0,0 +1,432 @@
+require 'spec_helper'
+
+describe 'Copy as GFM', feature: true, js: true do
+  include GitlabMarkdownHelper
+  include ActionView::Helpers::JavaScriptHelper
+
+  before do
+    @feat = MarkdownFeature.new
+
+    # `markdown` helper expects a `@project` variable
+    @project = @feat.project
+
+    visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
+  end
+
+  # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
+  # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM.
+  # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
+  # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper.
+
+  # These are all in a single `it` for performance reasons.
+  it 'works', :aggregate_failures do
+    verify(
+      'nesting',
+
+      '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
+    )
+
+    verify(
+      'a real world example from the gitlab-ce README',
+
+      <<-GFM.strip_heredoc
+        # 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)](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)
+
+        ## Canonical source
+
+        The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
+
+        ## Open source software to collaborate on code
+
+        To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
+
+
+        - Manage Git repositories with fine grained access controls that keep your code secure
+
+        - Perform code reviews and enhance collaboration with merge requests
+
+        - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
+
+        - Each project can also have an issue tracker, issue board, and a wiki
+
+        - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
+
+        - Completely free and open source (MIT Expat license)
+      GFM
+    )
+
+    verify(
+      'InlineDiffFilter',
+
+      '{-Deleted text-}',
+      '{+Added text+}'
+    )
+
+    verify(
+      'TaskListFilter',
+
+      '- [ ] Unchecked task',
+      '- [x] Checked task',
+      '1. [ ] Unchecked numbered task',
+      '1. [x] Checked numbered task'
+    )
+
+    verify(
+      'ReferenceFilter',
+
+      # issue reference
+      @feat.issue.to_reference,
+      # full issue reference
+      @feat.issue.to_reference(full: true),
+      # issue URL
+      namespace_project_issue_url(@project.namespace, @project, @feat.issue),
+      # issue URL with note anchor
+      namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'),
+      # issue link
+      "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
+      # issue link with note anchor
+      "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})",
+    )
+
+    verify(
+      'AutolinkFilter',
+
+      'https://example.com'
+    )
+
+    verify(
+      'TableOfContentsFilter',
+
+      '[[_TOC_]]'
+    )
+
+    verify(
+      'EmojiFilter',
+
+      ':thumbsup:'
+    )
+
+    verify(
+      'ImageLinkFilter',
+
+      '![Image](https://example.com/image.png)'
+    )
+
+    verify(
+      'VideoLinkFilter',
+
+      '![Video](https://example.com/video.mp4)'
+    )
+
+    verify(
+      'MathFilter: math as converted from GFM to HTML',
+
+      '$`c = \pm\sqrt{a^2 + b^2}`$',
+
+      # math block
+      <<-GFM.strip_heredoc
+        ```math
+        c = \pm\sqrt{a^2 + b^2}
+        ```
+      GFM
+    )
+
+    aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do
+      gfm = '$`c = \pm\sqrt{a^2 + b^2}`$'
+
+      html = <<-HTML.strip_heredoc
+        <span class="katex">
+          <span class="katex-mathml">
+            <math>
+              <semantics>
+                <mrow>
+                  <mi>c</mi>
+                  <mo>=</mo>
+                  <mo>±</mo>
+                  <msqrt>
+                    <mrow>
+                      <msup>
+                        <mi>a</mi>
+                        <mn>2</mn>
+                      </msup>
+                      <mo>+</mo>
+                      <msup>
+                        <mi>b</mi>
+                        <mn>2</mn>
+                      </msup>
+                    </mrow>
+                  </msqrt>
+                </mrow>
+                <annotation encoding="application/x-tex">c = \\pm\\sqrt{a^2 + b^2}</annotation>
+              </semantics>
+            </math>
+          </span>
+          <span class="katex-html" aria-hidden="true">
+              <span class="strut" style="height: 0.913389em;"></span>
+              <span class="strut bottom" style="height: 1.04em; vertical-align: -0.126611em;"></span>
+              <span class="base textstyle uncramped">
+                <span class="mord mathit">c</span>
+                <span class="mrel">=</span>
+                <span class="mord">±</span>
+                <span class="sqrt mord"><span class="sqrt-sign" style="top: -0.073389em;">
+                  <span class="style-wrap reset-textstyle textstyle uncramped">√</span>
+                </span>
+                <span class="vlist">
+                  <span class="" style="top: 0em;">
+                    <span class="fontsize-ensurer reset-size5 size5">
+                      <span class="" style="font-size: 1em;">​</span>
+                    </span>
+                    <span class="mord textstyle cramped">
+                      <span class="mord">
+                        <span class="mord mathit">a</span>
+                        <span class="msupsub">
+                          <span class="vlist">
+                            <span class="" style="top: -0.289em; margin-right: 0.05em;">
+                              <span class="fontsize-ensurer reset-size5 size5">
+                                <span class="" style="font-size: 0em;">​</span>
+                              </span>
+                              <span class="reset-textstyle scriptstyle cramped">
+                                <span class="mord mathrm">2</span>
+                              </span>
+                            </span>
+                            <span class="baseline-fix">
+                              <span class="fontsize-ensurer reset-size5 size5">
+                                <span class="" style="font-size: 0em;">​</span>
+                              </span>
+                            ​</span>
+                          </span>
+                        </span>
+                      </span>
+                      <span class="mbin">+</span>
+                      <span class="mord">
+                        <span class="mord mathit">b</span>
+                        <span class="msupsub">
+                          <span class="vlist">
+                            <span class="" style="top: -0.289em; margin-right: 0.05em;">
+                              <span class="fontsize-ensurer reset-size5 size5">
+                                <span class="" style="font-size: 0em;">​</span>
+                              </span>
+                              <span class="reset-textstyle scriptstyle cramped">
+                                <span class="mord mathrm">2</span>
+                              </span>
+                            </span>
+                            <span class="baseline-fix">
+                              <span class="fontsize-ensurer reset-size5 size5">
+                                <span class="" style="font-size: 0em;">​</span>
+                              </span>
+                            ​</span>
+                          </span>
+                        </span>
+                      </span>
+                    </span>
+                  </span>
+                  <span class="" style="top: -0.833389em;">
+                    <span class="fontsize-ensurer reset-size5 size5">
+                      <span class="" style="font-size: 1em;">​</span>
+                    </span>
+                    <span class="reset-textstyle textstyle uncramped sqrt-line"></span>
+                  </span>
+                  <span class="baseline-fix">
+                    <span class="fontsize-ensurer reset-size5 size5">
+                      <span class="" style="font-size: 1em;">​</span>
+                    </span>
+                  ​</span>
+                </span>
+              </span>
+            </span>
+          </span>
+        </span>
+      HTML
+
+      output_gfm = html_to_gfm(html)
+      expect(output_gfm.strip).to eq(gfm.strip)
+    end
+
+    verify(
+      'SanitizationFilter',
+
+      <<-GFM.strip_heredoc
+      <sub>sub</sub>
+
+      <dl>
+        <dt>dt</dt>
+        <dd>dd</dd>
+      </dl>
+
+      <kbd>kbd</kbd>
+
+      <q>q</q>
+
+      <samp>samp</samp>
+
+      <var>var</var>
+
+      <ruby>ruby</ruby>
+
+      <rt>rt</rt>
+
+      <rp>rp</rp>
+
+      <abbr>abbr</abbr>
+      GFM
+    )
+
+    verify(
+      'SanitizationFilter',
+
+      <<-GFM.strip_heredoc,
+        ```
+        Plain text
+        ```
+      GFM
+
+      <<-GFM.strip_heredoc,
+        ```ruby
+        def foo
+          bar
+        end
+        ```
+      GFM
+
+      <<-GFM.strip_heredoc
+        Foo
+
+            This is an example of GFM
+
+            ```js
+            Code goes here
+            ```
+      GFM
+    )
+
+    verify(
+      'MarkdownFilter',
+
+      "Line with two spaces at the end  \nto insert a linebreak",
+
+      '`code`',
+      '`` code with ` ticks ``',
+
+      '> Quote',
+
+      # multiline quote
+      <<-GFM.strip_heredoc,
+        > Multiline
+        > Quote
+        >
+        > With multiple paragraphs
+      GFM
+
+      '![Image](https://example.com/image.png)',
+
+      '# Heading with no anchor link',
+
+      '[Link](https://example.com)',
+
+      '- List item',
+
+      # multiline list item
+      <<-GFM.strip_heredoc,
+        - Multiline
+            List item
+      GFM
+
+      # nested lists
+      <<-GFM.strip_heredoc,
+        - Nested
+
+
+            - Lists
+      GFM
+
+      # list with blockquote
+      <<-GFM.strip_heredoc,
+        - List
+
+            > Blockquote
+      GFM
+
+      '1. Numbered list item',
+
+      # multiline numbered list item
+      <<-GFM.strip_heredoc,
+        1. Multiline
+            Numbered list item
+      GFM
+
+      # nested numbered list
+      <<-GFM.strip_heredoc,
+        1. Nested
+
+
+            1. Numbered lists
+      GFM
+
+      '# Heading',
+      '## Heading',
+      '### Heading',
+      '#### Heading',
+      '##### Heading',
+      '###### Heading',
+
+      '**Bold**',
+
+      '_Italics_',
+
+      '~~Strikethrough~~',
+
+      '2^2',
+
+      '-----',
+
+      # table
+      <<-GFM.strip_heredoc,
+        | Centered | Right | Left |
+        |:--------:|------:|------|
+        | Foo | Bar | **Baz** |
+        | Foo | Bar | **Baz** |
+      GFM
+
+      # table with empty heading
+      <<-GFM.strip_heredoc,
+        |  | x | y |
+        |---|---|---|
+        | a | 1 | 0 |
+        | b | 0 | 1 |
+      GFM
+    )
+  end
+
+  alias_method :gfm_to_html, :markdown
+
+  def html_to_gfm(html)
+    js = <<-JS.strip_heredoc
+      (function(html) {
+        var node = document.createElement('div');
+        node.innerHTML = html;
+        return window.gl.CopyAsGFM.nodeToGFM(node);
+      })("#{escape_javascript(html)}")
+    JS
+    page.evaluate_script(js)
+  end
+
+  def verify(label, *gfms)
+    aggregate_failures(label) do
+      gfms.each do |gfm|
+        html = gfm_to_html(gfm)
+        output_gfm = html_to_gfm(html)
+        expect(output_gfm.strip).to eq(gfm.strip)
+      end
+    end
+  end
+
+  # Fake a `current_user` helper
+  def current_user
+    @feat.user
+  end
+end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9be4e5dbdd3af3188c635479d1da43ad10032a7
--- /dev/null
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+feature 'Dashboard shortcuts', feature: true, js: true do
+  before do
+    login_as :user
+    visit dashboard_projects_path
+  end
+
+  scenario 'Navigate to tabs' do
+    find('body').native.send_key('g')
+    find('body').native.send_key('p')
+
+    ensure_active_main_tab('Projects')
+
+    find('body').native.send_key('g')
+    find('body').native.send_key('i')
+
+    ensure_active_main_tab('Issues')
+
+    find('body').native.send_key('g')
+    find('body').native.send_key('m')
+
+    ensure_active_main_tab('Merge Requests')
+  end
+
+  def ensure_active_main_tab(content)
+    expect(find('.nav-sidebar li.active')).to have_content(content)
+  end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 56f6cd2e095be70b81fb784afe1c81a324f74812..511c95b758feb0625647acc8daca3dc8c0e1e71f 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -19,6 +19,10 @@ feature 'Environment', :feature do
       visit_environment(environment)
     end
 
+    scenario 'shows environment name' do
+      expect(page).to have_content(environment.name)
+    end
+
     context 'without deployments' do
       scenario 'does show no deployments' do
         expect(page).to have_content('You don\'t have any deployments right now.')
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 72b984cfab849a5edf61c8bc0e616d275307f32c..c033b693213b65dd79426737fcccc97670773688 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do
         end
 
         scenario 'does create a new pipeline' do
-          expect(page).to have_content('Production')
+          expect(page).to have_content('production')
         end
       end
 
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 30b80aa82b02fe2bb81562c5f1d7c67029abb8e5..78a11ffee99d466c7921f8a18a2c986cb8acfbfc 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -7,7 +7,7 @@ feature 'Group merge requests page', feature: true do
   include_examples 'project features apply to issuables', MergeRequest
 
   context 'archived issuable' do
-    let(:project_archived) { create(:project, group: group, merge_requests_access_level: ProjectFeature::ENABLED, archived: true) }
+    let(:project_archived) { create(:project, :archived, group: group, merge_requests_access_level: ProjectFeature::ENABLED) }
     let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') }
     let(:access_level) { ProjectFeature::ENABLED }
     let(:user) { user_in_group }
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 3ac9b2e0ae07a49c4a30d99aca0f61eeb5979ce0..93763f092fb21e75758ca217fbc633cecdde7b44 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -125,7 +125,7 @@ describe 'Dropdown assignee', js: true, feature: true do
       click_assignee(user_jacob.name)
 
       expect(page).to have_css(js_dropdown_assignee, visible: false)
-      expect(filtered_search.value).to eq("assignee:@#{user_jacob.username}")
+      expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ")
     end
 
     it 'fills in the assignee username when the assignee has been filtered' do
@@ -133,14 +133,14 @@ describe 'Dropdown assignee', js: true, feature: true do
       click_assignee(user.name)
 
       expect(page).to have_css(js_dropdown_assignee, visible: false)
-      expect(filtered_search.value).to eq("assignee:@#{user.username}")
+      expect(filtered_search.value).to eq("assignee:@#{user.username} ")
     end
 
     it 'selects `no assignee`' do
       find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
 
       expect(page).to have_css(js_dropdown_assignee, visible: false)
-      expect(filtered_search.value).to eq("assignee:none")
+      expect(filtered_search.value).to eq("assignee:none ")
     end
   end
 
@@ -169,4 +169,22 @@ describe 'Dropdown assignee', js: true, feature: true do
       expect(page).to have_css(js_dropdown_assignee, visible: true)
     end
   end
+
+  describe 'caching requests' do
+    it 'caches requests after the first load' do
+      filtered_search.set('assignee')
+      send_keys_to_filtered_search(':')
+      initial_size = dropdown_assignee_size
+
+      expect(initial_size).to be > 0
+
+      new_user = create(:user)
+      project.team << [new_user, :master]
+      find('.filtered-search-input-container .clear-search').click
+      filtered_search.set('assignee')
+      send_keys_to_filtered_search(':')
+
+      expect(dropdown_assignee_size).to eq(initial_size)
+    end
+  end
 end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 464749d01e307fff82832a1271bf302973a2e6f3..59e302f0e2d10a0938ef892cbb558f4323f9142d 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -121,14 +121,14 @@ describe 'Dropdown author', js: true, feature: true do
       click_author(user_jacob.name)
 
       expect(page).to have_css(js_dropdown_author, visible: false)
-      expect(filtered_search.value).to eq("author:@#{user_jacob.username}")
+      expect(filtered_search.value).to eq("author:@#{user_jacob.username} ")
     end
 
     it 'fills in the author username when the author has been filtered' do
       click_author(user.name)
 
       expect(page).to have_css(js_dropdown_author, visible: false)
-      expect(filtered_search.value).to eq("author:@#{user.username}")
+      expect(filtered_search.value).to eq("author:@#{user.username} ")
     end
   end
 
@@ -157,4 +157,22 @@ describe 'Dropdown author', js: true, feature: true do
       expect(page).to have_css(js_dropdown_author, visible: true)
     end
   end
+
+  describe 'caching requests' do
+    it 'caches requests after the first load' do
+      filtered_search.set('author')
+      send_keys_to_filtered_search(':')
+      initial_size = dropdown_author_size
+
+      expect(initial_size).to be > 0
+
+      new_user = create(:user)
+      project.team << [new_user, :master]
+      find('.filtered-search-input-container .clear-search').click
+      filtered_search.set('author')
+      send_keys_to_filtered_search(':')
+
+      expect(dropdown_author_size).to eq(initial_size)
+    end
+  end
 end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index 89c144141c9366dbd80165554dbcef2fb00e0792..5079eb8dd00959ddd18b5198a0b187f4c714680b 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do
     visit namespace_project_issues_path(project.namespace, project)
   end
 
+  describe 'keyboard navigation' do
+    it 'selects label' do
+      send_keys_to_filtered_search('label:')
+
+      filtered_search.native.send_keys(:down, :down, :enter)
+
+      expect(filtered_search.value).to eq("label:~#{special_label.name} ")
+    end
+  end
+
   describe 'behavior' do
     it 'opens when the search bar has label:' do
       filtered_search.set('label:')
@@ -159,7 +169,7 @@ describe 'Dropdown label', js: true, feature: true do
       click_label(bug_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~#{bug_label.title}")
+      expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
     end
 
     it 'fills in the label name when the label is partially filled' do
@@ -167,49 +177,49 @@ describe 'Dropdown label', js: true, feature: true do
       click_label(bug_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~#{bug_label.title}")
+      expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
     end
 
     it 'fills in the label name that contains multiple words' do
       click_label(two_words_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\"")
+      expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ")
     end
 
     it 'fills in the label name that contains multiple words and is very long' do
       click_label(long_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~\"#{long_label.title}\"")
+      expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ")
     end
 
     it 'fills in the label name that contains double quotes' do
       click_label(wont_fix_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}'")
+      expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ")
     end
 
     it 'fills in the label name with the correct capitalization' do
       click_label(uppercase_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~#{uppercase_label.title}")
+      expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ")
     end
 
     it 'fills in the label name with special characters' do
       click_label(special_label.title)
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:~#{special_label.title}")
+      expect(filtered_search.value).to eq("label:~#{special_label.title} ")
     end
 
     it 'selects `no label`' do
       find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click
 
       expect(page).to have_css(js_dropdown_label, visible: false)
-      expect(filtered_search.value).to eq("label:none")
+      expect(filtered_search.value).to eq("label:none ")
     end
   end
 
@@ -239,4 +249,21 @@ describe 'Dropdown label', js: true, feature: true do
       expect(page).to have_css(js_dropdown_label, visible: true)
     end
   end
+
+  describe 'caching requests' do
+    it 'caches requests after the first load' do
+      filtered_search.set('label')
+      send_keys_to_filtered_search(':')
+      initial_size = dropdown_label_size
+
+      expect(initial_size).to be > 0
+
+      create(:label, project: project)
+      find('.filtered-search-input-container .clear-search').click
+      filtered_search.set('label')
+      send_keys_to_filtered_search(':')
+
+      expect(dropdown_label_size).to eq(initial_size)
+    end
+  end
 end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index e5a271b663f186a6c4b7053e2365aeb31bdb4538..0ce16715b8644940c308af73f3e13b4504f6a917 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -127,7 +127,7 @@ describe 'Dropdown milestone', js: true, feature: true do
       click_milestone(milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%#{milestone.title}")
+      expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
     end
 
     it 'fills in the milestone name when the milestone is partially filled' do
@@ -135,56 +135,56 @@ describe 'Dropdown milestone', js: true, feature: true do
       click_milestone(milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%#{milestone.title}")
+      expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
     end
 
     it 'fills in the milestone name that contains multiple words' do
       click_milestone(two_words_milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\"")
+      expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ")
     end
 
     it 'fills in the milestone name that contains multiple words and is very long' do
       click_milestone(long_milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\"")
+      expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ")
     end
 
     it 'fills in the milestone name that contains double quotes' do
       click_milestone(wont_fix_milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}'")
+      expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ")
     end
 
     it 'fills in the milestone name with the correct capitalization' do
       click_milestone(uppercase_milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title}")
+      expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ")
     end
 
     it 'fills in the milestone name with special characters' do
       click_milestone(special_milestone.title)
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:%#{special_milestone.title}")
+      expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ")
     end
 
     it 'selects `no milestone`' do
       click_static_milestone('No Milestone')
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:none")
+      expect(filtered_search.value).to eq("milestone:none ")
     end
 
     it 'selects `upcoming milestone`' do
       click_static_milestone('Upcoming')
 
       expect(page).to have_css(js_dropdown_milestone, visible: false)
-      expect(filtered_search.value).to eq("milestone:upcoming")
+      expect(filtered_search.value).to eq("milestone:upcoming ")
     end
   end
 
@@ -219,4 +219,21 @@ describe 'Dropdown milestone', js: true, feature: true do
       expect(page).to have_css(js_dropdown_milestone, visible: true)
     end
   end
+
+  describe 'caching requests' do
+    it 'caches requests after the first load' do
+      filtered_search.set('milestone')
+      send_keys_to_filtered_search(':')
+      initial_size = dropdown_milestone_size
+
+      expect(initial_size).to be > 0
+
+      create(:milestone, project: project)
+      find('.filtered-search-input-container .clear-search').click
+      filtered_search.set('milestone')
+      send_keys_to_filtered_search(':')
+
+      expect(dropdown_milestone_size).to eq(initial_size)
+    end
+  end
 end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index ead43d6784a3378e7b458aeed8ade8e7c7a7f2d3..3f70a6aa75f2e5f2a1efee62c5c3a9037462b028 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -19,9 +19,12 @@ describe 'Filter issues', js: true, feature: true do
   let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
   let(:filtered_search) { find('.filtered-search') }
 
-  def input_filtered_search(search_term)
+  def input_filtered_search(search_term, submit: true)
     filtered_search.set(search_term)
-    filtered_search.send_keys(:enter)
+
+    if submit
+      filtered_search.send_keys(:enter)
+    end
   end
 
   def expect_filtered_search_input(input)
@@ -43,6 +46,10 @@ describe 'Filter issues', js: true, feature: true do
     end
   end
 
+  def select_search_at_index(pos)
+    evaluate_script("el = document.querySelector('.filtered-search'); el.focus(); el.setSelectionRange(#{pos}, #{pos});")
+  end
+
   before do
     project.team << [user, :master]
     project.team << [user2, :master]
@@ -522,6 +529,44 @@ describe 'Filter issues', js: true, feature: true do
     end
   end
 
+  describe 'overwrites selected filter' do
+    it 'changes author' do
+      input_filtered_search("author:@#{user.username}", submit: false)
+
+      select_search_at_index(3)
+
+      page.within '#js-dropdown-author' do
+        click_button user2.username
+      end
+
+      expect(filtered_search.value).to eq("author:@#{user2.username} ")
+    end
+
+    it 'changes label' do
+      input_filtered_search("author:@#{user.username} label:~#{bug_label.title}", submit: false)
+
+      select_search_at_index(27)
+
+      page.within '#js-dropdown-label' do
+        click_button label.name
+      end
+
+      expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ")
+    end
+
+    it 'changes label correctly space is in previous label' do
+      input_filtered_search("label:~\"#{multiple_words_label.title}\"", submit: false)
+
+      select_search_at_index(0)
+
+      page.within '#js-dropdown-label' do
+        click_button label.name
+      end
+
+      expect(filtered_search.value).to eq("label:~#{label.name} ")
+    end
+  end
+
   describe 'filter issues by text' do
     context 'only text' do
       it 'filters issues by searched text' do
@@ -728,7 +773,7 @@ describe 'Filter issues', js: true, feature: true do
   describe 'RSS feeds' do
     it 'updates atom feed link for project issues' do
       visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id)
-      link = find('.nav-controls a', text: 'Subscribe')
+      link = find_link('Subscribe')
       params = CGI.parse(URI.parse(link[:href]).query)
       auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
       auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 56b1d354eb015f245c7d16d851d79819e4bedd65..90eb60eb3376325a862711b25c2c802be35905d5 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do
     left_style.to_s.gsub('left: ', '').to_f
   end
 
+  describe 'keyboard navigation' do
+    it 'makes item active' do
+      filtered_search.native.send_keys(:down)
+
+      page.within '#js-dropdown-hint' do
+        expect(page).to have_selector('.dropdown-active')
+      end
+    end
+
+    it 'selects item' do
+      filtered_search.native.send_keys(:down, :down, :enter)
+
+      expect(filtered_search.value).to eq('author:')
+    end
+  end
+
   describe 'clear search button' do
     it 'clears text' do
       search_text = 'search_text'
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 8771cc8e157739451b05ba8eb366d9a79bfcb254..741ca95f1ca0a770d6ce468ca268f1979dea9d12 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -68,6 +68,22 @@ describe 'New/edit issue', feature: true, js: true do
         end
       end
     end
+
+    it 'correctly updates the dropdown toggle when removing a label' do
+      click_button 'Labels'
+
+      page.within '.dropdown-menu-labels' do
+        click_link label.title
+      end
+
+      expect(find('.js-label-select')).to have_content(label.title)
+
+      page.within '.dropdown-menu-labels' do
+        click_link label.title
+      end
+
+      expect(find('.js-label-select')).to have_content('Labels')
+    end
   end
 
   context 'edit issue' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 31156fcf99460cce033285d20c791677e20ecd09..93139dc9e94875bd31b05eb61ae23e864d3d9364 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
 
 feature 'GFM autocomplete', feature: true, js: true do
   include WaitForAjax
-  let(:user)    { create(:user, username: 'someone.special') }
+  let(:user)    { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
   let(:project) { create(:project) }
   let(:label) { create(:label, project: project, title: 'special+') }
   let(:issue)   { create(:issue, project: project) }
@@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do
     expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
   end
 
+  it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+    page.within '.timeline-content-form' do
+      find('#note_note').native.send_keys('')
+      find('#note_note').native.send_keys("@#{user.name[0...8]}")
+    end
+
+    expect(page).to have_selector('.atwho-container')
+
+    wait_for_ajax
+
+    expect(find('#at-view-64')).to have_content(user.name)
+  end
+
   it 'selects the first item for non-assignee dropdowns if a query is entered' do
     page.within '.timeline-content-form' do
       find('#note_note').native.send_keys('')
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc8515cfe9bc24092afb2467696744db3d402c3f
--- /dev/null
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe 'Group label on issue', :feature do
+  it 'renders link to the project issues page' do
+    group = create(:group)
+    project = create(:empty_project, :public, namespace: group)
+    feature = create(:group_label, group: group, title: 'feature')
+    issue = create(:labeled_issue, project: project, labels: [feature])
+    label_link = namespace_project_issues_path(
+      project.namespace,
+      project,
+      label_name: [feature.name]
+    )
+
+    visit namespace_project_issue_path(project.namespace, project, issue)
+
+    link = find('.issuable-show-labels a')
+
+    expect(link[:href]).to eq(label_link)
+  end
+end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index bc068b5e7e03826b1fa90ec781af477072ed0709..1eb981942eaa4a7690af385ead1ab9dc9b736fb3 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
 
 feature 'Issue Sidebar', feature: true do
   include WaitForAjax
+  include MobileHelpers
 
   let(:project) { create(:project, :public) }
   let(:issue) { create(:issue, project: project) }
@@ -59,6 +60,23 @@ feature 'Issue Sidebar', feature: true do
       end
     end
 
+    context 'sidebar', js: true do
+      it 'changes size when the screen size is smaller' do
+        sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
+        # Resize the window
+        resize_screen_sm
+        # Make sure the sidebar is collapsed
+        expect(page).to have_css(sidebar_selector)
+        # Once is collapsed let's open the sidebard and reload
+        open_issue_sidebar
+        refresh
+        expect(page).to have_css(sidebar_selector)
+        # Restore the window size as it was including the sidebar
+        restore_window_size
+        open_issue_sidebar
+      end
+    end
+
     context 'creating a new label', js: true do
       it 'shows option to crate a new label is present' do
         page.within('.block.labels') do
@@ -109,4 +127,11 @@ feature 'Issue Sidebar', feature: true do
   def visit_issue(project, issue)
     visit namespace_project_issue_path(project.namespace, project, issue)
   end
+
+  def open_issue_sidebar
+    page.within('aside.right-sidebar.right-sidebar-collapsed') do
+      find('.js-sidebar-toggle').click
+      sleep 1
+    end
+  end
 end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index a4d3053d10cd74144fd7bb011e5adc9eae672f0c..c0ab42c6822f8f9c90dafad72a0ab45e1a56f4dc 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-feature 'Start new branch from an issue', feature: true do
+feature 'Start new branch from an issue', feature: true, js: true do
   let!(:project)   { create(:project) }
   let!(:issue)     { create(:issue, project: project) }
   let!(:user)      { create(:user)}
@@ -11,7 +11,7 @@ feature 'Start new branch from an issue', feature: true do
       login_as(user)
     end
 
-    it 'shows the new branch button', js: true do
+    it 'shows the new branch button' do
       visit namespace_project_issue_path(project.namespace, project, issue)
 
       expect(page).to have_css('#new-branch .available')
@@ -34,16 +34,26 @@ feature 'Start new branch from an issue', feature: true do
         visit namespace_project_issue_path(project.namespace, project, issue)
       end
 
-      it "hides the new branch button", js: true do
+      it "hides the new branch button" do
         expect(page).to have_css('#new-branch .unavailable')
         expect(page).not_to have_css('#new-branch .available')
         expect(page).to have_content /1 Related Merge Request/
       end
     end
+
+    context 'when issue is confidential' do
+      it 'hides the new branch button' do
+        issue = create(:issue, :confidential, project: project)
+
+        visit namespace_project_issue_path(project.namespace, project, issue)
+
+        expect(page).not_to have_css('#new-branch')
+      end
+    end
   end
 
-  context "for visiters" do
-    it 'shows no buttons', js: true do
+  context 'for visitors' do
+    it 'shows no buttons' do
       visit namespace_project_issue_path(project.namespace, project, issue)
 
       expect(page).not_to have_css('#new-branch')
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 76bcfbe523a4a113a047a9908573288706703015..ab7d89306db893cd9a4653926e5ff82e14963d3c 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -25,6 +25,11 @@ feature 'Login', feature: true do
 
       expect(current_path).to eq root_path
     end
+
+    it 'does not show flash messages when login page' do
+      visit root_path
+      expect(page).not_to have_content('You need to sign in or sign up before continuing.')
+    end
   end
 
   describe 'with two-factor authentication' do
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f2f8f11ab283272d8c0d8abc18aaa5013a4da168
--- /dev/null
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+feature 'Merge immediately', :feature, :js do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :public) }
+
+  let(:merge_request) do
+    create(:merge_request_with_diffs, source_project: project,
+                                      author: user,
+                                      title: 'Bug NS-04')
+  end
+
+  let(:pipeline) do
+    create(:ci_pipeline, project: project,
+                         sha: merge_request.diff_head_sha,
+                         ref: merge_request.source_branch)
+  end
+
+  before { project.team << [user, :master] }
+
+  context 'when there is active pipeline for merge request' do
+    background do
+      create(:ci_build, pipeline: pipeline)
+    end
+
+    before do
+      login_as user
+      visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+    end
+
+    it 'enables merge immediately' do
+      page.within '.mr-widget-body' do
+        find('.dropdown-toggle').click
+
+        click_link 'Merge Immediately'
+
+        expect(find('.js-merge-button')).to have_content('Merge in progress')
+      end
+    end
+  end
+end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index aa24a9050016ca096232142f9d708346b5101ed4..2ea9c317bd1a7737f35f8fa95177d7a072b89955 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -32,19 +32,61 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
       expect(page).to have_button "Merge When Pipeline Succeeds"
     end
 
-    context "Merge When Pipeline Succeeds enabled" do
-      before do
-        click_button "Merge When Pipeline Succeeds"
+    describe 'enabling Merge When Pipeline Succeeds' do
+      shared_examples 'Merge When Pipeline Succeeds activator' do
+        it 'activates the Merge When Pipeline Succeeds feature' do
+          click_button "Merge When Pipeline Succeeds"
+
+          expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
+          expect(page).to have_content "The source branch will not be removed."
+          expect(page).to have_link "Cancel Automatic Merge"
+          visit_merge_request(merge_request) # Needed to refresh the page
+          expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
+        end
       end
 
-      it 'activates Merge When Pipeline Succeeds feature' do
-        expect(page).to have_link "Cancel Automatic Merge"
+      context "when enabled immediately" do
+        it_behaves_like 'Merge When Pipeline Succeeds activator'
+      end
+
+      context 'when enabled after pipeline status changed' do
+        before do
+          pipeline.run!
 
-        expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
-        expect(page).to have_content "The source branch will not be removed."
+          # We depend on merge request widget being reloaded
+          # so we have to wait for asynchronous call to reload it
+          # and have_content expectation handles that.
+          #
+          expect(page).to have_content "Pipeline ##{pipeline.id} running"
+        end
+
+        it_behaves_like 'Merge When Pipeline Succeeds activator'
+      end
+
+      context 'when enabled after it was previously canceled' do
+        before do
+          click_button "Merge When Pipeline Succeeds"
+          click_link "Cancel Automatic Merge"
+        end
+
+        it_behaves_like 'Merge When Pipeline Succeeds activator'
+      end
 
-        visit_merge_request(merge_request) # Needed to refresh the page
-        expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
+      context 'when it was enabled and then canceled' do
+        let(:merge_request) do
+          create(:merge_request_with_diffs,
+                 :merge_when_build_succeeds,
+                   source_project: project,
+                   title: 'Bug NS-04',
+                   author: user,
+                   merge_user: user)
+        end
+
+        before do
+          click_link "Cancel Automatic Merge"
+        end
+
+        it_behaves_like 'Merge When Pipeline Succeeds activator'
       end
     end
   end
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 7e2907cd26fa1ff4ed8c2671e1ef2509d22e9237..d2f5c4afc93f8e3ddc274288e3638d018ccf8ee1 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -50,7 +50,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
           visit_merge_request(merge_request)
 
           expect(page).not_to have_button 'Accept Merge Request'
-          expect(page).to have_content('Please retry the build or push a new commit to fix the failure.')
+          expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
         end
       end
 
@@ -61,7 +61,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
           visit_merge_request(merge_request)
 
           expect(page).not_to have_button 'Accept Merge Request'
-          expect(page).to have_content('Please retry the build or push a new commit to fix the failure.')
+          expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
         end
       end
 
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..44a9b545ff886816bfec8d3373b316c26bec3d6a
--- /dev/null
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'toggler_behavior', js: true, feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:merge_request) { create(:merge_request, source_project: project, author: user) }
+  let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+  let(:fragment_id) { "#note_#{note.id}" }
+
+  before do
+    login_as :admin
+    project = merge_request.source_project
+    visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+    page.current_window.resize_to(1000, 300)
+  end
+
+  describe 'scroll position' do
+    it 'should be scrolled down to fragment' do
+      page_height = page.current_window.size[1]
+      page_scroll_y = page.evaluate_script("window.scrollY")
+      fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top")
+      expect(find('.js-toggle-content').visible?).to eq true
+      expect(find(fragment_id).visible?).to eq true
+      expect(fragment_position_top).to be >= page_scroll_y
+      expect(fragment_position_top).to be < (page_scroll_y + page_height)
+    end
+  end
+end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index b13674b4db9fca8d25caffe9703a067c35916339..2582a540240b9b5d690569ea990d4b161b7943f6 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -11,7 +11,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
 
   it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do
     let(:issuable) { create(:merge_request, source_project: project) }
-    let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
+    let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
   end
 
   describe 'merge-request-only commands' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d1805f500123a6638f3f24dc00622fbbf37a6d6
--- /dev/null
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe 'Merge request', :feature, :js do
+  include WaitForAjax
+
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+
+  before do
+    project.team << [user, :master]
+    login_as(user)
+
+    visit new_namespace_project_merge_request_path(
+      project.namespace,
+      project,
+      merge_request: {
+        source_project_id: project.id,
+        target_project_id: project.id,
+        source_branch: 'feature',
+        target_branch: 'master'
+      }
+    )
+  end
+
+  it 'shows widget status after creating new merge request' do
+    click_button 'Submit merge request'
+
+    expect(find('.mr-state-widget')).to have_content('Checking ability to merge automatically')
+
+    wait_for_ajax
+
+    expect(page).to have_selector('.accept_merge_request')
+  end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index b785b2f7704a580c9e4387f0a4f5bd5685ff71bb..fab2d532e06be1bc9c2404682df8bc056a58b12f 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -89,7 +89,7 @@ describe 'Comments', feature: true do
           end
         end
 
-        it 'should reset the edit note form textarea with the original content of the note if cancelled' do
+        it 'resets the edit note form textarea with the original content of the note if cancelled' do
           within('.current-note-edit-form') do
             fill_in 'note[note]', with: 'Some new content'
             find('.btn-cancel').click
@@ -198,7 +198,7 @@ describe 'Comments', feature: true do
       end
 
       describe 'the note form' do
-        it "shouldn't add a second form for same row" do
+        it "does not add a second form for same row" do
           click_diff_line
 
           is_expected.
@@ -206,7 +206,7 @@ describe 'Comments', feature: true do
                         count: 1)
         end
 
-        it 'should be removed when canceled' do
+        it 'is removed when canceled' do
           is_expected.to have_css('.js-temp-notes-holder')
 
           page.within("form[data-line-code='#{line_code}']") do
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 11d27feab0b22232598254a6955456c579a05938..f7e0115643ef6b240c089a875984bc1f13a9b94b 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -27,7 +27,7 @@ feature 'Builds', :feature do
         visit namespace_project_builds_path(project.namespace, project, scope: :pending)
       end
 
-      it "shows Pending tab builds" do
+      it "shows Pending tab jobs" do
         expect(page).to have_link 'Cancel running'
         expect(page).to have_selector('.nav-links li.active', text: 'Pending')
         expect(page).to have_content build.short_sha
@@ -42,7 +42,7 @@ feature 'Builds', :feature do
         visit namespace_project_builds_path(project.namespace, project, scope: :running)
       end
 
-      it "shows Running tab builds" do
+      it "shows Running tab jobs" do
         expect(page).to have_selector('.nav-links li.active', text: 'Running')
         expect(page).to have_link 'Cancel running'
         expect(page).to have_content build.short_sha
@@ -57,20 +57,20 @@ feature 'Builds', :feature do
         visit namespace_project_builds_path(project.namespace, project, scope: :finished)
       end
 
-      it "shows Finished tab builds" do
+      it "shows Finished tab jobs" do
         expect(page).to have_selector('.nav-links li.active', text: 'Finished')
-        expect(page).to have_content 'No builds to show'
+        expect(page).to have_content 'No jobs to show'
         expect(page).to have_link 'Cancel running'
       end
     end
 
-    context "All builds" do
+    context "All jobs" do
       before do
         project.builds.running_or_pending.each(&:success)
         visit namespace_project_builds_path(project.namespace, project)
       end
 
-      it "shows All tab builds" do
+      it "shows All tab jobs" do
         expect(page).to have_selector('.nav-links li.active', text: 'All')
         expect(page).to have_content build.short_sha
         expect(page).to have_content build.ref
@@ -98,7 +98,7 @@ feature 'Builds', :feature do
   end
 
   describe "GET /:project/builds/:id" do
-    context "Build from project" do
+    context "Job from project" do
       before do
         visit namespace_project_build_path(project.namespace, project, build)
       end
@@ -111,7 +111,7 @@ feature 'Builds', :feature do
       end
     end
 
-    context "Build from other project" do
+    context "Job from other project" do
       before do
         visit namespace_project_build_path(project.namespace, project, build2)
       end
@@ -149,7 +149,7 @@ feature 'Builds', :feature do
       context 'when expire date is defined' do
         let(:expire_at) { Time.now + 7.days }
 
-        context 'when user has ability to update build' do
+        context 'when user has ability to update job' do
           it 'keeps artifacts when keep button is clicked' do
             expect(page).to have_content 'The artifacts will be removed'
 
@@ -160,7 +160,7 @@ feature 'Builds', :feature do
           end
         end
 
-        context 'when user does not have ability to update build' do
+        context 'when user does not have ability to update job' do
           let(:user_access_level) { :guest }
 
           it 'does not have keep button' do
@@ -197,8 +197,8 @@ feature 'Builds', :feature do
         visit namespace_project_build_path(project.namespace, project, build)
       end
 
-      context 'when build has an initial trace' do
-        it 'loads build trace' do
+      context 'when job has an initial trace' do
+        it 'loads job trace' do
           expect(page).to have_content 'BUILD TRACE'
 
           build.append_trace(' and more trace', 11)
@@ -242,32 +242,32 @@ feature 'Builds', :feature do
       end
     end
 
-    context 'when build starts environment' do
+    context 'when job starts environment' do
       let(:environment) { create(:environment, project: project) }
       let(:pipeline) { create(:ci_pipeline, project: project) }
 
-      context 'build is successfull and has deployment' do
+      context 'job is successfull and has deployment' do
         let(:deployment) { create(:deployment) }
         let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
 
-        it 'shows a link for the build' do
+        it 'shows a link for the job' do
           visit namespace_project_build_path(project.namespace, project, build)
 
           expect(page).to have_link environment.name
         end
       end
 
-      context 'build is complete and not successfull' do
+      context 'job is complete and not successfull' do
         let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
 
-        it 'shows a link for the build' do
+        it 'shows a link for the job' do
           visit namespace_project_build_path(project.namespace, project, build)
 
           expect(page).to have_link environment.name
         end
       end
 
-      context 'build creates a new deployment' do
+      context 'job creates a new deployment' do
         let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
         let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
 
@@ -281,7 +281,7 @@ feature 'Builds', :feature do
   end
 
   describe "POST /:project/builds/:id/cancel" do
-    context "Build from project" do
+    context "Job from project" do
       before do
         build.run!
         visit namespace_project_build_path(project.namespace, project, build)
@@ -295,7 +295,7 @@ feature 'Builds', :feature do
       end
     end
 
-    context "Build from other project" do
+    context "Job from other project" do
       before do
         build.run!
         visit namespace_project_build_path(project.namespace, project, build)
@@ -307,13 +307,13 @@ feature 'Builds', :feature do
   end
 
   describe "POST /:project/builds/:id/retry" do
-    context "Build from project" do
+    context "Job from project" do
       before do
         build.run!
         visit namespace_project_build_path(project.namespace, project, build)
         click_link 'Cancel'
         page.within('.build-header') do
-          click_link 'Retry build'
+          click_link 'Retry job'
         end
       end
 
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index fe047e00409e2a24ccc58d696a9b20a09d5b3c16..36a80d7575deb1728cf27103d1dd0f48556e680e 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -7,7 +7,7 @@ feature 'User wants to edit a file', feature: true do
   let(:user) { create(:user) }
   let(:commit_params) do
     {
-      source_branch: project.default_branch,
+      start_branch: project.default_branch,
       target_branch: project.default_branch,
       commit_message: "Committing First Update",
       file_path: ".gitignore",
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index a521ce50f3574c15d9db39efdda54119df7e01f9..64094af29c09cfaf640b49c33a3a27995d24ad10 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -6,7 +6,8 @@ feature 'project owner creates a license file', feature: true, js: true do
   let(:project_master) { create(:user) }
   let(:project) { create(:project) }
   background do
-    project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master')
+    project.repository.remove_file(project_master, 'LICENSE',
+      message: 'Remove LICENSE', branch_name: 'master')
     project.team << [project_master, :master]
     login_as(project_master)
     visit namespace_project_path(project.namespace, project)
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 52d08982c7accd656756e1855e4ec35324889837..16dddb2a86b92292645d45a4fb24a2f4d363701f 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -74,6 +74,9 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
         Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the key and the
         correspondent hash or model as the value.
 
+        Also, if the attribute is a generated unique token, please add it to RelationFactory::TOKEN_RESET_MODELS if it needs to be
+        reset (to prevent duplicate column problems while importing to the same instance).
+
         IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
         CURRENT_SPEC: #{__FILE__}
       MSG
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d0bafc6168cf7aeb44b73c9d416e8b54e738b20c
--- /dev/null
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do
+  let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
+  let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+
+  let(:project) { create(:empty_project) }
+
+  background do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+  end
+
+  after do
+    FileUtils.rm_rf(export_path, secure: true)
+  end
+
+  context 'admin user' do
+    before do
+      login_as(:admin)
+    end
+
+    context 'moving the namespace' do
+      scenario 'removes the export file' do
+        setup_export_project
+
+        old_export_path = project.export_path.dup
+
+        expect(File).to exist(old_export_path)
+
+        project.namespace.update(path: 'new_path')
+
+        expect(File).not_to exist(old_export_path)
+      end
+    end
+
+    context 'deleting the namespace' do
+      scenario 'removes the export file' do
+        setup_export_project
+
+        old_export_path = project.export_path.dup
+
+        expect(File).to exist(old_export_path)
+
+        project.namespace.destroy
+
+        expect(File).not_to exist(old_export_path)
+      end
+    end
+
+    def setup_export_project
+      visit edit_namespace_project_path(project.namespace, project)
+
+      expect(page).to have_content('Export project')
+
+      click_link 'Export project'
+
+      visit edit_namespace_project_path(project.namespace, project)
+
+      expect(page).to have_content('Download export')
+    end
+  end
+end
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 7655c2b351f9e81d820d29814af1e2554eed1797..20cdfbae24fefb2424cd4b4045d233d1601e0db1 100644
Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 6dae5c64b30edcfc9ce2d1da8770074244bf9260..e90a033b8c41fdf4579e3a5b653761de90812ee0 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -18,8 +18,20 @@ feature 'issuable templates', feature: true, js: true do
     let(:description_addition) { ' appending to description' }
 
     background do
-      project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
-      project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
+      project.repository.commit_file(
+        user,
+        '.gitlab/issue_templates/bug.md',
+        template_content,
+        message: 'added issue template',
+        branch_name: 'master',
+        update: false)
+      project.repository.commit_file(
+        user,
+        '.gitlab/issue_templates/test.md',
+        longtemplate_content,
+        message: 'added issue template',
+        branch_name: 'master',
+        update: false)
       visit edit_namespace_project_issue_path project.namespace, project, issue
       fill_in :'issue[title]', with: 'test issue title'
     end
@@ -67,7 +79,13 @@ feature 'issuable templates', feature: true, js: true do
     let(:issue) { create(:issue, author: user, assignee: user, project: project) }
 
     background do
-      project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
+      project.repository.commit_file(
+        user,
+        '.gitlab/issue_templates/bug.md',
+        template_content,
+        message: 'added issue template',
+        branch_name: 'master',
+        update: false)
       visit edit_namespace_project_issue_path project.namespace, project, issue
       fill_in :'issue[title]', with: 'test issue title'
       fill_in :'issue[description]', with: prior_description
@@ -86,7 +104,13 @@ feature 'issuable templates', feature: true, js: true do
     let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
 
     background do
-      project.repository.commit_file(user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+      project.repository.commit_file(
+        user,
+        '.gitlab/merge_request_templates/feature-proposal.md',
+        template_content,
+        message: 'added merge request template',
+        branch_name: 'master',
+        update: false)
       visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
@@ -111,7 +135,13 @@ feature 'issuable templates', feature: true, js: true do
       fork_project.team << [fork_user, :master]
       create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
       login_as fork_user
-      project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+      project.repository.commit_file(
+        fork_user,
+        '.gitlab/merge_request_templates/feature-proposal.md',
+        template_content,
+        message: 'added merge request template',
+        branch_name: 'master',
+        update: false)
       visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index c9fa8315e79e9d688de3f50f8dda4e3b835ecf48..97ce9cdfd87b2838cdb128d3fc824a046ba4e94c 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -20,7 +20,7 @@ feature 'Prioritize labels', feature: true do
     scenario 'user can prioritize a group label', js: true do
       visit namespace_project_labels_path(project.namespace, project)
 
-      expect(page).to have_content('No prioritized labels yet')
+      expect(page).to have_content('Star labels to start sorting by priority')
 
       page.within('.other-labels') do
         all('.js-toggle-priority')[1].click
@@ -29,7 +29,7 @@ feature 'Prioritize labels', feature: true do
       end
 
       page.within('.prioritized-labels') do
-        expect(page).not_to have_content('No prioritized labels yet')
+        expect(page).not_to have_content('Star labels to start sorting by priority')
         expect(page).to have_content('feature')
       end
     end
@@ -55,7 +55,7 @@ feature 'Prioritize labels', feature: true do
     scenario 'user can prioritize a project label', js: true do
       visit namespace_project_labels_path(project.namespace, project)
 
-      expect(page).to have_content('No prioritized labels yet')
+      expect(page).to have_content('Star labels to start sorting by priority')
 
       page.within('.other-labels') do
         first('.js-toggle-priority').click
@@ -64,7 +64,7 @@ feature 'Prioritize labels', feature: true do
       end
 
       page.within('.prioritized-labels') do
-        expect(page).not_to have_content('No prioritized labels yet')
+        expect(page).not_to have_content('Star labels to start sorting by priority')
         expect(page).to have_content('bug')
       end
     end
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6728960fb803c2d4c1284af01831a12a788186f
--- /dev/null
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -0,0 +1,108 @@
+require 'spec_helper'
+
+feature 'Merge Request button', feature: true do
+  shared_examples 'Merge Request button only shown when allowed' do
+    let(:user) { create(:user) }
+    let(:project) { create(:project, :public) }
+    let(:forked_project) { create(:project, :public, forked_from_project: project) }
+
+    context 'not logged in' do
+      it 'does not show Create Merge Request button' do
+        visit url
+
+        within("#content-body") do
+          expect(page).not_to have_link(label)
+        end
+      end
+    end
+
+    context 'logged in as developer' do
+      before do
+        login_as(user)
+        project.team << [user, :developer]
+      end
+
+      it 'shows Create Merge Request button' do
+        href = new_namespace_project_merge_request_path(project.namespace,
+                                                        project,
+                                                        merge_request: { source_branch: 'feature',
+                                                                         target_branch: 'master' })
+
+        visit url
+
+        within("#content-body") do
+          expect(page).to have_link(label, href: href)
+        end
+      end
+
+      context 'merge requests are disabled' do
+        before do
+          project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
+        end
+
+        it 'does not show Create Merge Request button' do
+          visit url
+
+          within("#content-body") do
+            expect(page).not_to have_link(label)
+          end
+        end
+      end
+    end
+
+    context 'logged in as non-member' do
+      before do
+        login_as(user)
+      end
+
+      it 'does not show Create Merge Request button' do
+        visit url
+
+        within("#content-body") do
+          expect(page).not_to have_link(label)
+        end
+      end
+
+      context 'on own fork of project' do
+        let(:user) { forked_project.owner }
+
+        it 'shows Create Merge Request button' do
+          href = new_namespace_project_merge_request_path(forked_project.namespace,
+                                                          forked_project,
+                                                          merge_request: { source_branch: 'feature',
+                                                                           target_branch: 'master' })
+
+          visit fork_url
+
+          within("#content-body") do
+            expect(page).to have_link(label, href: href)
+          end
+        end
+      end
+    end
+  end
+
+  context 'on branches page' do
+    it_behaves_like 'Merge Request button only shown when allowed' do
+      let(:label) { 'Merge Request' }
+      let(:url) { namespace_project_branches_path(project.namespace, project) }
+      let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) }
+    end
+  end
+
+  context 'on compare page' do
+    it_behaves_like 'Merge Request button only shown when allowed' do
+      let(:label) { 'Create Merge Request' }
+      let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') }
+      let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') }
+    end
+  end
+
+  context 'on commits page' do
+    it_behaves_like 'Merge Request button only shown when allowed' do
+      let(:label) { 'Create Merge Request' }
+      let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') }
+      let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') }
+    end
+  end
+end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index abfc46601fbe89968124ae2db792e46e77c14ca1..b56e562b2b63b8963f9c735a048dd4fd5a3fcf89 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,11 +1,13 @@
 require "spec_helper"
 
 feature "New project", feature: true do
-  context "Visibility level selector" do
-    let(:user) { create(:admin) }
+  let(:user) { create(:admin) }
 
-    before { login_as(user) }
+  before do
+    login_as(user)
+  end
 
+  context "Visibility level selector" do
     Gitlab::VisibilityLevel.options.each do |key, level|
       it "sets selector to #{key}" do
         stub_application_setting(default_project_visibility: level)
@@ -16,4 +18,16 @@ feature "New project", feature: true do
       end
     end
   end
+
+  context 'Import project options' do
+    before do
+      visit new_project_path
+    end
+
+    it 'does not autocomplete sensitive git repo URL' do
+      autocomplete = find('#project_import_url')['autocomplete']
+
+      expect(autocomplete).to eq('off')
+    end
+  end
 end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index e673ece37c3b2f6060cef18612f23fea9189aeed..0b5ccc8c51573b4a7e1317ae40c8c067fb1de5d4 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -66,8 +66,8 @@ describe 'Pipeline', :feature, :js do
       context 'when pipeline has running builds' do
         it 'shows a running icon and a cancel action for the running build' do
           page.within('#ci-badge-deploy') do
-            expect(page).to have_selector('.ci-status-icon-running')
-            expect(page).to have_selector('.ci-action-icon-container .fa-ban')
+            expect(page).to have_selector('.js-ci-status-icon-running')
+            expect(page).to have_selector('.js-icon-action-cancel')
             expect(page).to have_content('deploy')
           end
         end
@@ -82,63 +82,63 @@ describe 'Pipeline', :feature, :js do
       context 'when pipeline has successful builds' do
         it 'shows the success icon and a retry action for the successful build' do
           page.within('#ci-badge-build') do
-            expect(page).to have_selector('.ci-status-icon-success')
+            expect(page).to have_selector('.js-ci-status-icon-success')
             expect(page).to have_content('build')
           end
 
           page.within('#ci-badge-build .ci-action-icon-container') do
-            expect(page).to have_selector('.ci-action-icon-container .fa-refresh')
+            expect(page).to have_selector('.js-icon-action-retry')
           end
         end
 
-        it 'should be possible to retry the success build' do
+        it 'should be possible to retry the success job' do
           find('#ci-badge-build .ci-action-icon-container').trigger('click')
 
-          expect(page).not_to have_content('Retry build')
+          expect(page).not_to have_content('Retry job')
         end
       end
 
       context 'when pipeline has failed builds' do
         it 'shows the failed icon and a retry action for the failed build' do
           page.within('#ci-badge-test') do
-            expect(page).to have_selector('.ci-status-icon-failed')
+            expect(page).to have_selector('.js-ci-status-icon-failed')
             expect(page).to have_content('test')
           end
 
           page.within('#ci-badge-test .ci-action-icon-container') do
-            expect(page).to have_selector('.ci-action-icon-container .fa-refresh')
+            expect(page).to have_selector('.js-icon-action-retry')
           end
         end
 
         it 'should be possible to retry the failed build' do
           find('#ci-badge-test .ci-action-icon-container').trigger('click')
 
-          expect(page).not_to have_content('Retry build')
+          expect(page).not_to have_content('Retry job')
         end
       end
 
-      context 'when pipeline has manual builds' do
+      context 'when pipeline has manual jobs' do
         it 'shows the skipped icon and a play action for the manual build' do
           page.within('#ci-badge-manual-build') do
-            expect(page).to have_selector('.ci-status-icon-manual')
+            expect(page).to have_selector('.js-ci-status-icon-manual')
             expect(page).to have_content('manual')
           end
 
           page.within('#ci-badge-manual-build .ci-action-icon-container') do
-            expect(page).to have_selector('.ci-action-icon-container .fa-play')
+            expect(page).to have_selector('.js-icon-action-play')
           end
         end
 
-        it 'should be possible to play the manual build' do
+        it 'should be possible to play the manual job' do
           find('#ci-badge-manual-build .ci-action-icon-container').trigger('click')
 
-          expect(page).not_to have_content('Play build')
+          expect(page).not_to have_content('Play job')
         end
       end
 
-      context 'when pipeline has external build' do
+      context 'when pipeline has external job' do
         it 'shows the success icon and the generic comit status build' do
-          expect(page).to have_selector('.ci-status-icon-success')
+          expect(page).to have_selector('.js-ci-status-icon-success')
           expect(page).to have_content('jenkins')
           expect(page).to have_link('jenkins', href: 'http://gitlab.com/status')
         end
@@ -146,12 +146,12 @@ describe 'Pipeline', :feature, :js do
     end
 
     context 'page tabs' do
-      it 'shows Pipeline and Builds tabs with link' do
+      it 'shows Pipeline and Jobs tabs with link' do
         expect(page).to have_link('Pipeline')
-        expect(page).to have_link('Builds')
+        expect(page).to have_link('Jobs')
       end
 
-      it 'shows counter in Builds tab' do
+      it 'shows counter in Jobs tab' do
         expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
       end
 
@@ -160,7 +160,7 @@ describe 'Pipeline', :feature, :js do
       end
     end
 
-    context 'retrying builds' do
+    context 'retrying jobs' do
       it { expect(page).not_to have_content('retried') }
 
       context 'when retrying' do
@@ -170,7 +170,7 @@ describe 'Pipeline', :feature, :js do
       end
     end
 
-    context 'canceling builds' do
+    context 'canceling jobs' do
       it { expect(page).not_to have_selector('.ci-canceled') }
 
       context 'when canceling' do
@@ -191,7 +191,7 @@ describe 'Pipeline', :feature, :js do
       visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
     end
 
-    it 'shows a list of builds' do
+    it 'shows a list of jobs' do
       expect(page).to have_content('Test')
       expect(page).to have_content(build_passed.id)
       expect(page).to have_content('Deploy')
@@ -203,26 +203,26 @@ describe 'Pipeline', :feature, :js do
       expect(page).to have_link('Play')
     end
 
-    it 'shows Builds tab pane as active' do
+    it 'shows jobs 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
+      it 'shows Pipeline and Jobs tabs with link' do
         expect(page).to have_link('Pipeline')
-        expect(page).to have_link('Builds')
+        expect(page).to have_link('Jobs')
       end
 
-      it 'shows counter in Builds tab' do
+      it 'shows counter in Jobs tab' do
         expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
       end
 
-      it 'shows Builds tab as active' do
+      it 'shows Jobs tab as active' do
         expect(page).to have_css('li.js-builds-tab-link.active')
       end
     end
 
-    context 'retrying builds' do
+    context 'retrying jobs' do
       it { expect(page).not_to have_content('retried') }
 
       context 'when retrying' do
@@ -233,7 +233,7 @@ describe 'Pipeline', :feature, :js do
       end
     end
 
-    context 'canceling builds' do
+    context 'canceling jobs' do
       it { expect(page).not_to have_selector('.ci-canceled') }
 
       context 'when canceling' do
@@ -244,7 +244,7 @@ describe 'Pipeline', :feature, :js do
       end
     end
 
-    context 'playing manual build' do
+    context 'playing manual job' do
       before do
         within '.pipeline-holder' do
           click_link('Play')
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index 55d5d082c6e3174e1fe9f918b50775f777ec73c6..5d0314d5c098708c85967a7341cbb615ac6d022a 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -37,7 +37,7 @@ describe 'Edit Project Settings', feature: true do
     it 'shows errors for invalid project path/name' do
       visit edit_namespace_project_path(project.namespace, project)
 
-      fill_in 'Project name', with: 'foo&bar'
+      fill_in 'project_name', with: 'foo&bar'
       fill_in 'Path', with: 'foo&bar'
 
       click_button 'Rename project'
@@ -53,7 +53,7 @@ describe 'Edit Project Settings', feature: true do
     it 'shows error for invalid project name' do
       visit edit_namespace_project_path(project.namespace, project)
 
-      fill_in 'Project name', with: '🚀 foo bar ☁️'
+      fill_in 'project_name', with: '🚀 foo bar ☁️'
 
       click_button 'Rename project'
 
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 472491188c9aecba02ee75bc021688f8a69fdd34..38fe2d928851667a0b80565b3f36efaa50684fe3 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -17,14 +17,15 @@ feature 'Ref switcher', feature: true, js: true do
 
     page.within '.project-refs-form' do
       input = find('input[type="search"]')
-      input.set 'expand'
+      input.set 'binary'
+      wait_for_ajax
 
       input.native.send_keys :down
       input.native.send_keys :down
       input.native.send_keys :enter
     end
 
-    expect(page).to have_title 'expand-collapse-files'
+    expect(page).to have_title 'binary-encoding'
   end
 
   it "user selects ref with special characters" do
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index 86a07b2c679cef209b3bade257591a918581c5ef..042a1ccab51776c2949f7df690de1d93b170ba2f 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -99,6 +99,15 @@ feature 'Setup Mattermost slash commands', feature: true do
       expect(select_element.all('option').count).to eq(3)
     end
 
+    it 'shows an error alert with the error message if there is an error requesting teams' do
+      allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [[], 'test mattermost error message'] }
+
+      click_link 'Add to Mattermost'
+
+      expect(page).to have_selector('.alert')
+      expect(page).to have_content('test mattermost error message')
+    end
+
     def stub_teams(count: 0)
       teams = create_teams(count)
 
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index 4bfaa4992725b7bfb45051881bf28a5a4a0e37b3..034b75c2e5107ced324538d0d36fccf5a48e367c 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -11,41 +11,41 @@ feature 'Project settings > Merge Requests', feature: true, js: true do
     login_as(user)
   end
 
-  context 'when Merge Request and Builds are initially enabled' do
+  context 'when Merge Request and Pipelines are initially enabled' do
     before do
       project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::ENABLED)
     end
 
-    context 'when Builds are initially enabled' do
+    context 'when Pipelines are initially enabled' do
       before do
         project.project_feature.update_attribute('builds_access_level', ProjectFeature::ENABLED)
         visit edit_project_path(project)
       end
 
       scenario 'shows the Merge Requests settings' do
-        expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
         expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
         select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level"
 
-        expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
         expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
       end
     end
 
-    context 'when Builds are initially disabled' do
+    context 'when Pipelines are initially disabled' do
       before do
         project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED)
         visit edit_project_path(project)
       end
 
       scenario 'shows the Merge Requests settings that do not depend on Builds feature' do
-        expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+        expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
         expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
         select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level"
 
-        expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+        expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
         expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
       end
     end
@@ -58,12 +58,12 @@ feature 'Project settings > Merge Requests', feature: true, js: true do
     end
 
     scenario 'does not show the Merge Requests settings' do
-      expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+      expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
       expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
       select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level"
 
-      expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+      expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
       expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
     end
   end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index a05b83959fb70379fcdd45095925dcf95750974e..0fe5a897565dc68648f282d0c00ef0c0e726afac 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -211,4 +211,44 @@ describe "Search", feature: true  do
       end
     end
   end
+
+  describe 'search for commits' do
+    before do
+      visit search_path(project_id: project.id)
+    end
+
+    it 'redirects to commit page when search by sha and only commit found' do
+      fill_in 'search', with: '6d394385cf567f80a8fd85055db1ab4c5295806f'
+
+      click_button 'Search'
+
+      expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+    end
+
+    it 'redirects to single commit regardless of query case' do
+      fill_in 'search', with: '6D394385cf'
+
+      click_button 'Search'
+
+      expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+    end
+
+    it 'holds on /search page when the only commit is found by message' do
+      create_commit('Message referencing another sha: "deadbeef" ', project, user, 'master')
+
+      fill_in 'search', with: 'deadbeef'
+      click_button 'Search'
+
+      expect(page).to have_current_path('/search', only_path: true)
+    end
+
+    it 'shows multiple matching commits' do
+      fill_in 'search', with: 'See merge request'
+
+      click_button 'Search'
+      click_link 'Commits'
+
+      expect(page).to have_selector('.commit-row-description', count: 9)
+    end
+  end
 end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index abb27c90e0a969ec83590d5162079c886ad663e5..a5d14aa19f1606f7b0608b4ca4a189e927ef298f 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -36,6 +36,19 @@ feature 'Task Lists', feature: true do
     MARKDOWN
   end
 
+  let(:nested_tasks_markdown) do
+    <<-EOT.strip_heredoc
+    - [ ] Task a
+      - [x] Task a.1
+      - [ ] Task a.2
+    - [ ] Task b
+
+    1. [ ] Task 1
+      1. [ ] Task 1.1
+      1. [x] Task 1.2
+    EOT
+  end
+
   before do
     Warden.test_mode!
 
@@ -123,6 +136,35 @@ feature 'Task Lists', feature: true do
         expect(page).to have_content("1 of 1 task completed")
       end
     end
+
+    describe 'nested tasks', js: true do
+      let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) }
+
+      before { visit_issue(project, issue) }
+
+      it 'renders' do
+        expect(page).to have_selector('ul.task-list',      count: 2)
+        expect(page).to have_selector('li.task-list-item', count: 7)
+        expect(page).to have_selector('ul input[checked]', count: 1)
+        expect(page).to have_selector('ol input[checked]', count: 1)
+      end
+
+      it 'solves tasks' do
+        expect(page).to have_content("2 of 7 tasks completed")
+
+        page.find('li.task-list-item', text: 'Task b').find('input').click
+        page.find('li.task-list-item ul li.task-list-item', text: 'Task a.2').find('input').click
+        page.find('li.task-list-item ol li.task-list-item', text: 'Task 1.1').find('input').click
+
+        expect(page).to have_content("5 of 7 tasks completed")
+
+        visit_issue(project, issue) # reload to see new system notes
+
+        expect(page).to have_content('marked the task Task b as complete')
+        expect(page).to have_content('marked the task Task a.2 as complete')
+        expect(page).to have_content('marked the task Task 1.1 as complete')
+      end
+    end
   end
 
   describe 'for Notes' do
@@ -236,7 +278,7 @@ feature 'Task Lists', feature: true do
         expect(page).to have_content("2 of 6 tasks completed")
       end
     end
-    
+
     describe 'single incomplete task' do
       let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) }
 
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 3850e930b6d73bb09ad050095e8e6796830d5a6c..1b352be9331536e7c51d882f8c0b349852eb1ee1 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -171,7 +171,7 @@ describe 'Dashboard Todos', feature: true do
       it 'links to the pipelines for the merge request' do
         href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
 
-        expect(page).to have_link "merge request #{todo.target.to_reference}", href
+        expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
       end
     end
   end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index db60c01db0d8591b3a5cce8f15a965d39bb71a79..91f34973ba5ddc7bf0d74435b2122e3f68016f79 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe BranchesFinder do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
 
   describe '#execute' do
diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb
index 65d7f14c72143258e9cc634dbc8bc555e43ced49..ad2d456529a858be018ca0dfc02633b0e6aaebc7 100644
--- a/spec/finders/contributed_projects_finder_spec.rb
+++ b/spec/finders/contributed_projects_finder_spec.rb
@@ -6,8 +6,8 @@ describe ContributedProjectsFinder do
 
   let(:finder) { described_class.new(source_user) }
 
-  let!(:public_project) { create(:project, :public) }
-  let!(:private_project) { create(:project, :private) }
+  let!(:public_project) { create(:empty_project, :public) }
+  let!(:private_project) { create(:empty_project, :private) }
 
   before do
     private_project.team << [source_user, Gitlab::Access::MASTER]
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index 00eec3f3f4c9887661e39b067804cde508cb0a62..ef97b061ca7b5398078dcec79b1621905d0cf720 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -6,11 +6,11 @@ describe GroupProjectsFinder do
 
   let(:finder) { described_class.new(source_user) }
 
-  let!(:public_project) { create(:project, :public, group: group, path: '1') }
-  let!(:private_project) { create(:project, :private, group: group, path: '2') }
-  let!(:shared_project_1) { create(:project, :public, path: '3') }
-  let!(:shared_project_2) { create(:project, :private, path: '4') }
-  let!(:shared_project_3) { create(:project, :internal, path: '5') }
+  let!(:public_project) { create(:empty_project, :public, group: group, path: '1') }
+  let!(:private_project) { create(:empty_project, :private, group: group, path: '2') }
+  let!(:shared_project_1) { create(:empty_project, :public, path: '3') }
+  let!(:shared_project_2) { create(:empty_project, :private, path: '4') }
+  let!(:shared_project_3) { create(:empty_project, :internal, path: '5') }
 
   before do
     shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb
index 29a47e005a669313151a1be73d5b71fae22562e9..4c389746252beb09d309ce5b00dea29e7402e7d5 100644
--- a/spec/finders/joined_groups_finder_spec.rb
+++ b/spec/finders/joined_groups_finder_spec.rb
@@ -42,7 +42,7 @@ describe JoinedGroupsFinder do
 
       context 'if profile visitor is in one of the private group projects' do
         before do
-          project = create(:project, :private, group: private_group, name: 'B', path: 'B')
+          project = create(:empty_project, :private, group: private_group, name: 'B', path: 'B')
           project.add_user(profile_visitor, Gitlab::Access::DEVELOPER)
         end
 
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 88361e27102e32ec71fd79d92119cec78a5ae4dd..3dcd7781e5b73d08b3ae349c8d2103007a78f61b 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -4,9 +4,9 @@ describe MergeRequestsFinder do
   let(:user)  { create :user }
   let(:user2) { create :user }
 
-  let(:project1) { create(:project) }
-  let(:project2) { create(:project, forked_from_project: project1) }
-  let(:project3) { create(:project, forked_from_project: project1, archived: true) }
+  let(:project1) { create(:empty_project) }
+  let(:project2) { create(:empty_project, forked_from_project: project1) }
+  let(:project3) { create(:empty_project, :archived, forked_from_project: project1) }
 
   let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
   let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb
index 8488dbd2a1634bd495e910d8aac6c4afcc75b97b..dea87980e25f8459520c8c22ac81d2bd9754b785 100644
--- a/spec/finders/move_to_project_finder_spec.rb
+++ b/spec/finders/move_to_project_finder_spec.rb
@@ -36,7 +36,7 @@ describe MoveToProjectFinder do
 
       it 'does not return archived projects' do
         reporter_project.team << [user, :reporter]
-        reporter_project.update_attributes(archived: true)
+        reporter_project.archive!
         other_reporter_project = create(:empty_project)
         other_reporter_project.team << [user, :reporter]
 
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 4d21254323c78d104a5dbced40dd20f5c141e083..bac653ea451b7e66315a5fa6ec4244d7d8b9ff4d 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -28,7 +28,7 @@ describe NotesFinder do
     end
 
     it "excludes notes on commits the author can't download" do
-      project = create(:project, :private)
+      project = create(:project, :private, :repository)
       note = create(:note_on_commit, project: project)
       params = { target_type: 'commit', target_id: note.noteable.id }
 
@@ -76,7 +76,7 @@ describe NotesFinder do
     end
 
     context 'for target' do
-      let(:project) { create(:project) }
+      let(:project) { create(:project, :repository) }
       let(:note1) { create :note_on_commit, project: project }
       let(:note2) { create :note_on_commit, project: project }
       let(:commit) { note1.noteable }
diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb
index a4681fe59d899bcd75c8a6ccf7c9f1692d61743a..e0e17af681a19877770c4d1fc6ae06ecc89ea9d3 100644
--- a/spec/finders/personal_projects_finder_spec.rb
+++ b/spec/finders/personal_projects_finder_spec.rb
@@ -4,14 +4,14 @@ describe PersonalProjectsFinder do
   let(:source_user)     { create(:user) }
   let(:current_user)    { create(:user) }
   let(:finder)          { described_class.new(source_user) }
-  let!(:public_project) { create(:project, :public, namespace: source_user.namespace) }
+  let!(:public_project) { create(:empty_project, :public, namespace: source_user.namespace) }
 
   let!(:private_project) do
-    create(:project, :private, namespace: source_user.namespace, path: 'mepmep')
+    create(:empty_project, :private, namespace: source_user.namespace, path: 'mepmep')
   end
 
   let!(:internal_project) do
-    create(:project, :internal, namespace: source_user.namespace, path: 'C')
+    create(:empty_project, :internal, namespace: source_user.namespace, path: 'C')
   end
 
   before do
diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb
index b0811d134faa2ccc60ba70fb2bda82cbb13d72c3..fdc8215aa47114d3160ffe0303a4b86eaab9e98e 100644
--- a/spec/finders/pipelines_finder_spec.rb
+++ b/spec/finders/pipelines_finder_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe PipelinesFinder do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   let!(:tag_pipeline)    { create(:ci_pipeline, project: project, ref: 'v1.0.0') }
   let!(:branch_pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 13bda5f7c5ad5ea79f6544cfcc14b293f3b60fcb..e44e7434c800db99c03e7f4e6143ece234d7b13a 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -6,19 +6,19 @@ describe ProjectsFinder do
     let(:group) { create(:group, :public) }
 
     let!(:private_project) do
-      create(:project, :private, name: 'A', path: 'A')
+      create(:empty_project, :private, name: 'A', path: 'A')
     end
 
     let!(:internal_project) do
-      create(:project, :internal, group: group, name: 'B', path: 'B')
+      create(:empty_project, :internal, group: group, name: 'B', path: 'B')
     end
 
     let!(:public_project) do
-      create(:project, :public, group: group, name: 'C', path: 'C')
+      create(:empty_project, :public, group: group, name: 'C', path: 'C')
     end
 
     let!(:shared_project) do
-      create(:project, :private, name: 'D', path: 'D')
+      create(:empty_project, :private, name: 'D', path: 'D')
     end
 
     let(:finder) { described_class.new }
diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb
index 98b42e264dc23a768c380ee9a9eff550c3d974b1..460e278e2d37ca193dd176cc3e9952d655277461 100644
--- a/spec/finders/tags_finder_spec.rb
+++ b/spec/finders/tags_finder_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe TagsFinder do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
 
   describe '#execute' do
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 92053e5a7c64dcd4a5da7df935ec65c06802a566..8b201f348f19d2fc181b3b5544e52df2954ebee7 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -55,7 +55,7 @@ describe ApplicationHelper do
 
   describe 'project_icon' do
     it 'returns an url for the avatar' do
-      project = create(:project, avatar: File.open(uploaded_image_temp_path))
+      project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
 
       avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/project/avatar/#{project.id}/banana_sample.gif"
       expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
@@ -63,7 +63,7 @@ describe ApplicationHelper do
     end
 
     it 'gives uploaded icon when present' do
-      project = create(:project)
+      project = create(:empty_project)
 
       allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
 
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index a43a7238c708b1ac9a5ba87d02e6e9ba5f61602a..fa516f9903ecc9a83c1730959d4a49394446b01b 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -70,7 +70,7 @@ describe BlobHelper do
 
   describe "#edit_blob_link" do
     let(:namespace) { create(:namespace, name: 'gitlab' )}
-    let(:project) { create(:project, namespace: namespace) }
+    let(:project) { create(:project, :repository, namespace: namespace) }
 
     before do
       allow(self).to receive(:current_user).and_return(double)
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 468bcc7badc283d5a945b068f5c50792dcf616cc..eae097126ce3a80dcb4d78222f4813da6c533f46 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -134,7 +134,7 @@ describe DiffHelper do
     let(:new_pos) { 50 }
     let(:text) { 'some_text' }
 
-    it "should generate foldable top match line for inline view with empty text by default" do
+    it "generates foldable top match line for inline view with empty text by default" do
       output = diff_match_line old_pos, new_pos
 
       expect(output).to be_html_safe
@@ -143,7 +143,7 @@ describe DiffHelper do
       expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: ''
     end
 
-    it "should allow to define text and bottom option" do
+    it "allows to define text and bottom option" do
       output = diff_match_line old_pos, new_pos, text: text, bottom: true
 
       expect(output).to be_html_safe
@@ -152,7 +152,7 @@ describe DiffHelper do
       expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text
     end
 
-    it "should generate match line for parallel view" do
+    it "generates match line for parallel view" do
       output = diff_match_line old_pos, new_pos, text: text, view: :parallel
 
       expect(output).to be_html_safe
@@ -162,7 +162,7 @@ describe DiffHelper do
       expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text
     end
 
-    it "should allow to generate only left match line for parallel view" do
+    it "allows to generate only left match line for parallel view" do
       output = diff_match_line old_pos, nil, text: text, view: :parallel
 
       expect(output).to be_html_safe
@@ -171,7 +171,7 @@ describe DiffHelper do
       expect(output).not_to have_css 'td:nth-child(3)'
     end
 
-    it "should allow to generate only right match line for parallel view" do
+    it "allows to generate only right match line for parallel view" do
       output = diff_match_line nil, new_pos, text: text, view: :parallel
 
       expect(output).to be_html_safe
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 810311e2b1a2ff2d06ebbc5b49dd9be1ee05c4d6..b8ec3521edb9892e9f0a56baa0c4a0ff9ac3402f 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe GitlabMarkdownHelper do
   include ApplicationHelper
 
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project, :repository) }
 
   let(:user)          { create(:user, username: 'gfm') }
   let(:commit)        { project.commit }
@@ -55,18 +55,18 @@ describe GitlabMarkdownHelper do
   end
 
   describe '#link_to_gfm' do
-    let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) }
-    let(:issues)      { create_list(:issue, 2, project: project) }
+    let(:link)    { '/commits/0a1b2c3d' }
+    let(:issues)  { create_list(:issue, 2, project: project) }
 
     it 'handles references nested in links with all the text' do
-      actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
+      actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link)
       doc = Nokogiri::HTML.parse(actual)
 
       # Make sure we didn't create invalid markup
       expect(doc.errors).to be_empty
 
       # Leading commit link
-      expect(doc.css('a')[0].attr('href')).to eq commit_path
+      expect(doc.css('a')[0].attr('href')).to eq link
       expect(doc.css('a')[0].text).to eq 'This should finally fix '
 
       # First issue link
@@ -75,7 +75,7 @@ describe GitlabMarkdownHelper do
       expect(doc.css('a')[1].text).to eq issues[0].to_reference
 
       # Internal commit link
-      expect(doc.css('a')[2].attr('href')).to eq commit_path
+      expect(doc.css('a')[2].attr('href')).to eq link
       expect(doc.css('a')[2].text).to eq ' and '
 
       # Second issue link
@@ -84,12 +84,12 @@ describe GitlabMarkdownHelper do
       expect(doc.css('a')[3].text).to eq issues[1].to_reference
 
       # Trailing commit link
-      expect(doc.css('a')[4].attr('href')).to eq commit_path
+      expect(doc.css('a')[4].attr('href')).to eq link
       expect(doc.css('a')[4].text).to eq ' for real'
     end
 
     it 'forwards HTML options' do
-      actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
+      actual = helper.link_to_gfm("Fixed in #{commit.id}", link, class: 'foo')
       doc = Nokogiri::HTML.parse(actual)
 
       expect(doc.css('a')).to satisfy do |v|
@@ -100,7 +100,7 @@ describe GitlabMarkdownHelper do
 
     it "escapes HTML passed in as the body" do
       actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
-      expect(helper.link_to_gfm(actual, commit_path)).
+      expect(helper.link_to_gfm(actual, link)).
         to match('&lt;h1&gt;test&lt;/h1&gt;')
     end
 
diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb
index 51c49f0e5871b8c912f123d024229df3f77ae7b6..400635abdde451c5315b32c3757f13d17ba62442 100644
--- a/spec/helpers/graph_helper_spec.rb
+++ b/spec/helpers/graph_helper_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe GraphHelper do
   describe '#get_refs' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:commit)  { project.commit("master") }
     let(:graph) { Network::Graph.new(project, 'master', commit, '') }
 
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index a4f08dc4af0b0eb1e5d76a9131a8afe8e2fd72a4..df71680e44ce3f5b9b07172dcd42bfc9f43a7c9a 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -115,6 +115,46 @@ describe IssuablesHelper do
     end
   end
 
+  describe '#issuable_reference' do
+    context 'when show_full_reference truthy' do
+      it 'display issuable full reference' do
+        assign(:show_full_reference, true)
+        issue = build_stubbed(:issue)
+
+        expect(helper.issuable_reference(issue)).to eql(issue.to_reference(full: true))
+      end
+    end
+
+    context 'when show_full_reference falsey' do
+      context 'when @group present' do
+        it 'display issuable reference to @group' do
+          project = build_stubbed(:project)
+
+          assign(:show_full_reference, nil)
+          assign(:group, project.namespace)
+
+          issue = build_stubbed(:issue)
+
+          expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project.namespace))
+        end
+      end
+
+      context 'when @project present' do
+        it 'display issuable reference to @project' do
+          project = build_stubbed(:project)
+
+          assign(:show_full_reference, nil)
+          assign(:group, nil)
+          assign(:project, project)
+
+          issue = build_stubbed(:issue)
+
+          expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project))
+        end
+      end
+    end
+  end
+
   describe '#issuable_filter_present?' do
     it 'returns true when any key is present' do
       allow(helper).to receive(:params).and_return(
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 9c7e0ee2fe02d4b866059c9997c448b49ae8669f..13fb9c1f1a7eacf7b47e3c394b66e3e49c6bf20a 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -1,7 +1,7 @@
 require "spec_helper"
 
 describe IssuesHelper do
-  let(:project) { create :project }
+  let(:project) { create(:empty_project) }
   let(:issue) { create :issue, project: project }
   let(:ext_project) { create :redmine_project }
 
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 33934cdf8b1387f98c0e7184822db3078c0f0b27..2b455571d524589efe19496e0ccec544f7ff3019 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -46,7 +46,7 @@ describe MembersHelper do
   end
 
   describe '#leave_confirmation_message' do
-    let(:project) { build_stubbed(:project) }
+    let(:project) { build_stubbed(:empty_project) }
     let(:group) { build_stubbed(:group) }
     let(:user) { build_stubbed(:user) }
 
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 1f221487393fbf94ff215643593116d1a27069ea..550b4a66a6a63806568cda02dcd9cd473871395f 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe MergeRequestsHelper do
   describe 'ci_build_details_path' do
-    let(:project) { create :project }
+    let(:project) { create(:empty_project) }
     let(:merge_request) { MergeRequest.new }
     let(:ci_service) { CiService.new }
     let(:last_commit) { Ci::Pipeline.new({}) }
@@ -30,7 +30,7 @@ describe MergeRequestsHelper do
     it { is_expected.to eq('#1, #2, and #3') }
 
     context 'for JIRA issues' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
       let(:issues) do
         [
           ExternalIssue.new('JIRA-123', project),
@@ -52,8 +52,8 @@ describe MergeRequestsHelper do
     end
 
     describe 'within different projects' do
-      let(:project) { create(:project) }
-      let(:fork_project) { create(:project, forked_from_project: project) }
+      let(:project) { create(:empty_project) }
+      let(:fork_project) { create(:empty_project, forked_from_project: project) }
       let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
       subject { format_mr_branch_names(merge_request) }
       let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" }
@@ -64,8 +64,8 @@ describe MergeRequestsHelper do
   end
 
   describe 'mr_widget_refresh_url' do
+    let(:project)       { create(:empty_project) }
     let(:merge_request) { create(:merge_request, source_project: project) }
-    let(:project)       { create(:project) }
 
     it 'returns correct url for MR' do
       expected_url = "#{project.path_with_namespace}/merge_requests/#{merge_request.iid}/merge_widget_refresh"
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index ea744dbb62951edea829ddbed5368dfd25464c7c..14a95479339e936a6942ef448a737514f6a5e00a 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -21,24 +21,22 @@ describe MilestonesHelper do
   end
 
   describe '#milestone_counts' do
-    let(:project) { FactoryGirl.create(:project) }
+    let(:project) { create(:empty_project) }
     let(:counts) { helper.milestone_counts(project.milestones) }
 
     context 'when there are milestones' do
-      let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
-      let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
-      let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) }
-
       it 'returns the correct counts' do
+        create_list(:active_milestone, 2, project: project)
+        create(:closed_milestone, project: project)
+
         expect(counts).to eq(opened: 2, closed: 1, all: 3)
       end
     end
 
     context 'when there are only milestones of one type' do
-      let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
-      let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
-
       it 'returns the correct counts' do
+        create_list(:active_milestone, 2, project: project)
+
         expect(counts).to eq(opened: 2, closed: 0, all: 2)
       end
     end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 77841e85223a54b9c96756ff730bcd49750ebb95..1f02e06e312580890e222cced2e9dce09052c696 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -110,7 +110,7 @@ describe PreferencesHelper do
       end
 
       context 'when repository is not empty' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
 
         it 'returns readme if user has repository access' do
           allow(helper).to receive(:can?).with(nil, :download_code, project).and_return(true)
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 8113742923b05969c4af4d22338f2e8317548938..8d1570aa6f355daf2c2a588361a886c51cb2edb3 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -10,7 +10,7 @@ describe ProjectsHelper do
   end
 
   describe "can_change_visibility_level?" do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:user) { create(:project_member, :reporter, user: create(:user), project: project).user }
     let(:fork_project) { Projects::ForkService.new(project, user).execute }
 
@@ -97,7 +97,7 @@ describe ProjectsHelper do
   end
 
   describe '#license_short_name' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context 'when project.repository has a license_key' do
       it 'returns the nickname of the license if present' do
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 4b2ca3514f8effb288728470ea594375fe996795..b7e547dc1f51af79defbf09b02d1d8195d7f88a6 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -41,8 +41,13 @@ describe SearchHelper do
         expect(search_autocomplete_opts("gro").size).to eq(1)
       end
 
+      it "includes nested group" do
+        create(:group, :nested, name: 'foo').add_owner(user)
+        expect(search_autocomplete_opts('foo').size).to eq(1)
+      end
+
       it "includes the user's projects" do
-        project = create(:project, namespace: create(:namespace, owner: user))
+        project = create(:empty_project, namespace: create(:namespace, owner: user))
         expect(search_autocomplete_opts(project.name).size).to eq(1)
       end
 
@@ -52,7 +57,9 @@ describe SearchHelper do
       end
 
       context "with a current project" do
-        before { @project = create(:project) }
+        before do
+          @project = create(:project, :repository)
+        end
 
         it "includes project-specific sections" do
           expect(search_autocomplete_opts("Files").size).to eq(1)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 37ac6a2699d2d8aebe1997bba0f3dfa45c7c4658..4da1569e59f0dbe25b0b7dffae5adc3ec740f73a 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -116,7 +116,7 @@ describe SubmoduleHelper do
 
     context 'submodules with relative links' do
       let(:group) { create(:group, name: "Master Project", path: "master-project") }
-      let(:project) { create(:project, group: group) }
+      let(:project) { create(:empty_project, group: group) }
       let(:commit_id) { sample_commit[:id] }
 
       before do
@@ -145,7 +145,7 @@ describe SubmoduleHelper do
 
       context 'personal project' do
         let(:user) { create(:user) }
-        let(:project) { create(:project, namespace: user.namespace) }
+        let(:project) { create(:empty_project, namespace: user.namespace) }
 
         it 'one level down with personal project' do
           result = relative_self_links('../test.git', commit_id)
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index 8d6537ba4b54b6eced05bac9a9faa4ea84846a18..9523d0f4aa6c9bed593be5161704a2681eac12e9 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe TreeHelper do
   describe 'flatten_tree' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     before do
       @repository = project.repository
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index db3ad1b99e97b4e26422ae414a7cd56e4b3d7cbd..8942b00b128219918015fb2a6ca36b0e44ff9f0c 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe VisibilityLevelHelper do
-  let(:project)          { build(:project) }
+  let(:project)          { build(:empty_project) }
   let(:group)            { build(:group) }
   let(:personal_snippet) { build(:personal_snippet) }
   let(:project_snippet)  { build(:project_snippet) }
@@ -60,8 +60,8 @@ describe VisibilityLevelHelper do
 
   describe "skip_level?" do
     describe "forks" do
-      let(:project)       { create(:project, :internal) }
-      let(:fork_project)  { create(:project, forked_from_project: project) }
+      let(:project)       { create(:empty_project, :internal) }
+      let(:fork_project)  { create(:empty_project, forked_from_project: project) }
 
       it "skips levels" do
         expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
@@ -71,7 +71,7 @@ describe VisibilityLevelHelper do
     end
 
     describe "non-forked project" do
-      let(:project) { create(:project, :internal) }
+      let(:project) { create(:empty_project, :internal) }
 
       it "skips levels" do
         expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
diff --git a/spec/initializers/metrics_spec.rb b/spec/initializers/metrics_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb59516237007bd5ab2a746cfb813c7be29786fc
--- /dev/null
+++ b/spec/initializers/metrics_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+require_relative '../../config/initializers/metrics'
+
+describe 'instrument_classes', lib: true do
+  let(:config) { double(:config) }
+
+  before do
+    allow(config).to receive(:instrument_method)
+    allow(config).to receive(:instrument_methods)
+    allow(config).to receive(:instrument_instance_methods)
+  end
+
+  it 'can autoload and instrument all files' do
+    expect { instrument_classes(config) }.not_to raise_error
+  end
+end
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
index 3cd419b37c9c7893bac30b00184a75178b94ef47..fbd9bb9f0ff8752530ae3668e1b78783f5ba813c 100644
--- a/spec/javascripts/.eslintrc
+++ b/spec/javascripts/.eslintrc
@@ -22,9 +22,10 @@
   },
   "plugins": ["jasmine"],
   "rules": {
-    "prefer-arrow-callback": 0,
     "func-names": 0,
     "jasmine/no-suite-dupes": [1, "branch"],
-    "jasmine/no-spec-dupes": [1, "branch"]
+    "jasmine/no-spec-dupes": [1, "branch"],
+    "no-console": 0,
+    "prefer-arrow-callback": 0
   }
 }
diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6
index cf19aa050315a0b1e56983d5bad83673fae5a811..a2d5782458576dae916af31b29845073c6d192df 100644
--- a/spec/javascripts/abuse_reports_spec.js.es6
+++ b/spec/javascripts/abuse_reports_spec.js.es6
@@ -21,7 +21,6 @@
       messages = $('.abuse-reports .message');
     });
 
-
     it('should truncate long messages', () => {
       const $longMessage = findMessage('LONG MESSAGE');
       expect($longMessage.data('original-message')).toEqual(jasmine.anything());
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 71446b9df618d036b506dd59240179a7ceff86e1..f1bfd5299834fdf38c79faf2b67aa8c2e96efce1 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -113,7 +113,7 @@
       });
     });
     describe('::getAwardUrl', function() {
-      return it('should return the url for request', function() {
+      return it('returns the url for request', function() {
         return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
       });
     });
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6
index 8d3e2237fda5a6ddb7c5cd4e3a5300461d4e9394..7a399b307ad889484b9461b62538ee893cde69dd 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js.es6
@@ -56,3 +56,8 @@ const boardsMockInterceptor = (request, next) => {
     status: 200
   }));
 };
+
+window.listObj = listObj;
+window.listObjDuplicate = listObjDuplicate;
+window.BoardsMockData = BoardsMockData;
+window.boardsMockInterceptor = boardsMockInterceptor;
diff --git a/spec/javascripts/commits_spec.js.es6 b/spec/javascripts/commits_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..bb9a9072f3a0cb1d3a3e0bc2abcca5dd59352df0
--- /dev/null
+++ b/spec/javascripts/commits_spec.js.es6
@@ -0,0 +1,50 @@
+/* global CommitsList */
+
+//= require jquery.endless-scroll
+//= require pager
+//= require commits
+
+(() => {
+  describe('Commits List', () => {
+    beforeEach(() => {
+      setFixtures(`
+        <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master">
+          <input id="commits-search">
+        </form>
+        <ol id="commits-list"></ol>
+        `);
+    });
+
+    it('should be defined', () => {
+      expect(CommitsList).toBeDefined();
+    });
+
+    describe('on entering input', () => {
+      let ajaxSpy;
+
+      beforeEach(() => {
+        CommitsList.init(25);
+        CommitsList.searchField.val('');
+        spyOn(history, 'replaceState').and.stub();
+        ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => {
+          req.success({
+            data: '<li>Result</li>',
+          });
+        });
+      });
+
+      it('should save the last search string', () => {
+        CommitsList.searchField.val('GitLab');
+        CommitsList.filterResults();
+        expect(ajaxSpy).toHaveBeenCalled();
+        expect(CommitsList.lastSearch).toEqual('GitLab');
+      });
+
+      it('should not make ajax call if the input does not change', () => {
+        CommitsList.filterResults();
+        expect(ajaxSpy).not.toHaveBeenCalled();
+        expect(CommitsList.lastSearch).toEqual('');
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6
index 21241116e29f86cbb9d85c9bd7d60a7d0e29db1a..95796f23894c2c428ddb87eea650500e3fd7932f 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js.es6
+++ b/spec/javascripts/environments/environment_rollback_spec.js.es6
@@ -33,7 +33,6 @@ describe('Rollback Component', () => {
     expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
   });
 
-
   it('Should render Rollback label when isLastDeployment is false', () => {
     const component = new window.gl.environmentsList.RollbackComponent({
       el: document.querySelector('.test-dom-element'),
diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6
index 20e11ca3738206e86ba38c608e704cd05d40daed..239cd69dd3aeee073a0cf461fe6206bc3f4ba493 100644
--- a/spec/javascripts/environments/environment_spec.js.es6
+++ b/spec/javascripts/environments/environment_spec.js.es6
@@ -8,12 +8,12 @@
 //= require ./mock_data
 
 describe('Environment', () => {
-  preloadFixtures('environments/environments');
+  preloadFixtures('static/environments/environments.html.raw');
 
   let component;
 
   beforeEach(() => {
-    loadFixtures('environments/environments');
+    loadFixtures('static/environments/environments.html.raw');
   });
 
   describe('successfull request', () => {
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6
index 8ecd01f9a832b6581b9bb9b51b194afaac079e8c..58f6fb96afb5a6300b814ba6568965819b5c501c 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars */
+
 const environmentsList = [
   {
     id: 31,
@@ -134,6 +134,8 @@ const environmentsList = [
   },
 ];
 
+window.environmentsList = environmentsList;
+
 const environment = {
   id: 4,
   name: 'production',
@@ -147,3 +149,5 @@ const environment = {
   created_at: '2016-12-16T11:51:04.690Z',
   updated_at: '2016-12-16T12:04:51.133Z',
 };
+
+window.environment = environment;
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..5eba4343a1d109f086ae0a616ddd759839f9f387
--- /dev/null
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
@@ -0,0 +1,40 @@
+//= require filtered_search/dropdown_utils
+//= require filtered_search/filtered_search_tokenizer
+//= require filtered_search/filtered_search_dropdown
+//= require filtered_search/dropdown_user
+
+(() => {
+  describe('Dropdown User', () => {
+    describe('getSearchInput', () => {
+      let dropdownUser;
+
+      beforeEach(() => {
+        spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+        spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+        spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
+
+        dropdownUser = new gl.DropdownUser();
+      });
+
+      it('should not return the double quote found in value', () => {
+        spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+          lastToken: {
+            value: '"johnny appleseed',
+          },
+        });
+
+        expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
+      });
+
+      it('should not return the single quote found in value', () => {
+        spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+          lastToken: {
+            value: '\'larry boy',
+          },
+        });
+
+        expect(dropdownUser.getSearchInput()).toBe('larry boy');
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
index ce61b73aa8a149121506d0f6e64ebd47179d859a..89e49b7c51190f9ce39ddb1f64e10e6cf9136bad 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
@@ -31,41 +31,130 @@
     });
 
     describe('filterWithSymbol', () => {
+      let input;
       const item = {
         title: '@root',
       };
 
+      beforeEach(() => {
+        setFixtures(`
+          <input type="text" id="test" />
+        `);
+
+        input = document.getElementById('test');
+      });
+
       it('should filter without symbol', () => {
-        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo');
+        input.value = ':roo';
+
+        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
         expect(updatedItem.droplab_hidden).toBe(false);
       });
 
       it('should filter with symbol', () => {
-        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':@roo');
+        input.value = '@roo';
+
+        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
         expect(updatedItem.droplab_hidden).toBe(false);
       });
 
       it('should filter with colon', () => {
-        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':');
+        input.value = 'roo';
+
+        const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
         expect(updatedItem.droplab_hidden).toBe(false);
       });
+
+      describe('filters multiple word title', () => {
+        const multipleWordItem = {
+          title: 'Community Contributions',
+        };
+
+        it('should filter with double quote', () => {
+          input.value = 'label:"';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with double quote and symbol', () => {
+          input.value = 'label:~"';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with double quote and multiple words', () => {
+          input.value = 'label:"community con';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with double quote, symbol and multiple words', () => {
+          input.value = 'label:~"community con';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with single quote', () => {
+          input.value = 'label:\'';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with single quote and symbol', () => {
+          input.value = 'label:~\'';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with single quote and multiple words', () => {
+          input.value = 'label:\'community con';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+
+        it('should filter with single quote, symbol and multiple words', () => {
+          input.value = 'label:~\'community con';
+
+          const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
+          expect(updatedItem.droplab_hidden).toBe(false);
+        });
+      });
     });
 
     describe('filterHint', () => {
+      let input;
+
+      beforeEach(() => {
+        setFixtures(`
+          <input type="text" id="test" />
+        `);
+
+        input = document.getElementById('test');
+      });
+
       it('should filter', () => {
-        let updatedItem = gl.DropdownUtils.filterHint({
+        input.value = 'l';
+        let updatedItem = gl.DropdownUtils.filterHint(input, {
           hint: 'label',
-        }, 'l');
+        });
         expect(updatedItem.droplab_hidden).toBe(false);
 
-        updatedItem = gl.DropdownUtils.filterHint({
+        input.value = 'o';
+        updatedItem = gl.DropdownUtils.filterHint(input, {
           hint: 'label',
         }, 'o');
         expect(updatedItem.droplab_hidden).toBe(true);
       });
 
       it('should return droplab_hidden false when item has no hint', () => {
-        const updatedItem = gl.DropdownUtils.filterHint({}, '');
+        const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
         expect(updatedItem.droplab_hidden).toBe(false);
       });
     });
@@ -103,5 +192,99 @@
         expect(result).toBe(false);
       });
     });
+
+    describe('getInputSelectionPosition', () => {
+      describe('word with trailing spaces', () => {
+        const value = 'label:none ';
+
+        it('should return selectionStart when cursor is at the trailing space', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 11,
+            value,
+          });
+
+          expect(left).toBe(11);
+          expect(right).toBe(11);
+        });
+
+        it('should return input when cursor is at the start of input', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 0,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(10);
+        });
+
+        it('should return input when cursor is at the middle of input', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 7,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(10);
+        });
+
+        it('should return input when cursor is at the end of input', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 10,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(10);
+        });
+      });
+
+      describe('multiple words', () => {
+        const value = 'label:~"Community Contribution"';
+
+        it('should return input when cursor is after the first word', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 17,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(31);
+        });
+
+        it('should return input when cursor is before the second word', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 18,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(31);
+        });
+      });
+
+      describe('incomplete multiple words', () => {
+        const value = 'label:~"Community Contribution';
+
+        it('should return entire input when cursor is at the start of input', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 0,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(30);
+        });
+
+        it('should return entire input when cursor is at the end of input', () => {
+          const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
+            selectionStart: 30,
+            value,
+          });
+
+          expect(left).toBe(0);
+          expect(right).toBe(30);
+        });
+      });
+    });
   });
 })();
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
index d0d27ceb4a6f8b012da929d357e8b5e4ab5410b3..4bd45eb457d07505dd27fe9edb94485ce74efca1 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
@@ -31,7 +31,7 @@
 
         it('should add tokenName and tokenValue', () => {
           gl.FilteredSearchDropdownManager.addWordToInput('label', 'none');
-          expect(getInputValue()).toBe('label:none');
+          expect(getInputValue()).toBe('label:none ');
         });
       });
 
@@ -45,13 +45,13 @@
         it('should replace tokenValue', () => {
           setInputValue('author:roo');
           gl.FilteredSearchDropdownManager.addWordToInput('author', '@root');
-          expect(getInputValue()).toBe('author:@root');
+          expect(getInputValue()).toBe('author:@root ');
         });
 
         it('should add tokenValues containing spaces', () => {
           setInputValue('label:~"test');
           gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\'');
-          expect(getInputValue()).toBe('label:~\'"test me"\'');
+          expect(getInputValue()).toBe('label:~\'"test me"\' ');
         });
       });
     });
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..a508dacf7f04b405c637b7fde1dc6a0faf3c1018
--- /dev/null
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
@@ -0,0 +1,69 @@
+/* global Turbolinks */
+
+//= require turbolinks
+//= require lib/utils/common_utils
+//= require filtered_search/filtered_search_token_keys
+//= require filtered_search/filtered_search_tokenizer
+//= require filtered_search/filtered_search_dropdown_manager
+//= require filtered_search/filtered_search_manager
+
+(() => {
+  describe('Filtered Search Manager', () => {
+    describe('search', () => {
+      let manager;
+      const defaultParams = '?scope=all&utf8=✓&state=opened';
+
+      function getInput() {
+        return document.querySelector('.filtered-search');
+      }
+
+      beforeEach(() => {
+        setFixtures(`
+          <input type='text' class='filtered-search' />
+        `);
+
+        spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {});
+        spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
+        spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
+        spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
+        spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
+
+        manager = new gl.FilteredSearchManager();
+      });
+
+      afterEach(() => {
+        getInput().outerHTML = '';
+      });
+
+      it('should search with a single word', () => {
+        getInput().value = 'searchTerm';
+
+        spyOn(Turbolinks, 'visit').and.callFake((url) => {
+          expect(url).toEqual(`${defaultParams}&search=searchTerm`);
+        });
+
+        manager.search();
+      });
+
+      it('should search with multiple words', () => {
+        getInput().value = 'awesome search terms';
+
+        spyOn(Turbolinks, 'visit').and.callFake((url) => {
+          expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
+        });
+
+        manager.search();
+      });
+
+      it('should search with special characters', () => {
+        getInput().value = '~!@#$%^&*()_+{}:<>,.?/';
+
+        spyOn(Turbolinks, 'visit').and.callFake((url) => {
+          expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
+        });
+
+        manager.search();
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
index 6df7c0e44efe9e29c5c8d4249ccbbacc2ac1e977..9d9097419ea95bfc1ba931f0edada9b8903033a8 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
@@ -72,6 +72,12 @@
         const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
         expect(result).toEqual(tokenKeys[0]);
       });
+
+      it('should return alternative tokenKey when found by key param', () => {
+        const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives();
+        const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
+        expect(result).toEqual(tokenKeys[0]);
+      });
     });
 
     describe('searchByConditionUrl', () => {
diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml
index 1ea1725c5616e95c1e90c677b09c7ef92f92dd1e..59edc0396d2f75563ae566cc4efe79187d3d15dd 100644
--- a/spec/javascripts/fixtures/environments/table.html.haml
+++ b/spec/javascripts/fixtures/environments/table.html.haml
@@ -3,7 +3,7 @@
     %tr
       %th Environment
       %th Last deployment
-      %th Build
+      %th Job
       %th Commit
       %th
       %th
diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6
index 6b48d82cb236e35fa38b1a48a6a843f45018b4e8..99cebb32a8ba236533e2337db0f2d0f52f31295a 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js.es6
+++ b/spec/javascripts/gfm_auto_complete_spec.js.es6
@@ -62,4 +62,30 @@ describe('GfmAutoComplete', function () {
       });
     });
   });
+
+  describe('isLoading', function () {
+    it('should be true with loading data object item', function () {
+      expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
+    });
+
+    it('should be true with loading data array', function () {
+      expect(GfmAutoComplete.isLoading(['loading'])).toBe(true);
+    });
+
+    it('should be true with loading data object array', function () {
+      expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true);
+    });
+
+    it('should be false with actual array data', function () {
+      expect(GfmAutoComplete.isLoading([
+        { title: 'Foo' },
+        { title: 'Bar' },
+        { title: 'Qux' },
+      ])).toBe(false);
+    });
+
+    it('should be false with actual data item', function () {
+      expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
+    });
+  });
 });
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index 06fa64b1b4e04253292db1f6a66e853fdb1983f5..4e7eed2767cd242e6714bc8125c390fdb9e728f6 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -44,6 +44,7 @@
 
   describe('Dropdown', function describeDropdown() {
     preloadFixtures('static/gl_dropdown.html.raw');
+    loadJSONFixtures('projects.json');
 
     function initDropDown(hasRemote, isFilterable) {
       this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({
diff --git a/spec/javascripts/gl_form_spec.js.es6 b/spec/javascripts/gl_form_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..b5f99483bfb2ad073790663c59ea7c87550f2f57
--- /dev/null
+++ b/spec/javascripts/gl_form_spec.js.es6
@@ -0,0 +1,122 @@
+/* global autosize */
+/*= require gl_form */
+/*= require autosize */
+/*= require lib/utils/text_utility */
+/*= require lib/utils/common_utils */
+
+describe('GLForm', () => {
+  const global = window.gl || (window.gl = {});
+  const GLForm = global.GLForm;
+
+  it('should be defined in the global scope', () => {
+    expect(GLForm).toBeDefined();
+  });
+
+  describe('when instantiated', function () {
+    beforeEach((done) => {
+      this.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
+      this.textarea = this.form.find('textarea');
+      spyOn($.prototype, 'off').and.returnValue(this.textarea);
+      spyOn($.prototype, 'on').and.returnValue(this.textarea);
+      spyOn($.prototype, 'css');
+      spyOn(window, 'autosize');
+
+      this.glForm = new GLForm(this.form);
+      setTimeout(() => {
+        $.prototype.off.calls.reset();
+        $.prototype.on.calls.reset();
+        $.prototype.css.calls.reset();
+        autosize.calls.reset();
+        done();
+      });
+    });
+
+    describe('.setupAutosize', () => {
+      beforeEach((done) => {
+        this.glForm.setupAutosize();
+        setTimeout(() => {
+          done();
+        });
+      });
+
+      it('should register an autosize event handler on the textarea', () => {
+        expect($.prototype.off).toHaveBeenCalledWith('autosize:resized');
+        expect($.prototype.on).toHaveBeenCalledWith('autosize:resized', jasmine.any(Function));
+      });
+
+      it('should register a mouseup event handler on the textarea', () => {
+        expect($.prototype.off).toHaveBeenCalledWith('mouseup.autosize');
+        expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', jasmine.any(Function));
+      });
+
+      it('should autosize the textarea', () => {
+        expect(autosize).toHaveBeenCalledWith(jasmine.any(Object));
+      });
+
+      it('should set the resize css property to vertical', () => {
+        expect($.prototype.css).toHaveBeenCalledWith('resize', 'vertical');
+      });
+    });
+
+    describe('.setHeightData', () => {
+      beforeEach(() => {
+        spyOn($.prototype, 'data');
+        spyOn($.prototype, 'outerHeight').and.returnValue(200);
+        this.glForm.setHeightData();
+      });
+
+      it('should set the height data attribute', () => {
+        expect($.prototype.data).toHaveBeenCalledWith('height', 200);
+      });
+
+      it('should call outerHeight', () => {
+        expect($.prototype.outerHeight).toHaveBeenCalled();
+      });
+    });
+
+    describe('.destroyAutosize', () => {
+      describe('when called', () => {
+        beforeEach(() => {
+          spyOn($.prototype, 'data');
+          spyOn($.prototype, 'outerHeight').and.returnValue(200);
+          spyOn(window, 'outerHeight').and.returnValue(400);
+          spyOn(autosize, 'destroy');
+
+          this.glForm.destroyAutosize();
+        });
+
+        it('should call outerHeight', () => {
+          expect($.prototype.outerHeight).toHaveBeenCalled();
+        });
+
+        it('should get data-height attribute', () => {
+          expect($.prototype.data).toHaveBeenCalledWith('height');
+        });
+
+        it('should call autosize destroy', () => {
+          expect(autosize.destroy).toHaveBeenCalledWith(this.textarea);
+        });
+
+        it('should set the data-height attribute', () => {
+          expect($.prototype.data).toHaveBeenCalledWith('height', 200);
+        });
+
+        it('should set the outerHeight', () => {
+          expect($.prototype.outerHeight).toHaveBeenCalledWith(200);
+        });
+
+        it('should set the css', () => {
+          expect($.prototype.css).toHaveBeenCalledWith('max-height', window.outerHeight);
+        });
+      });
+
+      it('should return undefined if the data-height equals the outerHeight', () => {
+        spyOn($.prototype, 'outerHeight').and.returnValue(200);
+        spyOn($.prototype, 'data').and.returnValue(200);
+        spyOn(autosize, 'destroy');
+        expect(this.glForm.destroyAutosize()).toBeUndefined();
+        expect(autosize.destroy).not.toHaveBeenCalled();
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js.es6
index 92a20687ec5974e91011dac46ae88e2ee93e8de8..d3c37d39431950cfd4d8531ba791a5afadaba9eb 100644
--- a/spec/javascripts/helpers/class_spec_helper.js.es6
+++ b/spec/javascripts/helpers/class_spec_helper.js.es6
@@ -1,5 +1,3 @@
-/* eslint-disable no-unused-vars */
-
 class ClassSpecHelper {
   static itShouldBeAStaticMethod(base, method) {
     return it('should be a static method', () => {
@@ -7,3 +5,5 @@ class ClassSpecHelper {
     });
   }
 }
+
+window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/javascripts/issuable_time_tracker_spec.js.es6 b/spec/javascripts/issuable_time_tracker_spec.js.es6
index a1e979e8d0993213e15aca80526273f7c588259b..c5671af235ee4e4e30ecc15c08f0b580b826b905 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js.es6
+++ b/spec/javascripts/issuable_time_tracker_spec.js.es6
@@ -4,7 +4,7 @@
 //= require issuable/time_tracking/components/time_tracker
 
 function initTimeTrackingComponent(opts) {
-  fixture.set(`
+  setFixtures(`
     <div>
       <div id="mock-container"></div>
     </div>
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index 031f9ca03c96361492aed9e724925e759af05442..32c96e2a0887af5fe4e75b2f0ac3837d131db40a 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -10,9 +10,9 @@
         // IE11 will return a relative pathname while other browsers will return a full pathname.
         // parseUrl uses an anchor element for parsing an url. With relative urls, the anchor
         // element will create an absolute url relative to the current execution context.
-        // The JavaScript test suite is executed at '/teaspoon' which will lead to an absolute
-        // url starting with '/teaspoon'.
-        expect(gl.utils.parseUrl('" test="asf"').pathname).toEqual('/teaspoon/%22%20test=%22asf%22');
+        // The JavaScript test suite is executed at '/' which will lead to an absolute url
+        // starting with '/'.
+        expect(gl.utils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22');
       });
     });
 
@@ -42,9 +42,13 @@
     });
 
     describe('gl.utils.getParameterByName', () => {
+      beforeEach(() => {
+        window.history.pushState({}, null, '?scope=all&p=2');
+      });
+
       it('should return valid parameter', () => {
-        const value = gl.utils.getParameterByName('reporter');
-        expect(value).toBe('Console');
+        const value = gl.utils.getParameterByName('scope');
+        expect(value).toBe('all');
       });
 
       it('should return invalid parameter', () => {
@@ -52,5 +56,22 @@
         expect(value).toBe(null);
       });
     });
+
+    describe('gl.utils.normalizedHeaders', () => {
+      it('should upperCase all the header keys to keep them consistent', () => {
+        const apiHeaders = {
+          'X-Something-Workhorse': { workhorse: 'ok' },
+          'x-something-nginx': { nginx: 'ok' },
+        };
+
+        const normalized = gl.utils.normalizeHeaders(apiHeaders);
+
+        const WORKHORSE = 'X-SOMETHING-WORKHORSE';
+        const NGINX = 'X-SOMETHING-NGINX';
+
+        expect(normalized[WORKHORSE].workhorse).toBe('ok');
+        expect(normalized[NGINX].nginx).toBe('ok');
+      });
+    });
   });
 })();
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index bf45100af03349a11450a4a544df127851d4e5ef..6f1d6406897eea506ab32ef44ed05bccbef07810 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -1,6 +1,7 @@
 /* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */
 
 /*= require merge_request_widget */
+/*= require smart_interval */
 /*= require lib/utils/datetime_utility */
 
 (function() {
@@ -21,7 +22,11 @@
           normal: "Build {{status}}"
         },
         gitlab_icon: "gitlab_logo.png",
-        builds_path: "http://sampledomain.local/sampleBuildsPath"
+        ci_pipeline: 80,
+        ci_sha: "12a34bc5",
+        builds_path: "http://sampledomain.local/sampleBuildsPath",
+        commits_path: "http://sampledomain.local/commits",
+        pipeline_path: "http://sampledomain.local/pipelines"
       };
       this["class"] = new window.gl.MergeRequestWidget(this.opts);
     });
@@ -118,10 +123,11 @@
       });
     });
 
-    return describe('getCIStatus', function() {
+    describe('getCIStatus', function() {
       beforeEach(function() {
         this.ciStatusData = {
           "title": "Sample MR title",
+          "pipeline": 80,
           "sha": "12a34bc5",
           "status": "success",
           "coverage": 98
@@ -165,6 +171,22 @@
         this["class"].getCIStatus(true);
         return expect(spy).not.toHaveBeenCalled();
       });
+      it('should update the pipeline URL when the pipeline changes', function() {
+        var spy;
+        spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
+        this["class"].getCIStatus(false);
+        this.ciStatusData.pipeline += 1;
+        this["class"].getCIStatus(false);
+        return expect(spy).toHaveBeenCalled();
+      });
+      it('should update the commit URL when the sha changes', function() {
+        var spy;
+        spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
+        this["class"].getCIStatus(false);
+        this.ciStatusData.sha = "9b50b99a";
+        this["class"].getCIStatus(false);
+        return expect(spy).toHaveBeenCalled();
+      });
     });
   });
 }).call(this);
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 0202c9ba85ed85666c4a4543b02196d399488ba0..e562385a6c64eb72899b0013ff79aa2cca5e6c17 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -17,6 +17,8 @@
 
   describe('Project Title', function() {
     preloadFixtures('static/project_title.html.raw');
+    loadJSONFixtures('projects.json');
+
     beforeEach(function() {
       loadFixtures('static/project_title.html.raw');
       return this.project = new Project();
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 942778229b5d2e2315dafb7843fea5d4b1a05b6e..3a01a53455746f500d8f85c10769e91a122a3973 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -37,6 +37,8 @@
   describe('RightSidebar', function() {
     var fixtureName = 'issues/open-issue.html.raw';
     preloadFixtures(fixtureName);
+    loadJSONFixtures('todos.json');
+
     beforeEach(function() {
       loadFixtures(fixtureName);
       this.sidebar = new Sidebar;
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 755dba197448844574aac6c5c01965292a6dc280..e0a5a7927bb1e343a25813c83aad26d2cfa2adf8 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -1,6 +1,7 @@
 /* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes */
 /* global ShortcutsIssuable */
 
+/*= require copy_as_gfm */
 /*= require shortcuts_issuable */
 
 (function() {
@@ -10,67 +11,70 @@
     beforeEach(function() {
       loadFixtures(fixtureName);
       document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
-      return this.shortcut = new ShortcutsIssuable();
+      this.shortcut = new ShortcutsIssuable();
     });
-    return describe('#replyWithSelectedText', function() {
+    describe('#replyWithSelectedText', function() {
       var stubSelection;
-      // Stub window.getSelection to return the provided String.
-      stubSelection = function(text) {
-        return window.getSelection = function() {
-          return text;
+      // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
+      stubSelection = function(html) {
+        window.gl.utils.getSelectedFragment = function() {
+          var node = document.createElement('div');
+          node.innerHTML = html;
+          return node;
         };
       };
       beforeEach(function() {
-        return this.selector = 'form.js-main-target-form textarea#note_note';
+        this.selector = 'form.js-main-target-form textarea#note_note';
       });
       describe('with empty selection', function() {
-        return it('does nothing', function() {
-          stubSelection('');
+        it('does not return an error', function() {
           this.shortcut.replyWithSelectedText();
-          return expect($(this.selector).val()).toBe('');
+          expect($(this.selector).val()).toBe('');
+        });
+        it('triggers `input`', function() {
+          var focused = false;
+          $(this.selector).on('focus', function() {
+            focused = true;
+          });
+          this.shortcut.replyWithSelectedText();
+          expect(focused).toBe(true);
         });
       });
       describe('with any selection', function() {
         beforeEach(function() {
-          return stubSelection('Selected text.');
+          stubSelection('<p>Selected text.</p>');
         });
         it('leaves existing input intact', function() {
           $(this.selector).val('This text was already here.');
           expect($(this.selector).val()).toBe('This text was already here.');
           this.shortcut.replyWithSelectedText();
-          return expect($(this.selector).val()).toBe("This text was already here.\n> Selected text.\n\n");
+          expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
         });
         it('triggers `input`', function() {
-          var triggered;
-          triggered = false;
+          var triggered = false;
           $(this.selector).on('input', function() {
-            return triggered = true;
+            triggered = true;
           });
           this.shortcut.replyWithSelectedText();
-          return expect(triggered).toBe(true);
+          expect(triggered).toBe(true);
         });
-        return it('triggers `focus`', function() {
-          var focused;
-          focused = false;
-          $(this.selector).on('focus', function() {
-            return focused = true;
-          });
+        it('triggers `focus`', function() {
           this.shortcut.replyWithSelectedText();
-          return expect(focused).toBe(true);
+          expect(document.activeElement).toBe(document.querySelector(this.selector));
         });
       });
       describe('with a one-line selection', function() {
-        return it('quotes the selection', function() {
-          stubSelection('This text has been selected.');
+        it('quotes the selection', function() {
+          stubSelection('<p>This text has been selected.</p>');
           this.shortcut.replyWithSelectedText();
-          return expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
+          expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
         });
       });
-      return describe('with a multi-line selection', function() {
-        return it('quotes the selected lines as a group', function() {
-          stubSelection("Selected line one.\n\nSelected line two.\nSelected line three.\n");
+      describe('with a multi-line selection', function() {
+        it('quotes the selected lines as a group', function() {
+          stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>");
           this.shortcut.replyWithSelectedText();
-          return expect($(this.selector).val()).toBe("> Selected line one.\n> Selected line two.\n> Selected line three.\n\n");
+          expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
         });
       });
     });
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index 80163fd72d34db36e82e2587365fa4f975f87f46..0e2fb07ba7f35bf79d088f579da0cf8ffeb426cb 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -25,19 +25,20 @@
         document.querySelector('#js-login-2fa-device'),
         document.querySelector('.js-2fa-form')
       );
+
+      // bypass automatic form submission within renderAuthenticated
+      spyOn(this.component, 'renderAuthenticated').and.returnValue(true);
+
       return this.component.start();
     });
     it('allows authenticating via a U2F device', function() {
-      var authenticatedMessage, deviceResponse, inProgressMessage;
+      var inProgressMessage;
       inProgressMessage = this.container.find("p");
       expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
       this.u2fDevice.respondToAuthenticateRequest({
         deviceData: "this is data from the device"
       });
-      authenticatedMessage = this.container.find("p");
-      deviceResponse = this.container.find('#js-device-response');
-      expect(authenticatedMessage.text()).toContain('We heard back from your U2F device. You have been authenticated.');
-      return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+      expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
     });
     return describe("errors", function() {
       it("displays an error message", function() {
@@ -51,7 +52,7 @@
         return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
       });
       return it("allows retrying authentication after an error", function() {
-        var authenticatedMessage, retryButton, setupButton;
+        var retryButton, setupButton;
         setupButton = this.container.find("#js-login-u2f-device");
         setupButton.trigger('click');
         this.u2fDevice.respondToAuthenticateRequest({
@@ -64,8 +65,7 @@
         this.u2fDevice.respondToAuthenticateRequest({
           deviceData: "this is data from the device"
         });
-        authenticatedMessage = this.container.find("p");
-        return expect(authenticatedMessage.text()).toContain("We heard back from your U2F device. You have been authenticated.");
+        expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
       });
     });
   });
diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6
index 1a7f2bb5fb8c367c7d26953b842ca66ad74d29c2..efb11211ce2b26c71015fc40688fa60b52489393 100644
--- a/spec/javascripts/vue_pagination/pagination_spec.js.es6
+++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6
@@ -1,7 +1,6 @@
 //= require vue
 //= require lib/utils/common_utils
 //= require vue_pagination/index
-/* global fixture, gl */
 
 describe('Pagination component', () => {
   let component;
@@ -17,7 +16,7 @@ describe('Pagination component', () => {
   };
 
   it('should render and start at page 1', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
@@ -40,7 +39,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the previous page', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
@@ -61,7 +60,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the next page', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
@@ -82,7 +81,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the last page', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
@@ -103,7 +102,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the first page', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
@@ -124,7 +123,7 @@ describe('Pagination component', () => {
   });
 
   it('should do nothing', () => {
-    fixture.set('<div class="test-pagination-container"></div>');
+    setFixtures('<div class="test-pagination-container"></div>');
 
     component = new window.gl.VueGlPagination({
       el: document.querySelector('.test-pagination-container'),
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index 81b9a513ce37f12ae72a6419bd6eb2bef81502b0..deaabceef1cb670d74a68fed1024bed4d3ed7196 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -24,7 +24,7 @@ describe Banzai::CrossProjectReference, lib: true do
       it 'returns the referenced project' do
         project2 = double('referenced project')
 
-        expect(Project).to receive(:find_with_namespace).
+        expect(Project).to receive(:find_by_full_path).
           with('cross/reference').and_return(project2)
 
         expect(project_from_ref('cross/reference')).to eq project2
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 9703e2315b8d1fac3dfe6ace3c976b4a067f3659..deadc36524cc72ba76069231d7c8f00bd195c290 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
   include FilterSpecHelper
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:commit1) { project.commit("HEAD~2") }
   let(:commit2) { project.commit }
 
@@ -99,7 +99,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
   end
 
   context 'cross-project / cross-namespace complete reference' do
-    let(:project2)  { create(:project, :public) }
+    let(:project2)  { create(:project, :public, :repository) }
     let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
 
     it 'links to a valid reference' do
@@ -133,8 +133,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
 
   context 'cross-project / same-namespace complete reference' do
     let(:namespace)         { create(:namespace) }
-    let(:project)           { create(:project, :public, namespace: namespace) }
-    let(:project2)          { create(:project, :public, path: "same-namespace", namespace: namespace) }
+    let(:project)           { create(:project, :public, :repository, namespace: namespace) }
+    let(:project2)          { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) }
     let(:reference)         { "#{project2.path}@#{commit1.id}...#{commit2.id}" }
 
     it 'links to a valid reference' do
@@ -168,8 +168,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
 
   context 'cross-project shorthand reference' do
     let(:namespace)         { create(:namespace) }
-    let(:project)           { create(:project, :public, namespace: namespace) }
-    let(:project2)          { create(:project, :public, path: "same-namespace", namespace: namespace) }
+    let(:project)           { create(:project, :public, :repository, namespace: namespace) }
+    let(:project2)          { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) }
     let(:reference)         { "#{project2.path}@#{commit1.id}...#{commit2.id}" }
 
     it 'links to a valid reference' do
@@ -203,7 +203,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
 
   context 'cross-project URL reference' do
     let(:namespace) { create(:namespace) }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
     let(:range)  { CommitRange.new("#{commit1.id}...master", project) }
     let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
 
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 2e6dcc3a434bc23314a2e41a4e2cae932699ccec..a19aac61229cd2d08e4027e65a96a64a8c425747 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::Filter::CommitReferenceFilter, lib: true do
   include FilterSpecHelper
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:commit)  { project.commit }
 
   it 'requires project context' do
@@ -96,7 +96,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
 
   context 'cross-project / cross-namespace complete reference' do
     let(:namespace) { create(:namespace) }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
     let(:commit)    { project2.commit }
     let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
 
@@ -122,7 +122,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
   context 'cross-project / same-namespace complete reference' do
     let(:namespace) { create(:namespace) }
     let(:project)   { create(:empty_project, namespace: namespace) }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
     let(:commit)    { project2.commit }
     let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
 
@@ -148,7 +148,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
   context 'cross-project shorthand reference' do
     let(:namespace) { create(:namespace) }
     let(:project)   { create(:empty_project, namespace: namespace) }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
     let(:commit)    { project2.commit }
     let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
 
@@ -173,7 +173,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
 
   context 'cross-project URL reference' do
     let(:namespace) { create(:namespace) }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:project, :public, :repository, namespace: namespace) }
     let(:commit)    { project2.commit }
     let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
 
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index fe2ce092e6b421f99a5a8fe83b0565fa7609966f..082c0d4dd0df8f11f69d50f219c5a0e8239e1326 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::Filter::GollumTagsFilter, lib: true do
   include FilterSpecHelper
 
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:user) { double }
   let(:project_wiki) { ProjectWiki.new(project, user) }
 
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 275010c1a2c9dac41bcee44726f2213da8f913c9..3d3d36061f4d3fee00f4818e5bff99a96fcdbd52 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -188,7 +188,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
 
   context 'cross-project URL reference' do
     let(:namespace) { create(:namespace, name: 'cross-reference') }
-    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
     let(:merge)     { create(:merge_request, source_project: project2, target_project: project2) }
     let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
 
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 73b5edb99b37e28e09332f3f13e09f9add59fa8d..a317c751d3275ebc5034e61bc38c9c2b2161e3f8 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
   include FilterSpecHelper
 
-  let(:project)   { create(:project, :public) }
+  let(:project)   { create(:empty_project, :public) }
   let(:milestone) { create(:milestone, project: project) }
   let(:reference) { milestone.to_reference }
 
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f85a5dcbd8bd74d2509fd8f958d5e3b7691a7bce
--- /dev/null
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Banzai::Filter::PlantumlFilter, lib: true do
+  include FilterSpecHelper
+
+  it 'should replace plantuml pre tag with img tag' do
+    stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
+    input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+    output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
+    doc = filter(input)
+
+    expect(doc.to_s).to eq output
+  end
+
+  it 'should not replace plantuml pre tag with img tag if disabled' do
+    stub_application_setting(plantuml_enabled: false)
+    input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+    output = '<pre class="plantuml"><code>Bob -&gt; Sara : Hello</code><pre></pre></pre>'
+    doc = filter(input)
+
+    expect(doc.to_s).to eq output
+  end
+
+  it 'should not replace plantuml pre tag with img tag if url is invalid' do
+    stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
+    input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+    output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
+    doc = filter(input)
+
+    expect(doc.to_s).to eq output
+  end
+end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index df2dd173b5786d5d8b93e2433c8d8e7bd098ba4c..1957ba739e267edc14b454f2f7ced2adf7b8b661 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -25,7 +25,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
     %(<a href="#{path}">#{path}</a>)
   end
 
-  let(:project)        { create(:project) }
+  let(:project)        { create(:project, :repository) }
   let(:project_path)   { project.path_with_namespace }
   let(:ref)            { 'markdown' }
   let(:commit)         { project.commit(ref) }
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index d265d29ee86990066e43a29b2c23ce551e854480..69e3c52b35af00df2089e326930896447a325ea2 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
   context "when no language is specified" do
     it "highlights as plaintext" do
       result = filter('<pre><code>def fun end</code></pre>')
-      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>def fun end</code></pre>')
+      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>def fun end</code></pre>')
     end
   end
 
   context "when a valid language is specified" do
     it "highlights as that language" do
       result = filter('<pre><code class="ruby">def fun end</code></pre>')
-      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
+      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
     end
   end
 
   context "when an invalid language is specified" do
     it "highlights as plaintext" do
       result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
-      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>This is a test</code></pre>')
+      expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>This is a test</code></pre>')
     end
   end
 
@@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
 
     it "highlights as plaintext" do
       result = filter('<pre><code class="ruby">This is a test</code></pre>')
-      expect(result.to_html).to eq('<pre class="code highlight" v-pre="true"><code>This is a test</code></pre>')
+      expect(result.to_html).to eq('<pre class="code highlight" lang="" v-pre="true"><code>This is a test</code></pre>')
     end
   end
 end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 8b76c1d73c993b5824821e14435f71483a2cbf12..639cac6406aa9ede6440ddbc9077433d5910b8a7 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -29,7 +29,7 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
     %(<div><a href="#{path}">#{path}</a></div>)
   end
 
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
 
   shared_examples :preserve_unchanged do
     it 'does not modify any relative URL in anchor' do
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 5bfeb82e738c41e87787eccce894d06c8e2844b8..3e1ac9fb2b277f65b2c094e9dfc3782204721052 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
     end
   end
 
+  context 'when a project is not specified' do
+    let(:project) { nil }
+
+    it 'does not link a User' do
+      doc = reference_filter("Hey #{reference}")
+
+      expect(doc).not_to include('a')
+    end
+
+    context 'when skip_project_check set to true' do
+      it 'links to a User' do
+        doc = reference_filter("Hey #{reference}", skip_project_check: true)
+
+        expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+      end
+
+      it 'does not link users using @all reference' do
+        doc = reference_filter("Hey #{User.reference_prefix}all", skip_project_check: true)
+
+        expect(doc).not_to include('a')
+      end
+    end
+  end
+
   describe '#namespaces' do
     it 'returns a Hash containing all Namespaces' do
       document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 6ab1be9ccb70e879d0c23369cf1c2bd7812411e1..00494f545a325f2db612cfed843840e24e175091 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -13,7 +13,7 @@ describe Banzai::Filter::VideoLinkFilter, lib: true do
     %(<img src="#{path}" />)
   end
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   context 'when the element src has a video extension' do
     UploaderHelper::VIDEO_EXT.each do |ext|
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index fafc2cec5461ec13e9cba54bd037664e62579b7c..31ca9d27b0bded68dfa987cd1b351d793801f5ad 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -147,7 +147,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
   describe '#nodes_user_can_reference' do
     context 'when the link has a data-author attribute' do
       it 'returns the nodes when the user is a member of the project' do
-        other_project = create(:project)
+        other_project = create(:empty_project)
         other_project.team << [user, :developer]
 
         link['data-project'] = other_project.id.to_s
@@ -164,7 +164,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
       end
 
       it 'returns an empty Array when the user could not be found' do
-        other_project = create(:project)
+        other_project = create(:empty_project)
 
         link['data-project'] = other_project.id.to_s
         link['data-author'] = ''
@@ -173,7 +173,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
       end
 
       it 'returns an empty Array when the user is not a team member' do
-        other_project = create(:project)
+        other_project = create(:empty_project)
 
         link['data-project'] = other_project.id.to_s
         link['data-author'] = user.id.to_s
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index f824e2e1efece63ab8a1fb1ba7d25b4d983288e8..49349035b3b55d197c5ba25f485fbd4830f65193 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -4,6 +4,33 @@ module Ci
   describe GitlabCiYamlProcessor, lib: true do
     let(:path) { 'path' }
 
+    describe '#build_attributes' do
+      context 'Coverage entry' do
+        subject { described_class.new(config, path).build_attributes(:rspec) }
+
+        let(:config_base) { { rspec: { script: "rspec" } } }
+        let(:config) { YAML.dump(config_base) }
+
+        context 'when config has coverage set at the global scope' do
+          before do
+            config_base.update(coverage: '/\(\d+\.\d+\) covered/')
+          end
+
+          context "and 'rspec' job doesn't have coverage set" do
+            it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') }
+          end
+
+          context "but 'rspec' job also has coverage set" do
+            before do
+              config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/'
+            end
+
+            it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') }
+          end
+        end
+      end
+    end
+
     describe "#builds_for_ref" do
       let(:type) { 'test' }
 
@@ -21,6 +48,7 @@ module Ci
           stage_idx: 1,
           name: "rspec",
           commands: "pwd\nrspec",
+          coverage_regex: nil,
           tag_list: [],
           options: {},
           allow_failure: false,
@@ -435,6 +463,7 @@ module Ci
           stage_idx: 1,
           name: "rspec",
           commands: "pwd\nrspec",
+          coverage_regex: nil,
           tag_list: [],
           options: {
             image: "ruby:2.1",
@@ -463,6 +492,7 @@ module Ci
           stage_idx: 1,
           name: "rspec",
           commands: "pwd\nrspec",
+          coverage_regex: nil,
           tag_list: [],
           options: {
             image: "ruby:2.5",
@@ -702,6 +732,7 @@ module Ci
           stage_idx: 1,
           name: "rspec",
           commands: "pwd\nrspec",
+          coverage_regex: nil,
           tag_list: [],
           options: {
             image: "ruby:2.1",
@@ -913,6 +944,7 @@ module Ci
             stage_idx: 1,
             name: "normal_job",
             commands: "test",
+            coverage_regex: nil,
             tag_list: [],
             options: {},
             when: "on_success",
@@ -958,6 +990,7 @@ module Ci
             stage_idx: 0,
             name: "job1",
             commands: "execute-script-for-job",
+            coverage_regex: nil,
             tag_list: [],
             options: {},
             when: "on_success",
@@ -970,6 +1003,7 @@ module Ci
             stage_idx: 0,
             name: "job2",
             commands: "execute-script-for-job",
+            coverage_regex: nil,
             tag_list: [],
             options: {},
             when: "on_success",
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 94266f6653b016c5abb32ed15ff8d7a12388328a..a5251e9a8c25cded8c7dc5b052cc79627b7c56d3 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ProjectUrlConstrainer, lib: true do
-  let!(:project) { create(:project) }
+  let!(:project) { create(:empty_project) }
   let!(:namespace) { project.namespace }
 
   describe '#matches?' do
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index ec2f66b11367a461602c548e42bf11b99ffe6157..e3066311b7dcaad6d40d87cef9eaf49a31fd8885 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe EventFilter, lib: true do
   describe '#apply_filter' do
     let(:source_user) { create(:user) }
-    let!(:public_project) { create(:project, :public) }
+    let!(:public_project) { create(:empty_project, :public) }
 
     let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) }
     let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) }
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 0e85e302f292700270c59bb4f8f39defffa877e7..29c07655ae8e12a515e0454163802a221613bf99 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -24,7 +24,7 @@ describe ExtractsPath, lib: true do
     let(:params) { { path: sample_commit[:line_code_path], ref: ref } }
 
     before do
-      @project = create(:project)
+      @project = create(:project, :repository)
     end
 
     it "log tree path has no escape sequences" do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f251c0dd25a1d8cac3080d26e096a52b37627b6e..b234de4c77267fa3ede434a071366b65332c176e 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -58,58 +58,102 @@ describe Gitlab::Auth, lib: true do
       expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
     end
 
-    it 'recognizes user lfs tokens' do
-      user = create(:user)
-      token = Gitlab::LfsToken.new(user).token
+    context 'while using LFS authenticate' do
+      it 'recognizes user lfs tokens' do
+        user = create(:user)
+        token = Gitlab::LfsToken.new(user).token
 
-      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
-      expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
-    end
+        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
+        expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+      end
 
-    it 'recognizes deploy key lfs tokens' do
-      key = create(:deploy_key)
-      token = Gitlab::LfsToken.new(key).token
+      it 'recognizes deploy key lfs tokens' do
+        key = create(:deploy_key)
+        token = Gitlab::LfsToken.new(key).token
 
-      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
-      expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
-    end
+        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
+        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
+      end
 
-    context "while using OAuth tokens as passwords" do
-      it 'succeeds for OAuth tokens with the `api` scope' do
+      it 'does not try password auth before oauth' do
         user = create(:user)
-        application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
-        token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
+        token = Gitlab::LfsToken.new(user).token
+
+        expect(gl_auth).not_to receive(:find_with_user_password)
 
+        gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
+      end
+    end
+
+    context 'while using OAuth tokens as passwords' do
+      let(:user) { create(:user) }
+      let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
+      let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
+
+      it 'succeeds for OAuth tokens with the `api` scope' do
         expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
-        expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
+        expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
       end
 
       it 'fails for OAuth tokens with other scopes' do
-        user = create(:user)
-        application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
-        token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "read_user")
+        token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
 
         expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
         expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
       end
+
+      it 'does not try password auth before oauth' do
+        expect(gl_auth).not_to receive(:find_with_user_password)
+
+        gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
+      end
     end
 
-    context "while using personal access tokens as passwords" do
-      it 'succeeds for personal access tokens with the `api` scope' do
-        user = create(:user)
-        personal_access_token = create(:personal_access_token, user: user, scopes: ['api'])
+    context 'while using personal access tokens as passwords' do
+      let(:user) { create(:user) }
+      let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
 
+      it 'succeeds for personal access tokens with the `api` scope' do
         expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
-        expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
+        expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
       end
 
       it 'fails for personal access tokens with other scopes' do
-        user = create(:user)
         personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
 
         expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
         expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
       end
+
+      it 'does not try password auth before personal access tokens' do
+        expect(gl_auth).not_to receive(:find_with_user_password)
+
+        gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
+      end
+    end
+
+    context 'while using regular user and password' do
+      it 'falls through lfs authentication' do
+        user = create(
+          :user,
+          username: 'normal_user',
+          password: 'my-secret',
+        )
+
+        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+      end
+
+      it 'falls through oauth authentication when the username is oauth2' do
+        user = create(
+          :user,
+          username: 'oauth2',
+          password: 'my-secret',
+        )
+
+        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+      end
     end
 
     it 'returns double nil for invalid credentials' do
diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/build/metadata_spec.rb
index d678e52272100948308aa3e920c0540a82dcbe49..9df96ea04eb6be95d6d56c77657cb9d7a7ea8e1a 100644
--- a/spec/lib/gitlab/badge/build/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/build/metadata_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 require 'lib/gitlab/badge/shared/metadata'
 
 describe Gitlab::Badge::Build::Metadata do
-  let(:badge) { double(project: create(:project), ref: 'feature') }
+  let(:badge) { double(project: create(:empty_project), ref: 'feature') }
   let(:metadata) { described_class.new(badge) }
 
   it_behaves_like 'badge metadata'
diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb
index 70f03021d361f1cc965cf6b4eda6ad93fcde2b56..3c5414701a7e7dccebe7e2495ff651f4d9de84f5 100644
--- a/spec/lib/gitlab/badge/build/status_spec.rb
+++ b/spec/lib/gitlab/badge/build/status_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Badge::Build::Status do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:sha) { project.commit.sha }
   let(:branch) { 'master' }
   let(:badge) { described_class.new(project, branch) }
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
index 74eaf7eaf8bdac93c5ed46749e3c31d4423a3ceb..5e93935ea37a00a2cdd7de310547ce5641b7d3b0 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -3,7 +3,7 @@ require 'lib/gitlab/badge/shared/metadata'
 
 describe Gitlab::Badge::Coverage::Metadata do
   let(:badge) do
-    double(project: create(:project), ref: 'feature', job: 'test')
+    double(project: create(:empty_project), ref: 'feature', job: 'test')
   end
 
   let(:metadata) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 72b1ba36b58b417db60925b14dbb86befcb2de5a..0a2fe5af2c372becaea35c2e1e5ba60830e54da2 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -52,7 +52,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
 
   let(:project) do
     create(
-      :project,
+      :empty_project,
       import_source: project_identifier,
       import_data: ProjectImportData.new(credentials: data)
     )
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index 89245761b6f8d37f29ad1fe41e707b5964c3c890..26b1baf75be3a89eb469dd0256acf5da8791a9f1 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Blame, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:path) { 'files/ruby/popen.rb' }
   let(:commit) { project.commit('master') }
   let(:blob) { project.repository.blob_at(commit.id, path) }
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index a2d84977f585780617d02c101d7d9fbd39f1ed57..b6e924d67bebcd504942564b0889a6db1f0969fe 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::ChatCommands::Command, service: true do
 
     context 'when no command is available' do
       let(:params) { { text: 'issue show 1' } }
-      let(:project) { create(:project, has_external_issue_tracker: true) }
+      let(:project) { create(:empty_project, has_external_issue_tracker: true) }
 
       it 'displays 404 messages' do
         expect(subject[:response_type]).to be(:ephemeral)
@@ -24,7 +24,7 @@ describe Gitlab::ChatCommands::Command, service: true do
 
       it 'displays the help message' do
         expect(subject[:response_type]).to be(:ephemeral)
-        expect(subject[:text]).to start_with('Available commands')
+        expect(subject[:text]).to start_with('Unknown command')
         expect(subject[:text]).to match('/gitlab issue show')
       end
     end
@@ -34,47 +34,7 @@ describe Gitlab::ChatCommands::Command, service: true do
 
       it 'rejects the actions' do
         expect(subject[:response_type]).to be(:ephemeral)
-        expect(subject[:text]).to start_with('Whoops! That action is not allowed')
-      end
-    end
-
-    context 'issue is successfully created' do
-      let(:params) { { text: "issue create my new issue" } }
-
-      before do
-        project.team << [user, :master]
-      end
-
-      it 'presents the issue' do
-        expect(subject[:text]).to match("my new issue")
-      end
-
-      it 'shows a link to the new issue' do
-        expect(subject[:text]).to match(/\/issues\/\d+/)
-      end
-    end
-
-    context 'searching for an issue' do
-      let(:params) { { text: 'issue search find me' } }
-      let!(:issue) { create(:issue, project: project, title: 'find me') }
-
-      before do
-        project.team << [user, :master]
-      end
-
-      context 'a single issue is found' do
-        it 'presents the issue' do
-          expect(subject[:text]).to match(issue.title)
-        end
-      end
-
-      context 'multiple issues found' do
-        let!(:issue2) { create(:issue, project: project, title: "someone find me") }
-
-        it 'shows a link to the new issue' do
-          expect(subject[:text]).to match(issue.title)
-          expect(subject[:text]).to match(issue2.title)
-        end
+        expect(subject[:text]).to start_with('Whoops! This action is not allowed')
       end
     end
 
@@ -90,7 +50,7 @@ describe Gitlab::ChatCommands::Command, service: true do
       context 'and user can not create deployment' do
         it 'returns action' do
           expect(subject[:response_type]).to be(:ephemeral)
-          expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+          expect(subject[:text]).to start_with('Whoops! This action is not allowed')
         end
       end
 
@@ -100,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do
         end
 
         it 'returns action' do
-          expect(subject[:text]).to include('Deployment from staging to production started.')
+          expect(subject[:text]).to include('Deployment started from staging to production')
           expect(subject[:response_type]).to be(:in_channel)
         end
 
@@ -130,7 +90,7 @@ describe Gitlab::ChatCommands::Command, service: true do
     context 'IssueCreate is triggered' do
       let(:params) { { text: 'issue create my title' } }
 
-      it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) }
+      it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
     end
 
     context 'IssueSearch is triggered' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index bd8099c92da997c1e6a1d2a39b53a83155e9f0a4..b3358a321618d5aa22f3709dd87be5a9bc6f788b 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
     end
 
     context 'if no environment is defined' do
-      it 'returns nil' do
-        expect(subject).to be_nil
+      it 'does not execute an action' do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to eq("No action found to be executed")
       end
     end
 
@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
       let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
 
       context 'without actions' do
-        it 'returns nil' do
-          expect(subject).to be_nil
+        it 'does not execute an action' do
+          expect(subject[:response_type]).to be(:ephemeral)
+          expect(subject[:text]).to eq("No action found to be executed")
         end
       end
 
@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
         end
 
         it 'returns success result' do
-          expect(subject.type).to eq(:success)
-          expect(subject.message).to include('Deployment from staging to production started')
+          expect(subject[:response_type]).to be(:in_channel)
+          expect(subject[:text]).to start_with('Deployment started from staging to production')
         end
 
         context 'when duplicate action exists' do
@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
           end
 
           it 'returns error' do
-            expect(subject.type).to eq(:error)
-            expect(subject.message).to include('Too many actions defined')
+            expect(subject[:response_type]).to be(:ephemeral)
+            expect(subject[:text]).to eq('Too many actions defined')
           end
         end
 
@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
                    name: 'teardown', environment: 'production')
           end
 
-          it 'returns success result' do
-            expect(subject.type).to eq(:success)
-            expect(subject.message).to include('Deployment from staging to production started')
+          it 'returns the success message' do
+            expect(subject[:response_type]).to be(:in_channel)
+            expect(subject[:text]).to start_with('Deployment started from staging to production')
           end
         end
       end
diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
similarity index 78%
rename from spec/lib/gitlab/chat_commands/issue_create_spec.rb
rename to spec/lib/gitlab/chat_commands/issue_new_spec.rb
index 6c71e79ff6d163fb12fdd96e0ec095f3cb6939d0..84c22328064e7f6390ba5e6361e315aa34c3f3bc 100644
--- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ChatCommands::IssueCreate, service: true do
+describe Gitlab::ChatCommands::IssueNew, service: true do
   describe '#execute' do
     let(:project) { create(:empty_project) }
     let(:user) { create(:user) }
@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
       it 'creates the issue' do
         expect { subject }.to change { project.issues.count }.by(1)
 
-        expect(subject.title).to eq('bird is the word')
+        expect(subject[:response_type]).to be(:in_channel)
       end
     end
 
@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
         expect { subject }.to change { project.issues.count }.by(1)
       end
     end
+
+    context 'issue cannot be created' do
+      let!(:issue)  { create(:issue, project: project, title: 'bird is the word') }
+      let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
+
+      it 'displays the errors' do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("- Title is too long")
+      end
+    end
   end
 
   describe '.match' do
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
index 24c06a967fa4ed8f30fe13fca196e283eaced5f7..551ccb79a5866f13176dc95d335511445123e002 100644
--- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
 
 describe Gitlab::ChatCommands::IssueSearch, service: true do
   describe '#execute' do
-    let!(:issue) { create(:issue, title: 'find me') }
+    let!(:issue) { create(:issue, project: project, title: 'find me') }
     let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
-    let(:project) { issue.project }
+    let(:project) { create(:empty_project) }
     let(:user) { issue.author }
     let(:regex_match) { described_class.match("issue search find") }
 
@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
 
     context 'when the user has no access' do
       it 'only returns the open issues' do
-        expect(subject).not_to include(confidential)
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("not found")
       end
     end
 
@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
       end
 
       it 'returns all results' do
-        expect(subject).to include(confidential, issue)
+        expect(subject).to have_key(:attachments)
+        expect(subject[:text]).to eq("Here are the 2 issues I found:")
       end
     end
 
     context 'without hits on the query' do
       it 'returns an empty collection' do
-        expect(subject).to be_empty
+        expect(subject[:text]).to match("not found")
       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 2eab73e49e5deb8224795d1472e5b3f3661c56b1..1f20d0a44ce56eb9c1d5af108c15b8a2c7a7b46e 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::ChatCommands::IssueShow, service: true do
   describe '#execute' do
-    let(:issue) { create(:issue) }
-    let(:project) { issue.project }
+    let(:issue) { create(:issue, project: project) }
+    let(:project) { create(:empty_project) }
     let(:user) { issue.author }
     let(:regex_match) { described_class.match("issue show #{issue.iid}") }
 
@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
     end
 
     context 'the issue exists' do
+      let(:title) { subject[:attachments].first[:title] }
+
       it 'returns the issue' do
-        expect(subject.iid).to be issue.iid
+        expect(subject[:response_type]).to be(:in_channel)
+        expect(title).to start_with(issue.title)
       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
+          expect(subject[:response_type]).to be(:in_channel)
+          expect(title).to start_with(issue.title)
         end
       end
     end
@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
     context 'the issue does not exist' do
       let(:regex_match) { described_class.match("issue show 2343242") }
 
-      it "returns nil" do
-        expect(subject).to be_nil
+      it "returns not found" do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("not found")
       end
     end
   end
 
-  describe 'self.match' do
+  describe '.match' do
     it 'matches the iid' do
       match = described_class.match("issue show 123")
 
       expect(match[:iid]).to eq("123")
     end
+
+    it 'accepts a reference' do
+      match = described_class.match("issue show #{Issue.reference_prefix}123")
+
+      expect(match[:iid]).to eq("123")
+    end
   end
 end
diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae41d75ab0c2654df66e2f946d7a43ad0c6bf5f9
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Access do
+  describe '#access_denied' do
+    subject { described_class.new.access_denied }
+
+    it { is_expected.to be_a(Hash) }
+
+    it 'displays an error message' do
+      expect(subject[:text]).to match("is not allowed")
+      expect(subject[:response_type]).to be(:ephemeral)
+    end
+  end
+
+  describe '#not_found' do
+    subject { described_class.new.not_found }
+
+    it { is_expected.to be_a(Hash) }
+
+    it 'tells the user the resource was not found' do
+      expect(subject[:text]).to match("not found!")
+      expect(subject[:response_type]).to be(:ephemeral)
+    end
+  end
+
+  describe '#authorize' do
+    context 'with an authorization URL' do
+      subject { described_class.new('http://authorize.me').authorize }
+
+      it { is_expected.to be_a(Hash) }
+
+      it 'tells the user to authorize' do
+        expect(subject[:text]).to match("connect your GitLab account")
+        expect(subject[:response_type]).to be(:ephemeral)
+      end
+    end
+
+    context 'without authorization url' do
+      subject { described_class.new.authorize }
+
+      it { is_expected.to be_a(Hash) }
+
+      it 'tells the user to authorize' do
+        expect(subject[:text]).to match("Couldn't identify you")
+        expect(subject[:response_type]).to be(:ephemeral)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dc2dd300072f5bd5c63275ad4658e1e7356900c4
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Deploy do
+  let(:build) { create(:ci_build) }
+
+  describe '#present' do
+    subject { described_class.new(build).present('staging', 'prod') }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'messages the channel of the deploy' do
+      expect(subject[:response_type]).to be(:in_channel)
+      expect(subject[:text]).to start_with("Deployment started from staging to prod")
+    end
+  end
+
+  describe '#no_actions' do
+    subject { described_class.new(nil).no_actions }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'tells the user there is no action' do
+      expect(subject[:response_type]).to be(:ephemeral)
+      expect(subject[:text]).to eq("No action found to be executed")
+    end
+  end
+
+  describe '#too_many_actions' do
+    subject { described_class.new([]).too_many_actions }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'tells the user there is no action' do
+      expect(subject[:response_type]).to be(:ephemeral)
+      expect(subject[:text]).to eq("Too many actions defined")
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17fcdbc2452c69700d310fe1c7dbf86acfacdf4a
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueNew do
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, project: project) }
+  let(:attachment) { subject[:attachments].first }
+
+  subject { described_class.new(issue).present }
+
+  it { is_expected.to be_a(Hash) }
+
+  it 'shows the issue' do
+    expect(subject[:response_type]).to be(:in_channel)
+    expect(subject).to have_key(:attachments)
+    expect(attachment[:title]).to start_with(issue.title)
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec6d3e34a9696df433a497c43b51f17efc2bb2f2
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueSearch do
+  let(:project) { create(:empty_project) }
+  let(:message) { subject[:text] }
+
+  before { create_list(:issue, 2, project: project) }
+
+  subject { described_class.new(project.issues).present }
+
+  it 'formats the message correct' do
+    is_expected.to have_key(:text)
+    is_expected.to have_key(:status)
+    is_expected.to have_key(:response_type)
+    is_expected.to have_key(:attachments)
+  end
+
+  it 'shows a list of results' do
+    expect(subject[:response_type]).to be(:ephemeral)
+
+    expect(message).to start_with("Here are the 2 issues I found")
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5b678d31fce40d90918ea703b140771a266e9e60
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueShow do
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, project: project) }
+  let(:attachment) { subject[:attachments].first }
+
+  subject { described_class.new(issue).present }
+
+  it { is_expected.to be_a(Hash) }
+
+  it 'shows the issue' do
+    expect(subject[:response_type]).to be(:in_channel)
+    expect(subject).to have_key(:attachments)
+    expect(attachment[:title]).to start_with(issue.title)
+  end
+
+  context 'with upvotes' do
+    before do
+      create(:award_emoji, :upvote, awardable: issue)
+    end
+
+    it 'shows the upvote count' do
+      expect(subject[:response_type]).to be(:in_channel)
+      expect(attachment[:text]).to start_with("**Open** · :+1: 1")
+    end
+  end
+
+  context 'confidential issue' do
+    let(:issue) { create(:issue, project: project) }
+
+    it 'shows an ephemeral response' do
+      expect(subject[:response_type]).to be(:in_channel)
+      expect(attachment[:text]).to start_with("**Open**")
+    end
+  end
+end
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 98effecdbbc8deae5a8dae74a7db2cac29451e63..cadfbadca10c18917122e4133aee5f1dfa8651ea 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Checks::ChangeAccess, lib: true do
   describe '#exec' do
     let(:user) { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:user_access) { Gitlab::UserAccess.new(user, project: project) }
     let(:changes) do
       {
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index f6288011494be13af46e00feeb1c02a379c5220b..7a84bbebd0231912f97ead4719a6ae2d9bd2e070 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Checks::ChangeAccess, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   context "exit code checking" do
     it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4c6bd859552cc032a1e0cd52abe75f61ae1507d9
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Coverage do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validations' do
+    context "when entry config value doesn't have the surrounding '/'" do
+      let(:config) { 'Code coverage: \d+\.\d+' }
+
+      describe '#errors' do
+        subject { entry.errors }
+        it { is_expected.to include(/coverage config must be a regular expression/) }
+      end
+
+      describe '#valid?' do
+        subject { entry }
+        it { is_expected.not_to be_valid }
+      end
+    end
+
+    context "when entry config value has the surrounding '/'" do
+      let(:config) { '/Code coverage: \d+\.\d+/' }
+
+      describe '#value' do
+        subject { entry.value }
+        it { is_expected.to eq(config[1...-1]) }
+      end
+
+      describe '#errors' do
+        subject { entry.errors }
+        it { is_expected.to be_empty }
+      end
+
+      describe '#valid?' do
+        subject { entry }
+        it { is_expected.to be_valid }
+      end
+    end
+
+    context 'when entry value is not valid' do
+      let(:config) { '(malformed regexp' }
+
+      describe '#errors' do
+        subject { entry.errors }
+        it { is_expected.to include(/coverage config must be a regular expression/) }
+      end
+
+      describe '#valid?' do
+        subject { entry }
+        it { is_expected.not_to be_valid }
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index e64c8d46bd8302a652365f6e86ec4a5f183b1fd3..d4f1780b17446c01d0571e03e68a63d197e5559c 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -4,12 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do
   let(:global) { described_class.new(hash) }
 
   describe '.nodes' do
-    it 'can contain global config keys' do
-      expect(described_class.nodes).to include :before_script
+    it 'returns a hash' do
+      expect(described_class.nodes).to be_a(Hash)
     end
 
-    it 'returns a hash' do
-      expect(described_class.nodes).to be_a Hash
+    context 'when filtering all the entry/node names' do
+      it 'contains the expected node names' do
+        node_names = described_class.nodes.keys
+        expect(node_names).to match_array(%i[before_script image services
+                                             after_script variables stages
+                                             types cache coverage])
+      end
     end
   end
 
@@ -35,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do
         end
 
         it 'creates node object for each entry' do
-          expect(global.descendants.count).to eq 8
+          expect(global.descendants.count).to eq 9
         end
 
         it 'creates node object using valid class' do
@@ -176,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do
 
       describe '#nodes' do
         it 'instantizes all nodes' do
-          expect(global.descendants.count).to eq 8
+          expect(global.descendants.count).to eq 9
         end
 
         it 'contains unspecified nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index fc9b8b86dc409402ae4211cc75bbf96fd4fe533d..d20f4ec207d68bc8b49004455bc73d235586901d 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -3,6 +3,20 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Entry::Job do
   let(:entry) { described_class.new(config, name: :rspec) }
 
+  describe '.nodes' do
+    context 'when filtering all the entry/node names' do
+      subject { described_class.nodes.keys }
+
+      let(:result) do
+        %i[before_script script stage type after_script cache
+           image services only except variables artifacts
+           environment coverage]
+      end
+
+      it { is_expected.to match_array result }
+    end
+  end
+
   describe 'validations' do
     before { entry.compose! }
 
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index b3c07347de132436546eea6672d0784bb4590997..8ad9b7cdf077d15f37b57a687f24a54246cc80db 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Status::Build::Cancelable do
     end
 
     describe '#action_icon' do
-      it { expect(subject.action_icon).to eq 'ban' }
+      it { expect(subject.action_icon).to eq 'icon_action_cancel' }
     end
 
     describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index dccb29b5ef606b380137e2b9090bf616bbfe4afe..0c40fca0c1a649186c5ecb7d2c869144270ae369 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -3,15 +3,23 @@ require 'spec_helper'
 describe Gitlab::Ci::Status::Build::Factory do
   let(:user) { create(:user) }
   let(:project) { build.project }
-
-  subject { described_class.new(build, user) }
-  let(:status) { subject.fabricate! }
+  let(:status) { factory.fabricate! }
+  let(:factory) { described_class.new(build, user) }
 
   before { project.team << [user, :developer] }
 
   context 'when build is successful' do
     let(:build) { create(:ci_build, :success) }
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::Build::Retryable]
+    end
+
     it 'fabricates a retryable build status' do
       expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
     end
@@ -26,24 +34,72 @@ describe Gitlab::Ci::Status::Build::Factory do
   end
 
   context 'when build is failed' do
-    let(:build) { create(:ci_build, :failed) }
+    context 'when build is not allowed to fail' do
+      let(:build) { create(:ci_build, :failed) }
 
-    it 'fabricates a retryable build status' do
-      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses)
+          .to eq [Gitlab::Ci::Status::Build::Retryable]
+      end
+
+      it 'fabricates a retryable build status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'failed'
+        expect(status.icon).to eq 'icon_status_failed'
+        expect(status.label).to eq 'failed'
+        expect(status).to have_details
+        expect(status).to have_action
+      end
     end
 
-    it 'fabricates status with correct details' do
-      expect(status.text).to eq 'failed'
-      expect(status.icon).to eq 'icon_status_failed'
-      expect(status.label).to eq 'failed'
-      expect(status).to have_details
-      expect(status).to have_action
+    context 'when build is allowed to fail' do
+      let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
+
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses)
+          .to eq [Gitlab::Ci::Status::Build::Retryable,
+                  Gitlab::Ci::Status::Build::FailedAllowed]
+      end
+
+      it 'fabricates a failed but allowed build status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::FailedAllowed
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'failed'
+        expect(status.icon).to eq 'icon_status_warning'
+        expect(status.label).to eq 'failed (allowed to fail)'
+        expect(status).to have_details
+        expect(status).to have_action
+        expect(status.action_title).to include 'Retry'
+        expect(status.action_path).to include 'retry'
+      end
     end
   end
 
   context 'when build is a canceled' do
     let(:build) { create(:ci_build, :canceled) }
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Canceled
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::Build::Retryable]
+    end
+
     it 'fabricates a retryable build status' do
       expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
     end
@@ -60,6 +116,15 @@ describe Gitlab::Ci::Status::Build::Factory do
   context 'when build is running' do
     let(:build) { create(:ci_build, :running) }
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Running
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::Build::Cancelable]
+    end
+
     it 'fabricates a canceable build status' do
       expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
     end
@@ -76,6 +141,15 @@ describe Gitlab::Ci::Status::Build::Factory do
   context 'when build is pending' do
     let(:build) { create(:ci_build, :pending) }
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Pending
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::Build::Cancelable]
+    end
+
     it 'fabricates a cancelable build status' do
       expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
     end
@@ -92,6 +166,14 @@ describe Gitlab::Ci::Status::Build::Factory do
   context 'when build is skipped' do
     let(:build) { create(:ci_build, :skipped) }
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
+    end
+
+    it 'does not match extended statuses' do
+      expect(factory.extended_statuses).to be_empty
+    end
+
     it 'fabricates a core skipped status' do
       expect(status).to be_a Gitlab::Ci::Status::Skipped
     end
@@ -109,6 +191,15 @@ describe Gitlab::Ci::Status::Build::Factory do
     context 'when build is a play action' do
       let(:build) { create(:ci_build, :playable) }
 
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses)
+          .to eq [Gitlab::Ci::Status::Build::Play]
+      end
+
       it 'fabricates a core skipped status' do
         expect(status).to be_a Gitlab::Ci::Status::Build::Play
       end
@@ -119,12 +210,22 @@ describe Gitlab::Ci::Status::Build::Factory do
         expect(status.label).to eq 'manual play action'
         expect(status).to have_details
         expect(status).to have_action
+        expect(status.action_path).to include 'play'
       end
     end
 
     context 'when build is an environment stop action' do
       let(:build) { create(:ci_build, :playable, :teardown_environment) }
 
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses)
+          .to eq [Gitlab::Ci::Status::Build::Stop]
+      end
+
       it 'fabricates a core skipped status' do
         expect(status).to be_a Gitlab::Ci::Status::Build::Stop
       end
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..20f714597385bf5170dbd754371710500502c5a5
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -0,0 +1,110 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::FailedAllowed do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'returns a warning icon' do
+      expect(subject.icon).to eq 'icon_status_warning'
+    end
+  end
+
+  describe '#label' do
+    it 'returns information about failed but allowed to fail status' do
+      expect(subject.label).to eq 'failed (allowed to fail)'
+    end
+  end
+
+  describe '#group' do
+    it 'returns status failed with warnings status group' do
+      expect(subject.group).to eq 'failed_with_warnings'
+    end
+  end
+
+  describe 'action details' do
+    describe '#has_action?' do
+      it 'does not decorate action details' do
+        expect(status).to receive(:has_action?)
+
+        subject.has_action?
+      end
+    end
+
+    describe '#action_path' do
+      it 'does not decorate action path' do
+        expect(status).to receive(:action_path)
+
+        subject.action_path
+      end
+    end
+
+    describe '#action_icon' do
+      it 'does not decorate action icon' do
+        expect(status).to receive(:action_icon)
+
+        subject.action_icon
+      end
+    end
+
+    describe '#action_title' do
+      it 'does not decorate action title' do
+        expect(status).to receive(:action_title)
+
+        subject.action_title
+      end
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is failed' do
+      context 'when build is allowed to fail' do
+        let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
+
+        it 'is a correct match' do
+          expect(subject).to be true
+        end
+      end
+
+      context 'when build is not allowed to fail' do
+        let(:build) { create(:ci_build, :failed) }
+
+        it 'is not a correct match' do
+          expect(subject).not_to be true
+        end
+      end
+    end
+
+    context 'when build did not fail' do
+      context 'when build is allowed to fail' do
+        let(:build) { create(:ci_build, :success, :allowed_to_fail) }
+
+        it 'is not a correct match' do
+          expect(subject).not_to be true
+        end
+      end
+
+      context 'when build is not allowed to fail' do
+        let(:build) { create(:ci_build, :success) }
+
+        it 'is not a correct match' do
+          expect(subject).not_to be true
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f1b50a59ce673258c3d0ffc21226b0929511dc8b..f3e72ea1796b64c8c914bf84de83b951d64b0637 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::Ci::Status::Build::Play do
     end
 
     describe '#action_icon' do
-      it { expect(subject.action_icon).to eq 'play' }
+      it { expect(subject.action_icon).to eq 'icon_action_play' }
     end
 
     describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 62036f8ec5d97779de4112b94e9a934106a656f2..2db0f8d29bd0a18548154733c24bcf191e41d687 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Status::Build::Retryable do
     end
 
     describe '#action_icon' do
-      it { expect(subject.action_icon).to eq 'refresh' }
+      it { expect(subject.action_icon).to eq 'icon_action_retry' }
     end
 
     describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 597e02e86e48a9496830660632844e690946f7b7..41c2b6247745805650199aa9b874fd2959e1050b 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Status::Build::Stop do
     end
 
     describe '#action_icon' do
-      it { expect(subject.action_icon).to eq 'stop' }
+      it { expect(subject.action_icon).to eq 'icon_action_stop' }
     end
 
     describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index f92a1c149bf1c604c47fe2f81b917b57d649c395..bbf9c7c83a3877aaf0180d4c504c192da3c39a68 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -1,24 +1,135 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Factory do
-  subject do
-    described_class.new(resource, user)
+  let(:user) { create(:user) }
+  let(:fabricated_status) { factory.fabricate! }
+  let(:factory) { described_class.new(resource, user) }
+
+  context 'when object has a core status' do
+    HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+      context "when simple core status is #{simple_status}" do
+        let(:resource) { double('resource', status: simple_status) }
+
+        let(:expected_status) do
+          Gitlab::Ci::Status.const_get(simple_status.capitalize)
+        end
+
+        it "fabricates a core status #{simple_status}" do
+          expect(fabricated_status).to be_a expected_status
+        end
+
+        it "matches a valid core status for #{simple_status}" do
+          expect(factory.core_status).to be_a expected_status
+        end
+
+        it "does not match any extended statuses for #{simple_status}" do
+          expect(factory.extended_statuses).to be_empty
+        end
+      end
+    end
   end
 
-  let(:user) { create(:user) }
+  context 'when resource supports multiple extended statuses' do
+    let(:resource) { double('resource', status: :success) }
 
-  let(:status) { subject.fabricate! }
+    let(:first_extended_status) do
+      Class.new(SimpleDelegator) do
+        def first_method
+          'first return value'
+        end
 
-  context 'when object has a core status' do
-    HasStatus::AVAILABLE_STATUSES.each do |core_status|
-      context "when core status is #{core_status}" do
-        let(:resource) { double(status: core_status) }
+        def second_method
+          'second return value'
+        end
+
+        def self.matches?(*)
+          true
+        end
+      end
+    end
 
-        it "fabricates a core status #{core_status}" do
-          expect(status).to be_a(
-            Gitlab::Ci::Status.const_get(core_status.capitalize))
+    let(:second_extended_status) do
+      Class.new(SimpleDelegator) do
+        def first_method
+          'decorated return value'
         end
+
+        def third_method
+          'third return value'
+        end
+
+        def self.matches?(*)
+          true
+        end
+      end
+    end
+
+    shared_examples 'compound decorator factory' do
+      it 'fabricates compound decorator' do
+        expect(fabricated_status.first_method).to eq 'decorated return value'
+        expect(fabricated_status.second_method).to eq 'second return value'
+        expect(fabricated_status.third_method).to eq 'third return value'
       end
+
+      it 'delegates to core status' do
+        expect(fabricated_status.text).to eq 'passed'
+      end
+
+      it 'latest matches status becomes a status name' do
+        expect(fabricated_status.class).to eq second_extended_status
+      end
+
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses)
+          .to eq [first_extended_status, second_extended_status]
+      end
+    end
+
+    context 'when exclusive statuses are matches' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([[first_extended_status, second_extended_status]])
+      end
+
+      it 'does not fabricate compound decorator' do
+        expect(fabricated_status.first_method).to eq 'first return value'
+        expect(fabricated_status.second_method).to eq 'second return value'
+        expect(fabricated_status).not_to respond_to(:third_method)
+      end
+
+      it 'delegates to core status' do
+        expect(fabricated_status.text).to eq 'passed'
+      end
+
+      it 'matches correct core status' do
+        expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+      end
+
+      it 'matches correct extended statuses' do
+        expect(factory.extended_statuses).to eq [first_extended_status]
+      end
+    end
+
+    context 'when exclusive statuses are not matched' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([[first_extended_status], [second_extended_status]])
+      end
+
+      it_behaves_like 'compound decorator factory'
+    end
+
+    context 'when using simplified status grouping' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([first_extended_status, second_extended_status])
+      end
+
+      it_behaves_like 'compound decorator factory'
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index d4a2dc7fcc1e1bb0d5213ffb099dc12cd8a61f44..b10a447c27a8dade794b3dbfcd06368937e57834 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -3,29 +3,32 @@ require 'spec_helper'
 describe Gitlab::Ci::Status::Pipeline::Factory do
   let(:user) { create(:user) }
   let(:project) { pipeline.project }
-
-  subject do
-    described_class.new(pipeline, user)
-  end
-
-  let(:status) do
-    subject.fabricate!
-  end
+  let(:status) { factory.fabricate! }
+  let(:factory) { described_class.new(pipeline, user) }
 
   before do
     project.team << [user, :developer]
   end
 
   context 'when pipeline has a core status' do
-    HasStatus::AVAILABLE_STATUSES.each do |core_status|
-      context "when core status is #{core_status}" do
-        let(:pipeline) do
-          create(:ci_pipeline, status: core_status)
+    HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+      context "when core status is #{simple_status}" do
+        let(:pipeline) { create(:ci_pipeline, status: simple_status) }
+
+        let(:expected_status) do
+          Gitlab::Ci::Status.const_get(simple_status.capitalize)
+        end
+
+        it "matches correct core status for #{simple_status}" do
+          expect(factory.core_status).to be_a expected_status
         end
 
-        it "fabricates a core status #{core_status}" do
-          expect(status).to be_a(
-            Gitlab::Ci::Status.const_get(core_status.capitalize))
+        it 'does not matche extended statuses' do
+          expect(factory.extended_statuses).to be_empty
+        end
+
+        it "fabricates a core status #{simple_status}" do
+          expect(status).to be_a expected_status
         end
 
         it 'extends core status with common pipeline methods' do
@@ -47,13 +50,22 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
       create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
     end
 
+    it 'matches correct core status' do
+      expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+    end
+
+    it 'matches correct extended statuses' do
+      expect(factory.extended_statuses)
+        .to eq [Gitlab::Ci::Status::SuccessWarning]
+    end
+
     it 'fabricates extended "success with warnings" status' do
-      expect(status)
-        .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings
+      expect(status).to be_a Gitlab::Ci::Status::SuccessWarning
     end
 
-    it 'extends core status with common pipeline methods' do
+    it 'extends core status with common pipeline method' do
       expect(status).to have_details
+      expect(status.details_path).to include "pipelines/#{pipeline.id}"
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
deleted file mode 100644
index 979160eb9c461d28c5bf8c1034c4e663b928866e..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
-  subject do
-    described_class.new(double('status'))
-  end
-
-  describe '#test' do
-    it { expect(subject.text).to eq 'passed' }
-  end
-
-  describe '#label' do
-    it { expect(subject.label).to eq 'passed with warnings' }
-  end
-
-  describe '#icon' do
-    it { expect(subject.icon).to eq 'icon_status_warning' }
-  end
-
-  describe '#group' do
-    it { expect(subject.group).to eq 'success_with_warnings' }
-  end
-
-  describe '.matches?' do
-    context 'when pipeline is successful' do
-      let(:pipeline) do
-        create(:ci_pipeline, status: :success)
-      end
-
-      context 'when pipeline has warnings' do
-        before do
-          allow(pipeline).to receive(:has_warnings?).and_return(true)
-        end
-
-        it 'is a correct match' do
-          expect(described_class.matches?(pipeline, double)).to eq true
-        end
-      end
-
-      context 'when pipeline does not have warnings' do
-        it 'does not match' do
-          expect(described_class.matches?(pipeline, double)).to eq false
-        end
-      end
-    end
-
-    context 'when pipeline is not successful' do
-      let(:pipeline) do
-        create(:ci_pipeline, status: :skipped)
-      end
-
-      context 'when pipeline has warnings' do
-        before do
-          allow(pipeline).to receive(:has_warnings?).and_return(true)
-        end
-
-        it 'does not match' do
-          expect(described_class.matches?(pipeline, double)).to eq false
-        end
-      end
-
-      context 'when pipeline does not have warnings' do
-        it 'does not match' do
-          expect(described_class.matches?(pipeline, double)).to eq false
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 6f8721d30c22c14de01d88ea1095397a6643de73..bbb40e2c1abbad8af5f4b35bd8886bc48073f985 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -43,4 +43,25 @@ describe Gitlab::Ci::Status::Stage::Factory do
       end
     end
   end
+
+  context 'when stage has warnings' do
+    let(:stage) do
+      build(:ci_stage, name: 'test', status: :success, pipeline: pipeline)
+    end
+
+    before do
+      create(:ci_build, :allowed_to_fail, :failed,
+             stage: 'test', pipeline: stage.pipeline)
+    end
+
+    it 'fabricates extended "success with warnings" status' do
+      expect(status)
+        .to be_a Gitlab::Ci::Status::SuccessWarning
+    end
+
+    it 'extends core status with common stage method' do
+      expect(status).to have_details
+      expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}"
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e2269397c667691e9450cbd0bd1b12355d7627f
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::SuccessWarning do
+  subject do
+    described_class.new(double('status'))
+  end
+
+  describe '#test' do
+    it { expect(subject.text).to eq 'passed' }
+  end
+
+  describe '#label' do
+    it { expect(subject.label).to eq 'passed with warnings' }
+  end
+
+  describe '#icon' do
+    it { expect(subject.icon).to eq 'icon_status_warning' }
+  end
+
+  describe '#group' do
+    it { expect(subject.group).to eq 'success_with_warnings' }
+  end
+
+  describe '.matches?' do
+    let(:matchable) { double('matchable') }
+
+    context 'when matchable subject is successful' do
+      before do
+        allow(matchable).to receive(:success?).and_return(true)
+      end
+
+      context 'when matchable subject has warnings' do
+        before do
+          allow(matchable).to receive(:has_warnings?).and_return(true)
+        end
+
+        it 'is a correct match' do
+          expect(described_class.matches?(matchable, double)).to eq true
+        end
+      end
+
+      context 'when matchable subject does not have warnings' do
+        before do
+          allow(matchable).to receive(:has_warnings?).and_return(false)
+        end
+
+        it 'does not match' do
+          expect(described_class.matches?(matchable, double)).to eq false
+        end
+      end
+    end
+
+    context 'when matchable subject is not successful' do
+      before do
+        allow(matchable).to receive(:success?).and_return(false)
+      end
+
+      context 'when matchable subject has warnings' do
+        before do
+          allow(matchable).to receive(:has_warnings?).and_return(true)
+        end
+
+        it 'does not match' do
+          expect(described_class.matches?(matchable, double)).to eq false
+        end
+      end
+
+      context 'when matchable subject does not have warnings' do
+        it 'does not match' do
+          expect(described_class.matches?(matchable, double)).to eq false
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb
index f06d78694d60022d4da37695196260e2b2e5162f..ff5551bf703e1f77baa1378366ba19691b492aeb 100644
--- a/spec/lib/gitlab/ci/trace_reader_spec.rb
+++ b/spec/lib/gitlab/ci/trace_reader_spec.rb
@@ -11,13 +11,25 @@ describe Gitlab::Ci::TraceReader do
       last_lines = random_lines
 
       expected = lines.last(last_lines).join
+      result = subject.read(last_lines: last_lines)
 
-      expect(subject.read(last_lines: last_lines)).to eq(expected)
+      expect(result).to eq(expected)
+      expect(result.encoding).to eq(Encoding.default_external)
     end
   end
 
   it 'returns everything if trying to get too many lines' do
-    expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join)
+    result = build_subject.read(last_lines: lines.size * 2)
+
+    expect(result).to eq(lines.join)
+    expect(result.encoding).to eq(Encoding.default_external)
+  end
+
+  it 'returns all contents if last_lines is not specified' do
+    result = build_subject.read
+
+    expect(result).to eq(lines.join)
+    expect(result.encoding).to eq(Encoding.default_external)
   end
 
   it 'raises an error if not passing an integer for last_lines' do
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 1bbaca0739af5d18d06dccc1e3b10443335e0b11..97af1c2523dc3caf0075eb90d413875ef0b60152 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,11 +1,11 @@
 require 'spec_helper'
 
 describe Gitlab::ClosingIssueExtractor, lib: true do
-  let(:project)   { create(:project) }
-  let(:project2)   { create(:project) }
+  let(:project) { create(:empty_project) }
+  let(:project2) { create(:empty_project) }
   let(:forked_project) { Projects::ForkService.new(project, project.creator).execute }
-  let(:issue)     { create(:issue, project: project) }
-  let(:issue2)     { create(:issue, project: project2) }
+  let(:issue) { create(:issue, project: project) }
+  let(:issue2) { create(:issue, project: project2) }
   let(:reference) { issue.to_reference }
   let(:cross_reference) { issue2.to_reference(project) }
   let(:fork_cross_reference) { issue.to_reference(forked_project) }
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 648d342ecf8e7d20f3c6e329c465f61968e37293..fbf679c521554e727272edb59ccb3274dd4fdc0e 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Conflict::File, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
   let(:rugged) { repository.rugged }
   let(:their_commit) { rugged.branches['conflict-start'].target }
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 004341ffd0214cc05eb1a39cf7c5bac04e4e5ac1..b01c4805a3449a624875fafad045490b645a044f 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -1,36 +1,64 @@
 require 'spec_helper'
 
 describe Gitlab::CurrentSettings do
+  include StubENV
+
+  before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+  end
+
   describe '#current_application_settings' do
-    it 'attempts to use cached values first' do
-      allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
-      expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults)
-      expect(ApplicationSetting).not_to receive(:last)
+    context 'with DB available' do
+      before do
+        allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
+      end
 
-      expect(current_application_settings).to be_a(ApplicationSetting)
-    end
+      it 'attempts to use cached values first' do
+        expect(ApplicationSetting).to receive(:current)
+        expect(ApplicationSetting).not_to receive(:last)
+
+        expect(current_application_settings).to be_a(ApplicationSetting)
+      end
 
-    it 'does not attempt to connect to DB or Redis' do
-      allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false)
-      expect(ApplicationSetting).not_to receive(:current)
-      expect(ApplicationSetting).not_to receive(:last)
+      it 'falls back to DB if Redis returns an empty value' do
+        expect(ApplicationSetting).to receive(:last).and_call_original
 
-      expect(current_application_settings).to eq fake_application_settings
+        expect(current_application_settings).to be_a(ApplicationSetting)
+      end
+
+      it 'falls back to DB if Redis fails' do
+        expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError)
+        expect(ApplicationSetting).to receive(:last).and_call_original
+
+        expect(current_application_settings).to be_a(ApplicationSetting)
+      end
     end
 
-    it 'falls back to DB if Redis returns an empty value' do
-      allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
-      expect(ApplicationSetting).to receive(:last).and_call_original
+    context 'with DB unavailable' do
+      before do
+        allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false)
+      end
 
-      expect(current_application_settings).to be_a(ApplicationSetting)
+      it 'returns an in-memory ApplicationSetting object' do
+        expect(ApplicationSetting).not_to receive(:current)
+        expect(ApplicationSetting).not_to receive(:last)
+
+        expect(current_application_settings).to be_a(OpenStruct)
+      end
     end
 
-    it 'falls back to DB if Redis fails' do
-      allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
-      expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError)
-      expect(ApplicationSetting).to receive(:last).and_call_original
+    context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do
+      before do
+        stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true')
+      end
+
+      it 'returns an in-memory ApplicationSetting object' do
+        expect(ApplicationSetting).not_to receive(:current)
+        expect(ApplicationSetting).not_to receive(:last)
 
-      expect(current_application_settings).to be_a(ApplicationSetting)
+        expect(current_application_settings).to be_a(ApplicationSetting)
+        expect(current_application_settings).not_to be_persisted
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index fb6b6c4a8d282090148156b5d21970fef4235fa7..3dd76ba5b8a060103354202af0eca0a56b7bfe2a 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::CycleAnalytics::StageSummary, models: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from) { 1.day.ago }
   let(:user) { create(:user, :admin) }
   subject { described_class.new(project, from: Time.now, current_user: user).data }
@@ -15,7 +15,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do
     end
 
     it "doesn't find issues from other projects" do
-      Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) }
+      Timecop.freeze(5.days.from_now) { create(:issue, project: create(:empty_project)) }
 
       expect(subject.first[:value]).to eq(0)
     end
@@ -30,7 +30,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do
     end
 
     it "doesn't find commits from other projects" do
-      Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project), user, 'master') }
+      Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') }
 
       expect(subject.second[:value]).to eq(0)
     end
@@ -51,7 +51,9 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do
     end
 
     it "doesn't find commits from other projects" do
-      Timecop.freeze(5.days.from_now) { create(:deployment, project: create(:project)) }
+      Timecop.freeze(5.days.from_now) do
+        create(:deployment, project: create(:project, :repository))
+      end
 
       expect(subject.third[:value]).to eq(0)
     end
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 9a4dec91e56389a837c0aebfcb05391da8da64fd..04ec34492e1809ed7d9a052b72a8f0e3b1a9ce17 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::DataBuilder::Note, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:data) { described_class.build(note, user) }
   let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index a68f5943a6a91015338a69a24789480f6d259363..f13041e498c46510d92d50f3a7481e7fe8b2395b 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::DataBuilder::Pipeline do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   let(:pipeline) do
     create(:ci_pipeline,
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index a379f798a16c0227604a461ccde9aa616bba89ac..dbcfb9b7400ad7c4ca525cf1b1658d9e9fc5e198 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::DataBuilder::Push, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
 
   describe '.build_sample' do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 38475792d933773a3cf1edf8619ca3165f3d13c8..050689b7c9ae14174299a080162571d34016b979 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Diff::File, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diff) { commit.raw_diffs.first }
   let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 1c2ddeed6922afc45e58649be1f3512f5c052738..5893485634d326e7e66e206bd88011ac29c471c4 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Diff::Highlight, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diff) { commit.raw_diffs.first }
   let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
@@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do
     context "with a diff file" do
       let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
 
-      it 'should return Gitlab::Diff::Line elements' do
+      it 'returns Gitlab::Diff::Line elements' do
         expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
       end
 
-      it 'should not modify "match" lines' do
+      it 'does not modify "match" lines' do
         expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
         expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
       end
@@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do
     context "with diff lines" do
       let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
 
-      it 'should return Gitlab::Diff::Line elements' do
+      it 'returns Gitlab::Diff::Line elements' do
         expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
       end
 
-      it 'should not modify "match" lines' do
+      it 'does not modify "match" lines' do
         expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
         expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
       end
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 4b943fa382d02e1b73073d57fae5ffb075e72080..2c7ecd1907ec6cbe8bce3be3f29907a2e4808fe3 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Diff::LineMapper, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diffs) { commit.raw_diffs }
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index af18d3c25a674808771e654ab647432646bc74ba..0f779339c544d335c28de77e1bbff08379aaa532 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Diff::ParallelDiff, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diffs) { commit.raw_diffs }
@@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
   subject { described_class.new(diff_file) }
 
   describe '#parallelize' do
-    it 'should return an array of arrays containing the parsed diff' do
+    it 'returns an array of arrays containing the parsed diff' do
       diff_lines = diff_file.highlighted_diff_lines
       expected = [
         # Unchanged lines
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 6e8fff6f5163c43589a580ca4a2440cbea0df8cb..cdf0af6d7ef7521c84644721d3d2b8732a5b899b 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Diff::Position, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   describe "position for an added file" do
     let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") }
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index c268f84c75900ddea0c604fb40503cf8685ea981..8e3e4034c8f021d3e9a46e9d241de9680769ee22 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -51,7 +51,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
 
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:current_user) { project.owner }
   let(:repository) { project.repository }
   let(:file_name) { "test-file" }
@@ -99,7 +99,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
     Files::CreateService.new(
       project,
       current_user,
-      source_branch: branch_name,
+      start_branch: branch_name,
       target_branch: branch_name,
       commit_message: "Create file",
       file_path: file_name,
@@ -112,7 +112,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
     Files::UpdateService.new(
       project,
       current_user,
-      source_branch: branch_name,
+      start_branch: branch_name,
       target_branch: branch_name,
       commit_message: "Update file",
       file_path: file_name,
@@ -125,7 +125,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
     Files::DeleteService.new(
       project,
       current_user,
-      source_branch: branch_name,
+      start_branch: branch_name,
       target_branch: branch_name,
       commit_message: "Delete file",
       file_path: file_name
@@ -1640,7 +1640,9 @@ describe Gitlab::Diff::PositionTracer, lib: true do
         }
 
         merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project)
-        repository.merge(current_user, merge_request, options)
+
+        repository.merge(current_user, merge_request.diff_head_sha, merge_request, options)
+
         project.commit(branch_name)
       end
 
diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb
index 19298e261e39866313a183cdc879d13a8b0bf614..9d806fc524d0bea1b456df3e40d8d1106634e3a3 100644
--- a/spec/lib/gitlab/email/email_shared_blocks.rb
+++ b/spec/lib/gitlab/email/email_shared_blocks.rb
@@ -18,7 +18,7 @@ shared_context :email_shared_context do
   end
 end
 
-shared_examples :email_shared_examples do
+shared_examples :reply_processing_shared_examples do
   context "when the user could not be found" do
     before do
       user.destroy
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index cb3651e3845be2137f4be158b7ff56cdb822370f..4a9c9a7fe345357937185cc476445ce8d4c3e376 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -3,7 +3,7 @@ require_relative '../email_shared_blocks'
 
 describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
   include_context :email_shared_context
-  it_behaves_like :email_shared_examples
+  it_behaves_like :reply_processing_shared_examples
 
   before do
     stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
@@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
   let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
   let(:namespace) { create(:namespace, path: 'gitlabhq') }
 
-  let!(:project)  { create(:project, :public, namespace: namespace) }
+  let!(:project)  { create(:project, :public, :repository, namespace: namespace) }
   let!(:user) do
     create(
       :user,
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 48660d1dd1b035c12e9a1da674e6bc9ef33a31d4..17a4ef25210c6a711c8c1ade3bbd21ab993350ab 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -3,7 +3,7 @@ require_relative '../email_shared_blocks'
 
 describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
   include_context :email_shared_context
-  it_behaves_like :email_shared_examples
+  it_behaves_like :reply_processing_shared_examples
 
   before do
     stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
@@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
   end
 
   let(:email_raw) { fixture_file('emails/valid_reply.eml') }
-  let(:project)   { create(:project, :public) }
+  let(:project)   { create(:project, :public, :repository) }
   let(:user)      { create(:user) }
   let(:note)      { create(:diff_note_on_merge_request, project: project) }
   let(:noteable)  { note.noteable }
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0939e6c45145a707a805d97cab5db128679f3c99
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::UnsubscribeHandler, lib: true do
+  include_context :email_shared_context
+
+  before do
+    stub_incoming_email_setting(enabled: true, address: 'reply+%{key}@appmail.adventuretime.ooo')
+    stub_config_setting(host: 'localhost')
+  end
+
+  let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") }
+  let(:project) { create(:empty_project, :public) }
+  let(:user) { create(:user) }
+  let(:noteable) { create(:issue, project: project) }
+
+  let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+
+  context 'when notification concerns a commit' do
+    let(:commit) { create(:commit, project: project) }
+    let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) }
+
+    it 'handler does not raise an error' do
+      expect { receiver.execute }.not_to raise_error
+    end
+  end
+
+  context 'user is unsubscribed' do
+    it 'leaves user unsubscribed' do
+      expect { receiver.execute }.not_to change { noteable.subscribed?(user) }.from(false)
+    end
+  end
+
+  context 'user is subscribed' do
+    before do
+      noteable.subscribe(user)
+    end
+
+    it 'unsubscribes user from notable' do
+      expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false)
+    end
+  end
+
+  context 'when the noteable could not be found' do
+    before do
+      noteable.destroy
+    end
+
+    it 'raises a NoteableNotFoundError' do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError)
+    end
+  end
+
+  context 'when no sent notification for the mail key could be found' do
+    let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') }
+
+    it 'raises a SentNotificationNotFoundError' do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 5b966bddb6a6733a19b6db6800479a1713001c55..7b3291b83154b1b7a5cb600b86f55cfa730d2143 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do
   include RepoHelpers
 
   let!(:group) { create(:group, name: 'my_group') }
-  let!(:project) { create(:project, name: 'my_project', namespace: group) }
+  let!(:project) { create(:project, :repository, name: 'my_project', namespace: group) }
   let!(:author) { create(:author, name: 'Author') }
 
   let(:message) do
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index f4703dc704f565ca1ca3c09db00d010bbce58328..5d416c9eec3204c1394a903f1af64d5bca768c69 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::Gfm::ReferenceRewriter do
   let(:text) { 'some text' }
-  let(:old_project) { create(:project, name: 'old') }
-  let(:new_project) { create(:project, name: 'new') }
+  let(:old_project) { create(:empty_project, name: 'old-project') }
+  let(:new_project) { create(:empty_project, name: 'new-project') }
   let(:user) { create(:user) }
 
   before { old_project.team << [user, :reporter] }
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index 6eca33f9fee300ea4537d257829874d6dfff3801..c3016f63ebf8a24b79728b65d8ac74ad9dbb613e 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::Gfm::UploadsRewriter do
   let(:user) { create(:user) }
-  let(:old_project) { create(:project) }
-  let(:new_project) { create(:project) }
+  let(:old_project) { create(:empty_project) }
+  let(:new_project) { create(:empty_project) }
   let(:rewriter) { described_class.new(text, old_project, user) }
 
   context 'text contains links to uploads' do
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index d1f947b68500940e51248da3b45039c40720c329..3f279c2186546a1b08c19b5c25457d1d19caf870 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -3,7 +3,7 @@ require 'fileutils'
 
 describe Gitlab::Git::Hook, lib: true do
   describe "#trigger" do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:user) { create(:user) }
 
     def create_hook(name)
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 1f9c987be0bc8b17d223d075ccdae0072427151b..d48629a296d57a11de1d04041dfd9ffd506882fb 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Git::RevList, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   context "validations" do
     described_class::ALLOWED_VARIABLES.each do |var|
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 44b84afde471e00c3f5a671898dd61b1318a2d8e..116ab16ae74d2a339464cb546c23d0d0d485e291 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::GitAccess, lib: true do
   let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:actor) { user }
   let(:authentication_abilities) do
@@ -88,7 +88,7 @@ describe Gitlab::GitAccess, lib: true do
       end
 
       context 'when project is public' do
-        let(:public_project) { create(:project, :public) }
+        let(:public_project) { create(:project, :public, :repository) }
         let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
         subject { guest_access.check('git-upload-pack', '_any') }
 
@@ -124,19 +124,19 @@ describe Gitlab::GitAccess, lib: true do
 
         context 'when unauthorized' do
           context 'from public project' do
-            let(:project) { create(:project, :public) }
+            let(:project) { create(:project, :public, :repository) }
 
             it { expect(subject).to be_allowed }
           end
 
           context 'from internal project' do
-            let(:project) { create(:project, :internal) }
+            let(:project) { create(:project, :internal, :repository) }
 
             it { expect(subject).not_to be_allowed }
           end
 
           context 'from private project' do
-            let(:project) { create(:project, :private) }
+            let(:project) { create(:project, :private, :repository) }
 
             it { expect(subject).not_to be_allowed }
           end
@@ -148,7 +148,7 @@ describe Gitlab::GitAccess, lib: true do
       let(:authentication_abilities) { build_authentication_abilities }
 
       describe 'owner' do
-        let(:project) { create(:project, namespace: user.namespace) }
+        let(:project) { create(:project, :repository, namespace: user.namespace) }
 
         context 'pull code' do
           it { expect(subject).to be_allowed }
@@ -209,7 +209,13 @@ describe Gitlab::GitAccess, lib: true do
         stub_git_hooks
         project.repository.add_branch(user, unprotected_branch, 'feature')
         target_branch = project.repository.lookup('feature')
-        source_branch = project.repository.commit_file(user, FFaker::InternetSE.login_user_name, FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.sentence, unprotected_branch, false)
+        source_branch = project.repository.commit_file(
+          user,
+          FFaker::InternetSE.login_user_name,
+          FFaker::HipsterIpsum.paragraph,
+          message: FFaker::HipsterIpsum.sentence,
+          branch_name: unprotected_branch,
+          update: false)
         rugged = project.repository.rugged
         author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
 
@@ -364,19 +370,19 @@ describe Gitlab::GitAccess, lib: true do
 
     context 'when unauthorized' do
       context 'to public project' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
 
         it { expect(subject).not_to be_allowed }
       end
 
       context 'to internal project' do
-        let(:project) { create(:project, :internal) }
+        let(:project) { create(:project, :internal, :repository) }
 
         it { expect(subject).not_to be_allowed }
       end
 
       context 'to private project' do
-        let(:project) { create(:project) }
+        let(:project) { create(:project, :private, :repository) }
 
         it { expect(subject).not_to be_allowed }
       end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index a5d172233cc33aa99241fe82ef1663b2d5c7bd25..4a0cdc6887ec1c3175635b7761b89791cdcd0fbd 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::GitAccessWiki, lib: true do
   let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:user) { create(:user) }
   let(:authentication_abilities) do
     [
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
index 462caa5b5fe8a6bd4c23f1c12b46508fa308f8a7..36e7d739f7ec632a17db26508cc9391b9a6f06f0 100644
--- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::BranchFormatter, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:commit) { create(:commit, project: project) }
   let(:repo) { double }
   let(:raw) do
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index c520a9c53ad7d4253b9acf6acecd6d2037e3e3e6..e6e33d3686af94167fa8629f93c73d6796ae1a5d 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::CommentFormatter, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:octocat) { double(id: 123456, login: 'octocat') }
   let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
   let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index e31ed9c1fa06d9c6415499739ee9550d9d73dc7b..eec1fabab547d8033985001f6e677178bd06e2f3 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::IssueFormatter, lib: true do
-  let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+  let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) }
   let(:octocat) { double(id: 123456, login: 'octocat') }
   let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
   let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
index 8098754d735f775b321b88710528ca8933fd2024..10449ef5fcbbee3eed699a528d41335eaefb8e62 100644
--- a/spec/lib/gitlab/github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::LabelFormatter, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
 
   subject { described_class.new(project, raw) }
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 2b3256edcb239dac62ad450eac9d7225afd685de..90947ff4707d375a21e14d265b3adbba8545fc05 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:source_sha) { create(:commit, project: project).id }
   let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
   let(:repository) { double(id: 1, fork: false) }
diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb
index 793128c6ab92b271ec401950f9b786fd8d5a6e9b..13b15e669ab5af6a0fbd58932c0e22bb409182c2 100644
--- a/spec/lib/gitlab/github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::ReleaseFormatter, lib: true do
-  let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+  let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) }
   let(:octocat) { double(id: 123456, login: 'octocat') }
   let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
 
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 097861fd34db95f76b64710e5e1acc351fb527be..ccaa88a5c798f8b55e511cc1c2179afff83f3742 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do
       'user_map' => { 'thilo...' => "@#{mapped_user.username}" }
     }
   end
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
 
   subject { described_class.new(project) }
 
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index f5c064303ad11292f9323967d05120b61a8b9061..abb5a26060f72c7ec303786c5e8b13f4dc2ea13f 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Graphs::Commits, lib: true do
-  let!(:project) { create(:project, :public, :empty_repo) }
+  let!(:project) { create(:empty_project, :public) }
 
   let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
   let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)}
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index fc021416d92472f38def5cbe5c21fc39ab0958e7..e177d8831587cf7150863c8db97c986b691f6911 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Highlight, lib: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
   let(:commit) { project.commit(sample_commit.id) }
 
@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do
       Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
     end
 
-    it 'should properly highlight all the lines' do
+    it 'highlights all the lines properly' do
       expect(lines[4]).to eq(%Q{<span id="LC5" class="line">  <span class="kp">extend</span> <span class="nb">self</span></span>\n})
       expect(lines[21]).to eq(%Q{<span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
       expect(lines[26]).to eq(%Q{<span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7fb6829f582f2514459b6644c05a0af35ade83a3..20241d4d63e8b569c34f0ff72505d33cd83708cd 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -52,6 +52,7 @@ snippets:
 - project
 - notes
 - award_emoji
+- user_agent_detail
 releases:
 - project
 project_members:
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index d6409a2955086b6ac6e78f9d1ececdb0223a6fe8..53f7d244d88bd81b5d9f18518cf60d4cb1ba9d32 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::ImportExport, services: true do
   describe 'export filename' do
-    let(:project) { create(:project, :public, path: 'project-path') }
+    let(:project) { create(:empty_project, :public, path: 'project-path') }
 
     it 'contains the project path' do
       expect(described_class.export_filename(project: project)).to include(project.path)
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 1cb02f8e3182c12899f3204fdf17eb84686d03cf..f2cb028206f17033e94af35d483dfedfcafba7bf 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::ImportExport::MembersMapper, services: true do
   describe 'map members' do
-    let(:user) { create(:user, authorized_projects_populated: true) }
-    let(:project) { create(:project, :public, name: 'searchable_project') }
+    let(:user) { create(:admin, authorized_projects_populated: true) }
+    let(:project) { create(:empty_project, :public, name: 'searchable_project') }
     let(:user2) { create(:user, authorized_projects_populated: true) }
     let(:exported_user_id) { 99 }
     let(:exported_members) do
@@ -24,7 +24,7 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
            {
              "id" => exported_user_id,
              "email" => user2.email,
-             "username" => user2.username
+             "username" => 'test'
            }
        },
        {
@@ -48,6 +48,10 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
         exported_members: exported_members, user: user, project: project)
     end
 
+    it 'includes the exported user ID in the map' do
+      expect(members_mapper.map.keys).to include(exported_user_id)
+    end
+
     it 'maps a project member' do
       expect(members_mapper.map[exported_user_id]).to eq(user2.id)
     end
@@ -56,12 +60,6 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
       expect(members_mapper.map[-1]).to eq(user.id)
     end
 
-    it 'updates missing author IDs on missing project member' do
-      members_mapper.map[-1]
-
-      expect(members_mapper.missing_author_ids.first).to eq(-1)
-    end
-
     it 'has invited members with no user' do
       members_mapper.map
 
@@ -74,5 +72,49 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
       expect(user.authorized_project?(project)).to be true
       expect(user2.authorized_project?(project)).to be true
     end
+
+    context 'user is not an admin' do
+      let(:user) { create(:user, authorized_projects_populated: true) }
+
+      it 'does not map a project member' do
+        expect(members_mapper.map[exported_user_id]).to eq(user.id)
+      end
+
+      it 'defaults to importer project member if it does not exist' do
+        expect(members_mapper.map[-1]).to eq(user.id)
+      end
+    end
+
+    context 'chooses the one with an email first' do
+      let(:user3) { create(:user, username: 'test') }
+
+      it 'maps the project member that has a matching email first' do
+        expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+      end
+    end
+
+    context 'importer same as group member' do
+      let(:user2) { create(:admin, authorized_projects_populated: true) }
+      let(:group) { create(:group) }
+      let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) }
+      let(:members_mapper) do
+        described_class.new(
+          exported_members: exported_members, user: user2, project: project)
+      end
+
+      before do
+        group.add_users([user, user2], GroupMember::DEVELOPER)
+      end
+
+      it 'maps the project member' do
+        expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+      end
+
+      it 'maps the project member if it already exists' do
+        project.add_master(user2)
+
+        expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 2c0750c3377ee3ef78ce99d4b10243be1999d603..2e9f60432b4577b156a9bd94d2d71a4e0798a440 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6980,12 +6980,17 @@
         }
       ]
     }
-  ],
-  "variables": [
-
   ],
   "triggers": [
-
+    {
+      "id": 123,
+      "token": "cdbfasdf44a5958c83654733449e585",
+      "project_id": null,
+      "deleted_at": null,
+      "created_at": "2017-01-16T15:25:28.637Z",
+      "updated_at": "2017-01-16T15:25:28.637Z",
+      "gl_project_id": 123
+    }
   ],
   "deploy_keys": [
 
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 4b07fa53bf509b934bc015cb602f8bb0f4b4f497..40d7d59f03bba53ca3808972c1b64dca8c47fecd 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -197,6 +197,20 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
           expect(restored_project_json).to be true
         end
       end
+
+      context 'tokens are regenerated' do
+        before do
+          restored_project_json
+        end
+
+        it 'has a new CI trigger token' do
+          expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+        end
+
+        it 'has a new CI build token' do
+          expect(Ci::Build.where(token: 'abcd')).to be_empty
+        end
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index c8bba553558e50d656c97770d47ab194bd850a76..d480c3821ec1ef7df0808302bae40f89ec3c401d 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -151,6 +151,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
 
     project = create(:project,
                      :public,
+                     :repository,
                      issues: [issue],
                      snippets: [snippet],
                      releases: [release],
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index 3aa492a8ab14d7ae54df04a6e8b0a69f17a1f048..57e412b0cef0ed28be6ff5ff7973772c36d21443 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::ImportExport::RelationFactory, lib: true do
   let(:project) { create(:empty_project) }
   let(:members_mapper) { double('members_mapper').as_null_object }
-  let(:user) { create(:user) }
+  let(:user) { create(:admin) }
   let(:created_object) do
     described_class.create(relation_sym: relation_sym,
                            relation_hash: relation_hash,
@@ -55,8 +55,8 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
       expect(created_object.project_id).to eq(project.id)
     end
 
-    it 'has a token' do
-      expect(created_object.token).to eq(token)
+    it 'has a nil token' do
+      expect(created_object.token).to eq(nil)
     end
 
     context 'original service exists' do
@@ -122,4 +122,71 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
       expect(created_object.values).not_to include(99)
     end
   end
+
+  context 'Notes user references' do
+    let(:relation_sym) { :notes }
+    let(:new_user) { create(:user) }
+    let(:exported_member) do
+      {
+        "id" => 111,
+        "access_level" => 30,
+        "source_id" => 1,
+        "source_type" => "Project",
+        "user_id" => 3,
+        "notification_level" => 3,
+        "created_at" => "2016-11-18T09:29:42.634Z",
+        "updated_at" => "2016-11-18T09:29:42.634Z",
+        "user" => {
+          "id" => 999,
+          "email" => new_user.email,
+          "username" => new_user.username
+        }
+      }
+    end
+
+    let(:relation_hash) do
+      {
+        "id" => 4947,
+        "note" => "merged",
+        "noteable_type" => "MergeRequest",
+        "author_id" => 999,
+        "created_at" => "2016-11-18T09:29:42.634Z",
+        "updated_at" => "2016-11-18T09:29:42.634Z",
+        "project_id" => 1,
+        "attachment" => {
+          "url" => nil
+        },
+        "noteable_id" => 377,
+        "system" => true,
+        "author" => {
+          "name" => "Administrator"
+        },
+        "events" => [
+
+        ]
+      }
+    end
+
+    let(:members_mapper) do
+      Gitlab::ImportExport::MembersMapper.new(
+        exported_members: [exported_member],
+        user: user,
+        project: project)
+    end
+
+    it 'maps the right author to the imported note' do
+      expect(created_object.author).to eq(new_user)
+    end
+  end
+
+  context 'encrypted attributes' do
+    let(:relation_sym) { 'Ci::Variable' }
+    let(:relation_hash) do
+      create(:ci_variable).as_json
+    end
+
+    it 'has no value for the encrypted attribute' do
+      expect(created_object.value).to be_nil
+    end
+  end
 end
diff --git a/spec/lib/gitlab/import_export/repo_bundler_spec.rb b/spec/lib/gitlab/import_export/repo_bundler_spec.rb
index 135e99bc95322ef299bb5391cc0a562ecbe6e4a4..d39ea60ff7f779e11568bbfdb73ad4c43ec4e760 100644
--- a/spec/lib/gitlab/import_export/repo_bundler_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_bundler_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::ImportExport::RepoSaver, services: true do
   describe 'bundle a project Git repo' do
     let(:user) { create(:user) }
-    let!(:project) { create(:project, :public, name: 'searchable_project') }
+    let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
     let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
     let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
     let(:bundler) { described_class.new(project: project, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 493bc2db21af108def3c04b6bb3fbac414a990e8..95b230e4f5ca9480395fad208ebc9220b4ced5fc 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -222,6 +222,7 @@ CommitStatus:
 - queued_at
 - token
 - lock_version
+- coverage_regex
 Ci::Variable:
 - id
 - project_id
diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
index b628da0f3e87a2aa39ff2448bd46179412946263..47d5d2fc150ed3f0d9f9bcdf6a31c1ec0bb191ea 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::ImportExport::WikiRepoSaver, services: true do
   describe 'bundle a wiki Git repo' do
     let(:user) { create(:user) }
-    let!(:project) { create(:project, :public, name: 'searchable_project') }
+    let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
     let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
     let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
     let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 1dcf2c0668b75f3e748a868c83022be5b65fc7af..7e951e3fcdd74bc50de478dad9ec0366fd2fcb2c 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -23,6 +23,48 @@ describe Gitlab::IncomingEmail, lib: true do
     end
   end
 
+  describe 'self.supports_wildcard?' do
+    context 'address contains the wildard placeholder' do
+      before do
+        stub_incoming_email_setting(address: 'replies+%{key}@example.com')
+      end
+
+      it 'confirms that wildcard is supported' do
+        expect(described_class.supports_wildcard?).to be_truthy
+      end
+    end
+
+    context "address doesn't contain the wildcard placeholder" do
+      before do
+        stub_incoming_email_setting(address: 'replies@example.com')
+      end
+
+      it 'returns that wildcard is not supported' do
+        expect(described_class.supports_wildcard?).to be_falsey
+      end
+    end
+
+    context 'address is not set' do
+      before do
+        stub_incoming_email_setting(address: nil)
+      end
+
+      it 'returns that wildard is not supported' do
+        expect(described_class.supports_wildcard?).to be_falsey
+      end
+    end
+  end
+
+  context 'self.unsubscribe_address' do
+    before do
+      stub_incoming_email_setting(address: 'replies+%{key}@example.com')
+    end
+
+    it 'returns the address with interpolated reply key and unsubscribe suffix' do
+      expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com')
+    end
+  end
+
   context "self.reply_address" do
     before do
       stub_incoming_email_setting(address: "replies+%{key}@example.com")
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..780f5b1f8d7a4abaa49c42ac33f20ee5125e138e
--- /dev/null
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::JobWaiter do
+  describe '#wait' do
+    let(:waiter) { described_class.new(%w(a)) }
+    it 'returns when all jobs have been completed' do
+      expect(Gitlab::SidekiqStatus).to receive(:all_completed?).with(%w(a)).
+        and_return(true)
+
+      expect(waiter).not_to receive(:sleep)
+
+      waiter.wait
+    end
+
+    it 'sleeps between checking the job statuses' do
+      expect(Gitlab::SidekiqStatus).to receive(:all_completed?).
+        with(%w(a)).
+        and_return(false, true)
+
+      expect(waiter).to receive(:sleep).with(described_class::INTERVAL)
+
+      waiter.wait
+    end
+
+    it 'returns when timing out' do
+      expect(waiter).not_to receive(:sleep)
+      waiter.wait(0)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index b9d12c3c24c18dfc5ce1239d359842e810c122be..9dd997aa7dc5cb60065d9e32c852f8c487925ee9 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do
 
       it { is_expected.to be_falsey }
 
-      it 'should block user in GitLab' do
+      it 'blocks user in GitLab' do
         expect(access).to receive(:block_user).with(user, 'does not exist anymore')
 
         access.allowed?
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 14ee386dba6c26fbb9d75f7651f01febf6bd560d..92e3624a8d8258395a1b01bf996b9b894edf69e5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::ProjectSearchResults, lib: true do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:query) { 'hello world' }
 
   describe 'initialize with empty ref' do
@@ -22,6 +22,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
   end
 
   describe 'blob search' do
+    let(:project) { create(:project, :repository) }
     let(:results) { described_class.new(user, project, 'files').objects('blobs') }
 
     it 'finds by name' do
@@ -74,6 +75,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
   end
 
   describe 'confidential issues' do
+    let(:project) { create(:empty_project) }
     let(:query) { 'issue' }
     let(:author) { create(:user) }
     let(:assignee) { create(:user) }
@@ -178,4 +180,119 @@ describe Gitlab::ProjectSearchResults, lib: true do
       expect(results.objects('notes')).not_to include note
     end
   end
+
+  # Examples for commit access level test
+  #
+  # params:
+  # * search_phrase
+  # * commit
+  #
+  shared_examples 'access restricted commits' do
+    context 'when project is internal' do
+      let(:project) { create(:project, :internal, :repository) }
+
+      it 'does not search if user is not authenticated' do
+        commits = described_class.new(nil, project, search_phrase).objects('commits')
+
+        expect(commits).to be_empty
+      end
+
+      it 'searches if user is authenticated' do
+        commits = described_class.new(user, project, search_phrase).objects('commits')
+
+        expect(commits).to contain_exactly commit
+      end
+    end
+
+    context 'when project is private' do
+      let!(:creator) { create(:user, username: 'private-project-author') }
+      let!(:private_project) { create(:project, :private, :repository, creator: creator, namespace: creator.namespace) }
+      let(:team_master) do
+        user = create(:user, username: 'private-project-master')
+        private_project.team << [user, :master]
+        user
+      end
+      let(:team_reporter) do
+        user = create(:user, username: 'private-project-reporter')
+        private_project.team << [user, :reporter]
+        user
+      end
+
+      it 'does not show commit to stranger' do
+        commits = described_class.new(nil, private_project, search_phrase).objects('commits')
+
+        expect(commits).to be_empty
+      end
+
+      context 'team access' do
+        it 'shows commit to creator' do
+          commits = described_class.new(creator, private_project, search_phrase).objects('commits')
+
+          expect(commits).to contain_exactly commit
+        end
+
+        it 'shows commit to master' do
+          commits = described_class.new(team_master, private_project, search_phrase).objects('commits')
+
+          expect(commits).to contain_exactly commit
+        end
+
+        it 'shows commit to reporter' do
+          commits = described_class.new(team_reporter, private_project, search_phrase).objects('commits')
+
+          expect(commits).to contain_exactly commit
+        end
+      end
+    end
+  end
+
+  describe 'commit search' do
+    context 'by commit message' do
+      let(:project) { create(:project, :public, :repository) }
+      let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') }
+      let(:message) { 'Sorry, I did a mistake' }
+
+      it 'finds commit by message' do
+        commits = described_class.new(user, project, message).objects('commits')
+
+        expect(commits).to contain_exactly commit
+      end
+
+      it 'handles when no commit match' do
+        commits = described_class.new(user, project, 'not really an existing description').objects('commits')
+
+        expect(commits).to be_empty
+      end
+
+      it_behaves_like 'access restricted commits' do
+        let(:search_phrase) { message }
+        let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') }
+      end
+    end
+
+    context 'by commit hash' do
+      let(:project) { create(:project, :public, :repository) }
+      let(:commit) { project.repository.commit('0b4bc9a') }
+      commit_hashes = { short: '0b4bc9a', full: '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+
+      commit_hashes.each do |type, commit_hash|
+        it "shows commit by #{type} hash id" do
+          commits = described_class.new(user, project, commit_hash).objects('commits')
+
+          expect(commits).to contain_exactly commit
+        end
+      end
+
+      it 'handles not existing commit hash correctly' do
+        commits = described_class.new(user, project, 'deadbeef').objects('commits')
+
+        expect(commits).to be_empty
+      end
+
+      it_behaves_like 'access restricted commits' do
+        let(:search_phrase) { '0b4bc9a49' }
+        let(:commit) { project.repository.commit('0b4bc9a') }
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index bf0ab9635fd308d49f29f17b6cc0d9d361eb5bf2..6b689c41ef656175d65165678b998379c2d680a0 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -1,9 +1,11 @@
 require 'spec_helper'
 
 describe Gitlab::ReferenceExtractor, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
 
-  before { project.team << [project.creator, :developer] }
+  before do
+    project.team << [project.creator, :developer]
+  end
 
   subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
 
@@ -78,22 +80,27 @@ describe Gitlab::ReferenceExtractor, lib: true do
   end
 
   it 'accesses valid commits' do
+    project = create(:project, :repository) { |p| p.add_developer(p.creator) }
     commit = project.commit('master')
 
-    subject.analyze("this references commits #{commit.sha[0..6]} and 012345")
-    extracted = subject.commits
+    extractor = described_class.new(project, project.creator)
+    extractor.analyze("this references commits #{commit.sha[0..6]} and 012345")
+    extracted = extractor.commits
+
     expect(extracted.size).to eq(1)
     expect(extracted[0].sha).to eq(commit.sha)
     expect(extracted[0].message).to eq(commit.message)
   end
 
   it 'accesses valid commit ranges' do
+    project = create(:project, :repository) { |p| p.add_developer(p.creator) }
     commit = project.commit('master')
     earlier_commit = project.commit('master~2')
 
-    subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}")
+    extractor = described_class.new(project, project.creator)
+    extractor.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}")
+    extracted = extractor.commit_ranges
 
-    extracted = subject.commit_ranges
     expect(extracted.size).to eq(1)
     expect(extracted.first).to be_kind_of(CommitRange)
     expect(extracted.first.commit_from).to eq earlier_commit
@@ -102,7 +109,6 @@ describe Gitlab::ReferenceExtractor, lib: true do
 
   context 'with an external issue tracker' do
     let(:project) { create(:jira_project) }
-    subject { described_class.new(project, project.creator) }
 
     it 'returns JIRA issues for a JIRA-integrated project' do
       subject.analyze('JIRA-123 and FOOBAR-4567')
@@ -112,7 +118,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
   end
 
   context 'with a project with an underscore' do
-    let(:other_project) { create(:project, path: 'test_project') }
+    let(:other_project) { create(:empty_project, path: 'test_project') }
     let(:issue) { create(:issue, project: other_project) }
 
     before do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 9614aad3e73f9d11f7c67b823e48f4bd62b96d92..847fb977400157c5118c43d70277dee64a580425 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::SearchResults do
   let(:user) { create(:user) }
-  let!(:project) { create(:project, name: 'foo') }
+  let!(:project) { create(:empty_project, name: 'foo') }
   let!(:issue) { create(:issue, project: project, title: 'foo') }
 
   let!(:merge_request) do
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..287bf62d9bdca1f4e900491a7375bf807025f721
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqStatus::ClientMiddleware do
+  describe '#call' do
+    it 'tracks the job in Redis' do
+      expect(Gitlab::SidekiqStatus).to receive(:set).with('123')
+
+      described_class.new.
+        call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil }
+    end
+  end
+end
diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..80728197b8cf313fe960f880ca69f498a7499f12
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqStatus::ServerMiddleware do
+  describe '#call' do
+    it 'stops tracking of a job upon completion' do
+      expect(Gitlab::SidekiqStatus).to receive(:unset).with('123')
+
+      ret = described_class.new.
+        call(double(:worker), { 'jid' => '123' }, double(:queue)) { 10 }
+
+      expect(ret).to eq(10)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0aa36a3416b7167dde0bf13b0cd5745643ea203c
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqStatus do
+  describe '.set', :redis do
+    it 'stores the job ID' do
+      described_class.set('123')
+
+      key = described_class.key_for('123')
+
+      Sidekiq.redis do |redis|
+        expect(redis.exists(key)).to eq(true)
+        expect(redis.ttl(key) > 0).to eq(true)
+      end
+    end
+  end
+
+  describe '.unset', :redis do
+    it 'removes the job ID' do
+      described_class.set('123')
+      described_class.unset('123')
+
+      key = described_class.key_for('123')
+
+      Sidekiq.redis do |redis|
+        expect(redis.exists(key)).to eq(false)
+      end
+    end
+  end
+
+  describe '.all_completed?', :redis do
+    it 'returns true if all jobs have been completed' do
+      expect(described_class.all_completed?(%w(123))).to eq(true)
+    end
+
+    it 'returns false if a job has not yet been completed' do
+      described_class.set('123')
+
+      expect(described_class.all_completed?(%w(123 456))).to eq(false)
+    end
+  end
+
+  describe '.key_for' do
+    it 'returns the key for a job ID' do
+      key = described_class.key_for('123')
+
+      expect(key).to be_an_instance_of(String)
+      expect(key).to include('123')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index d2d334e64131d41034b0eec7daebf65e77f3804b..1335a2b8f3534261a63ae8c1edbb1c5a9ff0dc5a 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -4,16 +4,14 @@ describe Gitlab::Template::IssueTemplate do
   subject { described_class }
 
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
-  let(:file_path_1) { '.gitlab/issue_templates/bug.md' }
-  let(:file_path_2) { '.gitlab/issue_templates/template_test.md' }
-  let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' }
-
-  before do
-    project.add_user(user, Gitlab::Access::MASTER)
-    project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
-    project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
-    project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
+
+  let(:project) do
+    create(:project,
+      :repository,
+      create_template: {
+        user: user,
+        access: Gitlab::Access::MASTER,
+        path: 'issue_templates' })
   end
 
   describe '.all' do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index ddf68c4cf78bfc0ab8d16b706fac05e8f0af25b6..320b870309a1ce33c882082c997a6c33c43dcfca 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -4,16 +4,14 @@ describe Gitlab::Template::MergeRequestTemplate do
   subject { described_class }
 
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
-  let(:file_path_1) { '.gitlab/merge_request_templates/bug.md' }
-  let(:file_path_2) { '.gitlab/merge_request_templates/template_test.md' }
-  let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' }
-
-  before do
-    project.add_user(user, Gitlab::Access::MASTER)
-    project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
-    project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
-    project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
+
+  let(:project) do
+    create(:project,
+      :repository,
+      create_template: {
+        user: user,
+        access: Gitlab::Access::MASTER,
+        path: 'merge_request_templates' })
   end
 
   describe '.all' do
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index a826b24419a8b8998c9142b1a94e5f9a99b99490..3fe8cf43934d0e988ea475515c52ce4c74a4ea4e 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do
 
       context 'on another object' do
         it 'returns a proper URL' do
-          project = build_stubbed(:project)
+          project = build_stubbed(:empty_project)
 
           expect { described_class.build(project) }.
             to raise_error(NotImplementedError, 'No URL builder defined for Project')
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index d3c3b800b94b3683d42e0fd0a432099f87d7f6d7..369e55f61f1d490eef4aaf5b9949cc1ffe67e2fd 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -66,7 +66,8 @@ describe Gitlab::UserAccess, lib: true do
     end
 
     describe 'push to protected branch' do
-      let(:branch) { create :protected_branch, project: project }
+      let(:branch) { create :protected_branch, project: project, name: "test" }
+      let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project }
 
       it 'returns true if user is a master' do
         project.team << [user, :master]
@@ -85,6 +86,12 @@ describe Gitlab::UserAccess, lib: true do
 
         expect(access.can_push_to_branch?(branch.name)).to be_falsey
       end
+
+      it 'returns true if branch does not exist and user has permission to merge' do
+        project.team << [user, :developer]
+
+        expect(access.can_push_to_branch?(not_existing_branch.name)).to be_truthy
+      end
     end
 
     describe 'push to protected branch if allowed for developers' do
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index 888ab80cad57373733deeed242f7277d3d06dfc6..e9d4af54389aac41a73237ea89b0b9d3709265b6 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::View::Presenter::Delegated do
-  let(:project) { double(:project, bar: 'baz') }
+  let(:project) { double(:project, user: 'John Doe') }
   let(:presenter_class) do
     Class.new(described_class)
   end
@@ -12,10 +12,14 @@ describe Gitlab::View::Presenter::Delegated do
 
   describe '#initialize' do
     it 'takes arbitrary key/values and exposes them' do
-      presenter = presenter_class.new(project, user: 'user', foo: 'bar')
+      presenter = presenter_class.new(project, current_user: 'Jane Doe')
 
-      expect(presenter.user).to eq('user')
-      expect(presenter.foo).to eq('bar')
+      expect(presenter.current_user).to eq('Jane Doe')
+    end
+
+    it 'raise an error if the presentee already respond to method' do
+      expect { presenter_class.new(project, user: 'Jane Doe') }.
+        to raise_error Gitlab::View::Presenter::CannotOverrideMethodError
     end
   end
 
@@ -23,7 +27,7 @@ describe Gitlab::View::Presenter::Delegated do
     it 'forwards missing methods to subject' do
       presenter = presenter_class.new(project)
 
-      expect(presenter.bar).to eq('baz')
+      expect(presenter.user).to eq('John Doe')
     end
   end
 end
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 55c5ecbf92f79dad70d41671457e702269ab2dd5..70d2e22b48f08d877f71b89ed5bd38d588940c16 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -22,13 +22,6 @@ describe Gitlab::View::Presenter::Factory do
   end
 
   describe '#fabricate!' do
-    it 'exposes given params' do
-      presenter = described_class.new(build, user: 'user', foo: 'bar').fabricate!
-
-      expect(presenter.user).to eq('user')
-      expect(presenter.foo).to eq('bar')
-    end
-
     it 'detects the presenter based on the given subject' do
       presenter = described_class.new(build).fabricate!
 
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
index b489bdf198116faa1a60f8b899f63bf31e06ef68..1795ed2405bc7a33fba7d36cc4ca9e6d46406de0 100644
--- a/spec/lib/gitlab/view/presenter/simple_spec.rb
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::View::Presenter::Simple do
-  let(:project) { double(:project) }
+  let(:project) { double(:project, user: 'John Doe') }
   let(:presenter_class) do
     Class.new(described_class)
   end
@@ -12,10 +12,15 @@ describe Gitlab::View::Presenter::Simple do
 
   describe '#initialize' do
     it 'takes arbitrary key/values and exposes them' do
-      presenter = presenter_class.new(project, user: 'user', foo: 'bar')
+      presenter = presenter_class.new(project, current_user: 'Jane Doe')
 
-      expect(presenter.user).to eq('user')
-      expect(presenter.foo).to eq('bar')
+      expect(presenter.current_user).to eq('Jane Doe')
+    end
+
+    it 'override the presentee attributes' do
+      presenter = presenter_class.new(project, user: 'Jane Doe')
+
+      expect(presenter.user).to eq('Jane Doe')
     end
   end
 
@@ -23,7 +28,7 @@ describe Gitlab::View::Presenter::Simple do
     it 'does not forward missing methods to subject' do
       presenter = presenter_class.new(project)
 
-      expect { presenter.foo }.to raise_error(NoMethodError)
+      expect { presenter.user }.to raise_error(NoMethodError)
     end
   end
 end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 4b1cd466677d1966df68fcacde63332ac5d7e44c..7dd4d76d1a39e27a7b32ad8f3e72f99051d21c6f 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::Workhorse, lib: true do
-  let(:project)    { create(:project) }
+  let(:project)    { create(:project, :repository) }
   let(:repository) { project.repository }
 
   def decode_workhorse_header(array)
diff --git a/spec/lib/light_url_builder_spec.rb b/spec/lib/light_url_builder_spec.rb
index a826b24419a8b8998c9142b1a94e5f9a99b99490..3fe8cf43934d0e988ea475515c52ce4c74a4ea4e 100644
--- a/spec/lib/light_url_builder_spec.rb
+++ b/spec/lib/light_url_builder_spec.rb
@@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do
 
       context 'on another object' do
         it 'returns a proper URL' do
-          project = build_stubbed(:project)
+          project = build_stubbed(:empty_project)
 
           expect { described_class.build(project) }.
             to raise_error(NotImplementedError, 'No URL builder defined for Project')
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index f227926f39c83bbddcc8fb111bf958685fa35195..5892f3481a4f5a4db90cd174d1de98bc4bb44224 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe RepositoryCache, lib: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:backend) { double('backend').as_null_object }
   let(:cache) { RepositoryCache.new('example', project.id, backend) }
 
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 1bdf005c8237dbaf87f9f8167bb2667dcccc4e5c..2f4a33a1868d92f7a2f18734e3eedbd23cd69ad1 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Ability, lib: true do
   describe '.can_edit_note?' do
     let(:project) { create(:empty_project) }
-    let!(:note) { create(:note_on_issue, project: project) }
+    let(:note) { create(:note_on_issue, project: project) }
 
     context 'using an anonymous user' do
       it 'returns false' do
@@ -60,7 +60,7 @@ describe Ability, lib: true do
   describe '.users_that_can_read_project' do
     context 'using a public project' do
       it 'returns all the users' do
-        project = create(:project, :public)
+        project = create(:empty_project, :public)
         user = build(:user)
 
         expect(described_class.users_that_can_read_project([user], project)).
@@ -69,7 +69,7 @@ describe Ability, lib: true do
     end
 
     context 'using an internal project' do
-      let(:project) { create(:project, :internal) }
+      let(:project) { create(:empty_project, :internal) }
 
       it 'returns users that are administrators' do
         user = build(:user, admin: true)
@@ -120,7 +120,7 @@ describe Ability, lib: true do
     end
 
     context 'using a private project' do
-      let(:project) { create(:project, :private) }
+      let(:project) { create(:empty_project, :private) }
 
       it 'returns users that are administrators' do
         user = build(:user, admin: true)
@@ -171,6 +171,33 @@ describe Ability, lib: true do
     end
   end
 
+  describe '.users_that_can_read_personal_snippet' do
+    def users_for_snippet(snippet)
+      described_class.users_that_can_read_personal_snippet(users, snippet)
+    end
+
+    let(:users)  { create_list(:user, 3) }
+    let(:author) { users[0] }
+
+    it 'private snippet is readable only by its author' do
+      snippet = create(:personal_snippet, :private, author: author)
+
+      expect(users_for_snippet(snippet)).to match_array([author])
+    end
+
+    it 'internal snippet is readable by all registered users' do
+      snippet = create(:personal_snippet, :public, author: author)
+
+      expect(users_for_snippet(snippet)).to match_array(users)
+    end
+
+    it 'public snippet is readable by all users' do
+      snippet = create(:personal_snippet, :public, author: author)
+
+      expect(users_for_snippet(snippet)).to match_array(users)
+    end
+  end
+
   describe '.issues_readable_by_user' do
     context 'with an admin user' do
       it 'returns all given issues' do
@@ -220,7 +247,7 @@ describe Ability, lib: true do
   end
 
   describe '.project_disabled_features_rules' do
-    let(:project) { create(:project,  wiki_access_level: ProjectFeature::DISABLED) }
+    let(:project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED) }
 
     subject { described_class.allowed(project.owner, project) }
 
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f031876e81244715d770212ba904c6cb5ead0fc1..4080092405d14d06d6992a45409836b139301752 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Ci::Build, :models do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:build) { create(:ci_build, pipeline: pipeline) }
   let(:test_trace) { 'This is a test' }
 
@@ -221,6 +221,47 @@ describe Ci::Build, :models do
     end
   end
 
+  describe '#coverage_regex' do
+    subject { build.coverage_regex }
+
+    context 'when project has build_coverage_regex set' do
+      let(:project_regex) { '\(\d+\.\d+\) covered' }
+
+      before do
+        project.build_coverage_regex = project_regex
+      end
+
+      context 'and coverage_regex attribute is not set' do
+        it { is_expected.to eq(project_regex) }
+      end
+
+      context 'but coverage_regex attribute is also set' do
+        let(:build_regex) { 'Code coverage: \d+\.\d+' }
+
+        before do
+          build.coverage_regex = build_regex
+        end
+
+        it { is_expected.to eq(build_regex) }
+      end
+    end
+
+    context 'when neither project nor build has coverage regex set' do
+      it { is_expected.to be_nil }
+    end
+  end
+
+  describe '#update_coverage' do
+    context "regarding coverage_regex's value," do
+      it "saves the correct extracted coverage value" do
+        build.coverage_regex = '\(\d+.\d+\%\) covered'
+        allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' }
+        expect(build).to receive(:update_attributes).with(coverage: 98.29) { true }
+        expect(build.update_coverage).to be true
+      end
+    end
+  end
+
   describe 'deployment' do
     describe '#last_deployment' do
       subject { build.last_deployment }
@@ -443,11 +484,11 @@ describe Ci::Build, :models do
         let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
         subject { build.erased? }
 
-        context 'build has not been erased' do
+        context 'job has not been erased' do
           it { is_expected.to be_falsey }
         end
 
-        context 'build has been erased' do
+        context 'job has been erased' do
           before do
             build.erase
           end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index d1aee27057abe3cd2a100d6111301abd4fa63aa6..426be74cd0209865a2f6ee1541bf580e39bb30c4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -122,55 +122,80 @@ describe Ci::Pipeline, models: true do
     end
   end
 
-  describe '#stages' do
+  describe 'pipeline stages' do
     before do
-      create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success')
-      create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed')
-      create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running')
-      create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success')
-    end
-
-    subject { pipeline.stages }
-
-    context 'stages list' do
-      it 'returns ordered list of stages' do
-        expect(subject.map(&:name)).to eq(%w[build test deploy])
+      create(:commit_status, pipeline: pipeline,
+                             stage: 'build',
+                             name: 'linux',
+                             stage_idx: 0,
+                             status: 'success')
+
+      create(:commit_status, pipeline: pipeline,
+                             stage: 'build',
+                             name: 'mac',
+                             stage_idx: 0,
+                             status: 'failed')
+
+      create(:commit_status, pipeline: pipeline,
+                             stage: 'deploy',
+                             name: 'staging',
+                             stage_idx: 2,
+                             status: 'running')
+
+      create(:commit_status, pipeline: pipeline,
+                             stage: 'test',
+                             name: 'rspec',
+                             stage_idx: 1,
+                             status: 'success')
+    end
+
+    describe '#stages' do
+      subject { pipeline.stages }
+
+      context 'stages list' do
+        it 'returns ordered list of stages' do
+          expect(subject.map(&:name)).to eq(%w[build test deploy])
+        end
       end
-    end
 
-    it 'returns a valid number of stages' do
-      expect(pipeline.stages_count).to eq(3)
-    end
+      context 'stages with statuses' do
+        let(:statuses) do
+          subject.map { |stage| [stage.name, stage.status] }
+        end
 
-    it 'returns a valid names of stages' do
-      expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
-    end
+        it 'returns list of stages with correct statuses' do
+          expect(statuses).to eq([['build', 'failed'],
+                                  ['test', 'success'],
+                                  ['deploy', 'running']])
+        end
 
-    context 'stages with statuses' do
-      let(:statuses) do
-        subject.map do |stage|
-          [stage.name, stage.status]
+        context 'when commit status  is retried' do
+          before do
+            create(:commit_status, pipeline: pipeline,
+                                   stage: 'build',
+                                   name: 'mac',
+                                   stage_idx: 0,
+                                   status: 'success')
+          end
+
+          it 'ignores the previous state' do
+            expect(statuses).to eq([['build', 'success'],
+                                    ['test', 'success'],
+                                    ['deploy', 'running']])
+          end
         end
       end
+    end
 
-      it 'returns list of stages with statuses' do
-        expect(statuses).to eq([['build', 'failed'],
-                                ['test', 'success'],
-                                ['deploy', 'running']
-                               ])
+    describe '#stages_count' do
+      it 'returns a valid number of stages' do
+        expect(pipeline.stages_count).to eq(3)
       end
+    end
 
-      context 'when build is retried' do
-        before do
-          create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success')
-        end
-
-        it 'ignores the previous state' do
-          expect(statuses).to eq([['build', 'success'],
-                                  ['test', 'success'],
-                                  ['deploy', 'running']
-                                 ])
-        end
+    describe '#stages_name' do
+      it 'returns a valid names of stages' do
+        expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
       end
     end
   end
@@ -259,7 +284,7 @@ describe Ci::Pipeline, models: true do
     end
 
     describe 'merge request metrics' do
-      let(:project) { FactoryGirl.create :project }
+      let(:project) { create(:project, :repository) }
       let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
       let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
 
@@ -314,7 +339,7 @@ describe Ci::Pipeline, models: true do
   end
 
   context 'with non-empty project' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     let(:pipeline) do
       create(:ci_pipeline,
@@ -865,7 +890,7 @@ describe Ci::Pipeline, models: true do
   end
 
   describe "#merge_requests" do
-    let(:project) { FactoryGirl.create :project }
+    let(:project) { create(:project, :repository) }
     let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
 
     it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
@@ -931,7 +956,7 @@ describe Ci::Pipeline, models: true do
   end
 
   describe 'notifications when pipeline success or failed' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     let(:pipeline) do
       create(:ci_pipeline,
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 6283673d7ae2098685a8779e5fbc5f6986718b7b..f8513ac8b1c1ba6d134a670ba40313c4ee95b7aa 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -91,8 +91,7 @@ describe Ci::Runner, models: true do
   end
 
   describe '#can_pick?' do
-    let(:project) { create(:project) }
-    let(:pipeline) { create(:ci_pipeline, project: project) }
+    let(:pipeline) { create(:ci_pipeline) }
     let(:build) { create(:ci_build, pipeline: pipeline) }
     let(:runner) { create(:ci_runner) }
 
@@ -340,8 +339,8 @@ describe Ci::Runner, models: true do
 
   describe '.assignable_for' do
     let(:runner) { create(:ci_runner) }
-    let(:project) { create(:project) }
-    let(:another_project) { create(:project) }
+    let(:project) { create(:empty_project) }
+    let(:another_project) { create(:empty_project) }
 
     before do
       project.runners << runner
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 742bedb37e49620f9ed9868e8754a5c994350787..c4a9743a4e245b8d6acd96ed61d652d5a2fac15e 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -142,6 +142,78 @@ describe Ci::Stage, models: true do
     end
   end
 
+  describe '#success?' do
+    context 'when stage is successful' do
+      before do
+        create_job(:ci_build, status: :success)
+        create_job(:generic_commit_status, status: :success)
+      end
+
+      it 'is successful' do
+        expect(stage).to be_success
+      end
+    end
+
+    context 'when stage is not successful' do
+      before do
+        create_job(:ci_build, status: :failed)
+        create_job(:generic_commit_status, status: :success)
+      end
+
+      it 'is not successful' do
+        expect(stage).not_to be_success
+      end
+    end
+  end
+
+  describe '#has_warnings?' do
+    context 'when stage has warnings' do
+      context 'when using memoized warnings flag' do
+        context 'when there are warnings' do
+          let(:stage) { build(:ci_stage, warnings: true) }
+
+          it 'has memoized warnings' do
+            expect(stage).not_to receive(:statuses)
+            expect(stage).to have_warnings
+          end
+        end
+
+        context 'when there are no warnings' do
+          let(:stage) { build(:ci_stage, warnings: false) }
+
+          it 'has memoized warnings' do
+            expect(stage).not_to receive(:statuses)
+            expect(stage).not_to have_warnings
+          end
+        end
+      end
+
+      context 'when calculating warnings from statuses' do
+        before do
+          create(:ci_build, :failed, :allowed_to_fail,
+                 stage: stage_name, pipeline: pipeline)
+        end
+
+        it 'has warnings calculated from statuses' do
+          expect(stage).to receive(:statuses).and_call_original
+          expect(stage).to have_warnings
+        end
+      end
+    end
+
+    context 'when stage does not have warnings' do
+      before do
+        create(:ci_build, :success, stage: stage_name,
+                                    pipeline: pipeline)
+      end
+
+      it 'does not have warnings calculated from statuses' do
+        expect(stage).to receive(:statuses).and_call_original
+        expect(stage).not_to have_warnings
+      end
+    end
+  end
+
   def create_job(type, status: 'success', stage: stage_name)
     create(type, pipeline: pipeline, stage: stage, status: status)
   end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 30782ca75a0639a7158e1b7c643aaf8ef44ca278..e4bddf670964e5101629134fe607cf36b4cecc49 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -7,7 +7,7 @@ describe CommitRange, models: true do
     it { is_expected.to include_module(Referable) }
   end
 
-  let!(:project) { create(:project, :public) }
+  let!(:project) { create(:project, :public, :repository) }
   let!(:commit1) { project.commit("HEAD~2") }
   let!(:commit2) { project.commit }
 
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 0d425ab7fd4c5cad1a8de8eeb65584f9d024ca45..32f9366a14cc1de4d92a7bb7c5d7f580951549e1 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Commit, models: true do
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:commit)  { project.commit }
 
   describe 'modules' do
@@ -34,7 +34,7 @@ describe Commit, models: true do
   end
 
   describe '#to_reference' do
-    let(:project) { create(:project, path: 'sample-project') }
+    let(:project) { create(:project, :repository, path: 'sample-project') }
     let(:commit)  { project.commit }
 
     it 'returns a String reference to the object' do
@@ -42,13 +42,13 @@ describe Commit, models: true do
     end
 
     it 'supports a cross-project reference' do
-      another_project = build(:project, name: 'another-project', namespace: project.namespace)
+      another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace)
       expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}"
     end
   end
 
   describe '#reference_link_text' do
-    let(:project) { create(:project, path: 'sample-project') }
+    let(:project) { create(:project, :repository, path: 'sample-project') }
     let(:commit)  { project.commit }
 
     it 'returns a String reference to the object' do
@@ -56,7 +56,7 @@ describe Commit, models: true do
     end
 
     it 'supports a cross-project reference' do
-      another_project = build(:project, name: 'another-project', namespace: project.namespace)
+      another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace)
       expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}"
     end
   end
@@ -131,7 +131,7 @@ eos
 
   describe '#closes_issues' do
     let(:issue) { create :issue, project: project }
-    let(:other_project) { create :project, :public }
+    let(:other_project) { create(:empty_project, :public) }
     let(:other_issue) { create :issue, project: other_project }
     let(:commiter) { create :user }
 
@@ -154,7 +154,7 @@ eos
   end
 
   it_behaves_like 'a mentionable' do
-    subject { create(:project).commit }
+    subject { create(:project, :repository).commit }
 
     let(:author) { create(:user, email: subject.author_email) }
     let(:backref_text) { "commit #{subject.id}" }
@@ -351,4 +351,22 @@ eos
       expect(commit).not_to be_work_in_progress
     end
   end
+
+  describe '.valid_hash?' do
+    it 'checks hash contents' do
+      expect(described_class.valid_hash?('abcdef01239ABCDEF')).to be true
+      expect(described_class.valid_hash?("abcdef01239ABCD\nEF")).to be false
+      expect(described_class.valid_hash?(' abcdef01239ABCDEF ')).to be false
+      expect(described_class.valid_hash?('Gabcdef01239ABCDEF')).to be false
+      expect(described_class.valid_hash?('gabcdef01239ABCDEF')).to be false
+      expect(described_class.valid_hash?('-abcdef01239ABCDEF')).to be false
+    end
+
+    it 'checks hash length' do
+      expect(described_class.valid_hash?('a' * 6)).to be false
+      expect(described_class.valid_hash?('a' * 7)).to be true
+      expect(described_class.valid_hash?('a' * 40)).to be true
+      expect(described_class.valid_hash?('a' * 41)).to be false
+    end
+  end
 end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 64ea607eb95a334f2d5e5a71aef55b219adfaeca..bf4394f7d5b758644934ab3478cd9a6c0739704c 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe CommitStatus, models: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   let(:pipeline) do
     create(:ci_pipeline, project: project, sha: project.commit.id)
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 49ab3c4b6e99ef2fe000afd3ebf88c28a4d0cce6..da003dbf794c2542de875984f99e589fb32da52b 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Compare, models: true do
   include RepoHelpers
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:project, :public, :repository) }
   let(:commit)  { project.commit }
 
   let(:start_commit) { sample_image_commit }
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 4d0f51fe82a3ec5778e5c0fc96d7d6e3b1dccff4..dbfe3cd2d3631e16d6676c5bf10a733a78b1f9cf 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -219,4 +219,10 @@ describe HasStatus do
       end
     end
   end
+
+  describe '::DEFAULT_STATUS' do
+    it 'is a status created' do
+      expect(described_class::DEFAULT_STATUS).to eq 'created'
+    end
+  end
 end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index d7d31892e126b90534124d1d58ac2875015f4442..545a11912e37c0805fa6165a03fbd5048905849f 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -301,7 +301,7 @@ describe Issue, "Issuable" do
   end
 
   describe '#labels_array' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:bug) { create(:label, project: project, title: 'bug') }
     let(:issue) { create(:issue, project: project) }
 
@@ -315,7 +315,7 @@ describe Issue, "Issuable" do
   end
 
   describe '#user_notes_count' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:issue1) { create(:issue, project: project) }
     let(:issue2) { create(:issue, project: project) }
 
@@ -359,7 +359,7 @@ describe Issue, "Issuable" do
   end
 
   describe ".with_label" do
-    let(:project) { create(:project, :public) }
+    let(:project) { create(:empty_project, :public) }
     let(:bug) { create(:label, project: project, title: 'bug') }
     let(:feature) { create(:label, project: project, title: 'feature') }
     let(:enhancement) { create(:label, project: project, title: 'enhancement') }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 132858950d54c5ac1464208c514c34ec6951597f..2092576e98101e21d332a050c05e3e6c9063732b 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -13,7 +13,7 @@ describe Mentionable do
   end
 
   describe 'references' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:mentionable) { Example.new }
 
     it 'excludes JIRA references' do
@@ -30,12 +30,20 @@ describe Issue, "Mentionable" do
   describe '#mentioned_users' do
     let!(:user) { create(:user, username: 'stranger') }
     let!(:user2) { create(:user, username: 'john') }
-    let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") }
+    let!(:user3) { create(:user, username: 'jim') }
+    let(:issue) { create(:issue, description: "#{user.to_reference} mentioned") }
 
     subject { issue.mentioned_users }
 
-    it { is_expected.to include(user) }
-    it { is_expected.not_to include(user2) }
+    it { expect(subject).to contain_exactly(user) }
+
+    context 'when a note on personal snippet' do
+      let!(:note) { create(:note_on_personal_snippet, note: "#{user.to_reference} mentioned #{user3.to_reference}") }
+
+      subject { note.mentioned_users }
+
+      it { expect(subject).to contain_exactly(user, user3) }
+    end
   end
 
   describe '#referenced_mentionables' do
@@ -75,13 +83,13 @@ describe Issue, "Mentionable" do
   end
 
   describe '#create_cross_references!' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:author)  { build(:user) }
     let(:commit)  { project.commit }
     let(:commit2) { project.commit }
 
     let!(:issue) do
-      create(:issue, project: project, description: commit.to_reference)
+      create(:issue, project: project, description: "See #{commit.to_reference}")
     end
 
     it 'correctly removes already-mentioned Commits' do
@@ -92,7 +100,7 @@ describe Issue, "Mentionable" do
   end
 
   describe '#create_new_cross_references!' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:author)  { create(:author) }
     let(:issues)  { create_list(:issue, 2, project: project, author: author) }
 
@@ -138,6 +146,16 @@ describe Issue, "Mentionable" do
         issue.update_attributes(description: issues[1].to_reference)
         issue.create_new_cross_references!
       end
+
+      it 'notifies new references from project snippet note' do
+        snippet = create(:snippet, project: project)
+        note = create(:note, note: issues[0].to_reference, noteable: snippet, project: project, author: author)
+
+        expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
+
+        note.update_attributes(note: issues[1].to_reference)
+        note.create_new_cross_references!
+      end
     end
 
     def create_issue(description:)
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 0e097559b592cc2921c5430e3ad5c74271977d48..ad703a6c8bbb43cda2cfdc9e3a79ca36a57aaae5 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -7,7 +7,7 @@ describe Milestone, 'Milestoneish' do
   let(:member) { create(:user) }
   let(:guest) { create(:user) }
   let(:admin) { create(:admin) }
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
   let(:milestone) { create(:milestone, project: project) }
   let!(:issue) { create(:issue, project: project, milestone: milestone) }
   let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 9041690023f0a7719ee0de805ed7e17652f256a4..6cf5877424d5574f2f098c96f9e4351b5b9bce2e 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ProjectFeaturesCompatibility do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:features) { %w(issues wiki builds merge_requests snippets) }
 
   # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index b556135532f3b725e21d3531ff71a157a1b19e75..30443534ccaf6baaa6c751c2f19cb807d38428d6 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -68,4 +68,14 @@ describe Group, 'Routable' do
       end
     end
   end
+
+  describe '.member_descendants' do
+    let!(:user) { create(:user) }
+    let!(:nested_group) { create(:group, parent: group) }
+
+    before { group.add_owner(user) }
+    subject { described_class.member_descendants(user.id) }
+
+    it { is_expected.to eq([nested_group]) }
+  end
 end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 70f985afefb1bba0292186f4c87237a0a1d2890b..9053485939ea75f7472623d2c9e8adecf57dd968 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#code', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
@@ -27,15 +27,13 @@ describe 'CycleAnalytics#code', feature: true do
 
     context "when a regular merge request (that doesn't close the issue) is created" do
       it "returns nil" do
-        5.times do
-          issue = create(:issue, project: project)
+        issue = create(:issue, project: project)
 
-          create_commit_referencing_issue(issue)
-          create_merge_request_closing_issue(issue, message: "Closes nothing")
+        create_commit_referencing_issue(issue)
+        create_merge_request_closing_issue(issue, message: "Closes nothing")
 
-          merge_merge_requests_closing_issue(issue)
-          deploy_master
-        end
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
 
         expect(subject[:code].median).to be_nil
       end
@@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do
 
     context "when a regular merge request (that doesn't close the issue) is created" do
       it "returns nil" do
-        5.times do
-          issue = create(:issue, project: project)
+        issue = create(:issue, project: project)
 
-          create_commit_referencing_issue(issue)
-          create_merge_request_closing_issue(issue, message: "Closes nothing")
+        create_commit_referencing_issue(issue)
+        create_merge_request_closing_issue(issue, message: "Closes nothing")
 
-          merge_merge_requests_closing_issue(issue)
-        end
+        merge_merge_requests_closing_issue(issue)
 
         expect(subject[:code].median).to be_nil
       end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index e4b6a8f45180753656cb040af935659436b4e975..fc7d18bd40ee8d334e6941b54f19374d196de12b 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#issue', models: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
@@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do
 
   context "when a regular label (instead of a list label) is added to the issue" do
     it "returns nil" do
-      5.times do
-        regular_label = create(:label)
-        issue = create(:issue, project: project)
-        issue.update(label_ids: [regular_label.id])
+      regular_label = create(:label)
+      issue = create(:issue, project: project)
+      issue.update(label_ids: [regular_label.id])
 
-        create_merge_request_closing_issue(issue)
-        merge_merge_requests_closing_issue(issue)
-      end
+      create_merge_request_closing_issue(issue)
+      merge_merge_requests_closing_issue(issue)
 
       expect(subject[:issue].median).to be_nil
     end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index dc5b04852d668c8a40ebd625ca03ad53d2683977..55483fc876a77a82caab6b163e0ce1497c67dbee 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#plan', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 5e99188f318e31dd0e179da0c244b377d4ca2a51..b9fe492fe2c790b679dc0dc2a9536be94ee1dbb4 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#production', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
@@ -21,7 +21,13 @@ describe 'CycleAnalytics#production', feature: true do
        ["production deploy happens after merge request is merged (along with other changes)",
         lambda do |context, data|
           # Make other changes on master
-          sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
+          sha = context.project.repository.commit_file(
+            context.user,
+            context.random_git_name,
+            'content',
+            message: 'commit message',
+            branch_name: 'master',
+            update: false)
           context.project.repository.commit(sha)
 
           context.deploy_master
@@ -29,11 +35,9 @@ describe 'CycleAnalytics#production', feature: true do
 
   context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
     it "returns nil" do
-      5.times do
-        merge_request = create(:merge_request)
-        MergeRequests::MergeService.new(project, user).execute(merge_request)
-        deploy_master
-      end
+      merge_request = create(:merge_request)
+      MergeRequests::MergeService.new(project, user).execute(merge_request)
+      deploy_master
 
       expect(subject[:production].median).to be_nil
     end
@@ -41,12 +45,10 @@ describe 'CycleAnalytics#production', feature: true do
 
   context "when the deployment happens to a non-production environment" do
     it "returns nil" do
-      5.times do
-        issue = create(:issue, project: project)
-        merge_request = create_merge_request_closing_issue(issue)
-        MergeRequests::MergeService.new(project, user).execute(merge_request)
-        deploy_master(environment: 'staging')
-      end
+      issue = create(:issue, project: project)
+      merge_request = create_merge_request_closing_issue(issue)
+      MergeRequests::MergeService.new(project, user).execute(merge_request)
+      deploy_master(environment: 'staging')
 
       expect(subject[:production].median).to be_nil
     end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 45baa5f700644c70453029050ad6ce70fbad0089..febb18c9884c0ddd74c7d78c336ea77387bb8e36 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#review', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
@@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do
 
   context "when a regular merge request (that doesn't close the issue) is created and merged" do
     it "returns nil" do
-      5.times do
-        MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
-      end
+      MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
 
       expect(subject[:review].median).to be_nil
     end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 77625aad5806495bdf5f046b13a41815ae09314e..9a024d533a16066e7300cf7ddcccb0f66209ec25 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -3,9 +3,10 @@ require 'spec_helper'
 describe 'CycleAnalytics#staging', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
+
   subject { CycleAnalytics.new(project, from: from_date) }
 
   generate_cycle_analytics_spec(
@@ -28,10 +29,10 @@ describe 'CycleAnalytics#staging', feature: true do
                                sha = context.project.repository.commit_file(
                                  context.user,
                                  context.random_git_name,
-                                 "content",
-                                 "commit message",
-                                 'master',
-                                 false)
+                                 'content',
+                                 message: 'commit message',
+                                 branch_name: 'master',
+                                 update: false)
                                context.project.repository.commit(sha)
 
                                context.deploy_master
@@ -39,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do
 
   context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
     it "returns nil" do
-      5.times do
-        merge_request = create(:merge_request)
-        MergeRequests::MergeService.new(project, user).execute(merge_request)
-        deploy_master
-      end
+      merge_request = create(:merge_request)
+      MergeRequests::MergeService.new(project, user).execute(merge_request)
+      deploy_master
 
       expect(subject[:staging].median).to be_nil
     end
@@ -51,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do
 
   context "when the deployment happens to a non-production environment" do
     it "returns nil" do
-      5.times do
-        issue = create(:issue, project: project)
-        merge_request = create_merge_request_closing_issue(issue)
-        MergeRequests::MergeService.new(project, user).execute(merge_request)
-        deploy_master(environment: 'staging')
-      end
+      issue = create(:issue, project: project)
+      merge_request = create_merge_request_closing_issue(issue)
+      MergeRequests::MergeService.new(project, user).execute(merge_request)
+      deploy_master(environment: 'staging')
 
       expect(subject[:staging].median).to be_nil
     end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 27a117d2d76ba832eb0d11219ebe2218ba47224e..c2ba012a0e67b5fd99b9678912e03a12669dcecb 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'CycleAnalytics#test', feature: true do
   extend CycleAnalyticsHelpers::TestGeneration
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
@@ -24,16 +24,14 @@ describe 'CycleAnalytics#test', feature: true do
 
   context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
     it "returns nil" do
-      5.times do
-        issue = create(:issue, project: project)
-        merge_request = create_merge_request_closing_issue(issue)
-        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+      issue = create(:issue, project: project)
+      merge_request = create_merge_request_closing_issue(issue)
+      pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
 
-        pipeline.run!
-        pipeline.succeed!
+      pipeline.run!
+      pipeline.succeed!
 
-        merge_merge_requests_closing_issue(issue)
-      end
+      merge_merge_requests_closing_issue(issue)
 
       expect(subject[:test].median).to be_nil
     end
@@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do
 
   context "when the pipeline is not for a merge request" do
     it "returns nil" do
-      5.times do
-        pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
+      pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
 
-        pipeline.run!
-        pipeline.succeed!
-      end
+      pipeline.run!
+      pipeline.succeed!
 
       expect(subject[:test].median).to be_nil
     end
@@ -54,16 +50,14 @@ describe 'CycleAnalytics#test', feature: true do
 
   context "when the pipeline is dropped (failed)" do
     it "returns nil" do
-      5.times do
-        issue = create(:issue, project: project)
-        merge_request = create_merge_request_closing_issue(issue)
-        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+      issue = create(:issue, project: project)
+      merge_request = create_merge_request_closing_issue(issue)
+      pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
 
-        pipeline.run!
-        pipeline.drop!
+      pipeline.run!
+      pipeline.drop!
 
-        merge_merge_requests_closing_issue(issue)
-      end
+      merge_merge_requests_closing_issue(issue)
 
       expect(subject[:test].median).to be_nil
     end
@@ -71,16 +65,14 @@ describe 'CycleAnalytics#test', feature: true do
 
   context "when the pipeline is cancelled" do
     it "returns nil" do
-      5.times do
-        issue = create(:issue, project: project)
-        merge_request = create_merge_request_closing_issue(issue)
-        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+      issue = create(:issue, project: project)
+      merge_request = create_merge_request_closing_issue(issue)
+      pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
 
-        pipeline.run!
-        pipeline.cancel!
+      pipeline.run!
+      pipeline.cancel!
 
-        merge_merge_requests_closing_issue(issue)
-      end
+      merge_merge_requests_closing_issue(issue)
 
       expect(subject[:test].median).to be_nil
     end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 8a1e337c1a3fe840ffcfd6284431be3799ce3487..aacc178a19e432e6a6cb92859016cc50714bc2c4 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -12,7 +12,7 @@ describe DeployKeysProject, models: true do
   end
 
   describe "Destroying" do
-    let(:project)     { create(:project) }
+    let(:project)     { create(:empty_project) }
     subject           { create(:deploy_keys_project, project: project) }
     let(:deploy_key)  { subject.deploy_key }
 
@@ -39,7 +39,7 @@ describe DeployKeysProject, models: true do
     end
 
     context "when the deploy key is used by more than one project" do
-      let!(:other_project) { create(:project) }
+      let!(:other_project) { create(:empty_project) }
 
       before do
         other_project.deploy_keys << deploy_key
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index ca594a320c0772366241085429dba70672d02308..fc4435a2f64420e75dec4cd9853e8559ad06c864 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -17,7 +17,7 @@ describe Deployment, models: true do
   it { is_expected.to validate_presence_of(:sha) }
 
   describe '#includes_commit?' do
-    let(:project)     { create(:project) }
+    let(:project)     { create(:project, :repository) }
     let(:environment) { create(:environment, project: project) }
     let(:deployment) do
       create(:deployment, environment: environment, sha: project.commit.id)
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 3db5937a4f3932eec5c2354fbb372e79ebe7fb33..9ea3a4b7020a2afd1631f8b35a72b6d8d73f8479 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
 describe DiffNote, models: true do
   include RepoHelpers
 
-  let(:project) { create(:project) }
-  let(:merge_request) { create(:merge_request, source_project: project) }
+  let(:merge_request) { create(:merge_request) }
+  let(:project) { merge_request.project }
   let(:commit) { project.commit(sample_commit.id) }
 
   let(:path) { "files/ruby/popen.rb" }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 96efe1696c31905ece1c996ad0c653dbc90bf22b..eba392044bf4a3475d61588e644e430b886ebf4c 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -32,7 +32,7 @@ describe Environment, models: true do
   end
 
   describe '#includes_commit?' do
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     context 'without a last deployment' do
       it "returns false" do
@@ -81,7 +81,7 @@ describe Environment, models: true do
   end
 
   describe '#first_deployment_for' do
-    let(:project)       { create(:project) }
+    let(:project)       { create(:project, :repository) }
     let!(:deployment)   { create(:deployment, environment: environment, ref: commit.parent.id) }
     let!(:deployment1)  { create(:deployment, environment: environment, ref: commit.id) }
     let(:head_commit)   { project.commit }
@@ -288,6 +288,11 @@ describe Environment, models: true do
       "1-foo"                     => "env-1-foo" + SUFFIX,
       "1/foo"                     => "env-1-foo" + SUFFIX,
       "foo-"                      => "foo" + SUFFIX,
+      "foo--bar"                  => "foo-bar" + SUFFIX,
+      "foo**bar"                  => "foo-bar" + SUFFIX,
+      "*-foo"                     => "env-foo" + SUFFIX,
+      "staging-12345678-"         => "staging-12345678" + SUFFIX,
+      "staging-12345678-01234567" => "staging-12345678" + SUFFIX,
     }.each do |name, matcher|
       it "returns a slug matching #{matcher}, given #{name}" do
         slug = described_class.new(name: name).generate_slug
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index f8660da031dba4e2c91abd100e6fa1ec99ac50ed..349474bb656b28ed8c26c88fb85a574dfd014359 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -27,7 +27,7 @@ describe Event, models: true do
   end
 
   describe "Push event" do
-    let(:project) { create(:project, :private) }
+    let(:project) { create(:empty_project, :private) }
     let(:user) { project.owner }
     let(:event) { create_event(project, user) }
 
@@ -187,7 +187,7 @@ describe Event, models: true do
     end
 
     context 'merge request diff note event' do
-      let(:project) { create(:project, :public) }
+      let(:project) { create(:empty_project, :public) }
       let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) }
       let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) }
       let(:target) { note_on_merge_request }
@@ -202,7 +202,7 @@ describe Event, models: true do
       end
 
       context 'private project' do
-        let(:project) { create(:project, :private) }
+        let(:project) { create(:empty_project, :private) }
 
         it do
           expect(event.visible_to_user?(non_member)).to eq false
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 1863581f57be4a46ae361c1c146cd553e3fe0b88..454550c9710a1c9a1938c868578ef4b73eb5fc5e 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ForkedProjectLink, "add link on fork" do
-  let(:project_from) { create(:project) }
+  let(:project_from) { create(:project, :repository) }
   let(:namespace) { create(:namespace) }
   let(:user) { create(:user, namespace: namespace) }
 
@@ -21,7 +21,7 @@ end
 
 describe '#forked?' do
   let(:forked_project_link) { build(:forked_project_link) }
-  let(:project_from) { create(:project) }
+  let(:project_from) { create(:project, :repository) }
   let(:project_to) { create(:project, forked_project_link: forked_project_link) }
 
   before :each do
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index d87684fd49eb5c75d3ac968ded162e5db2f64e0b..cacbab8bcb1a3a3d767057f52027a6d00648e113 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -4,9 +4,9 @@ describe GlobalMilestone, models: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:group) { create(:group) }
-  let(:project1) { create(:project, group: group) }
-  let(:project2) { create(:project, path: 'gitlab-ci', group: group) }
-  let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) }
+  let(:project1) { create(:empty_project, group: group) }
+  let(:project2) { create(:empty_project, path: 'gitlab-ci', group: group) }
+  let(:project3) { create(:empty_project, path: 'cookbook-gitlab', group: group) }
 
   describe '.build_collection' do
     let(:milestone1_due_date) { 2.weeks.from_now.to_date }
diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb
index 601167c3bd325335ffa709de16a94166ac05f7ad..916afb7aaf5d982d28e53a5f1ee2d53a8f39c3bc 100644
--- a/spec/models/group_milestone_spec.rb
+++ b/spec/models/group_milestone_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe GroupMilestone, models: true do
   let(:group) { create(:group) }
-  let(:project) { create(:project, group: group) }
+  let(:project) { create(:empty_project, group: group) }
   let(:project_milestone) do
     create(:milestone, title: "Milestone v1.2", project: project)
   end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 45fe927202b993bb6725825d750e406346ed5443..9ca5055519119936f3c612ec5848f05d454986b4 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -81,13 +81,19 @@ describe Group, models: true do
     describe 'public_only' do
       subject { described_class.public_only.to_a }
 
-      it{ is_expected.to eq([group]) }
+      it { is_expected.to eq([group]) }
     end
 
     describe 'public_and_internal_only' do
       subject { described_class.public_and_internal_only.to_a }
 
-      it{ is_expected.to match_array([group, internal_group]) }
+      it { is_expected.to match_array([group, internal_group]) }
+    end
+
+    describe 'non_public_only' do
+      subject { described_class.non_public_only.to_a }
+
+      it { is_expected.to match_array([private_group, internal_group]) }
     end
   end
 
@@ -269,6 +275,12 @@ describe Group, models: true do
     it 'returns the canonical URL' do
       expect(group.web_url).to include("groups/#{group.name}")
     end
+
+    context 'nested group' do
+      let(:nested_group) { create(:group, :nested) }
+
+      it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") }
+    end
   end
 
   describe 'nested group' do
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index d79f929f7a1ca0aca42043cce986495e473b097e..582b54c0712816b79ffce6776d5f98f51b0d9bf6 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
 describe Guest, lib: true do
-  let(:public_project) { create(:project, :public) }
-  let(:private_project) { create(:project, :private) }
-  let(:internal_project) { create(:project, :internal) }
+  let(:public_project) { build_stubbed(:empty_project, :public) }
+  let(:private_project) { build_stubbed(:empty_project, :private) }
+  let(:internal_project) { build_stubbed(:empty_project, :internal) }
 
   describe '.can_pull?' do
     context 'when project is private' do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index ad2b710041a38b3f3974e5a73f0031fc1c94b56f..e8caad00c4435afbd50af8ad08f49fd70f75fb1f 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -4,7 +4,7 @@ describe SystemHook, models: true do
   describe "execute" do
     let(:system_hook) { create(:system_hook) }
     let(:user)        { create(:user) }
-    let(:project)     { create(:project, namespace: user.namespace) }
+    let(:project)     { create(:empty_project, namespace: user.namespace) }
     let(:group)       { create(:group) }
 
     before do
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index e52b9d75cefab6713e5ff8e04448f4fbeab00751..9d4db1bfb526ae0a437928e8f2df47028bffcece 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -25,7 +25,7 @@ describe WebHook, models: true do
   end
 
   describe "execute" do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:project_hook) { create(:project_hook) }
 
     before(:each) do
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index 2459a49f0958b4daebf20f0c7a4f31feaeecd174..08712f2a76859db3d0ffed850288d47b3ca259b3 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Issue::Metrics, models: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
 
   subject { create(:issue, project: project) }
 
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index d742c8146802c2848ca9495662310fa714daf243..d8aed25c041f5a97d63bbdbf8dff83fdf9d02655 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe IssueCollection do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:issue1) { create(:issue, project: project) }
   let(:issue2) { create(:issue, project: project) }
   let(:collection) { described_class.new([issue1, issue2]) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index bfa36d92ac3812f37f82f9a1c97e9837c5ad1f62..bba9058f3941e7326c528a9d4148555a1eee665c 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -23,21 +23,74 @@ describe Issue, models: true do
   end
 
   describe '#to_reference' do
-    let(:project) { build(:empty_project, name: 'sample-project') }
-    let(:issue) { build(:issue, iid: 1, project: project) }
+    let(:namespace) { build(:namespace, path: 'sample-namespace') }
+    let(:project)   { build(:empty_project, name: 'sample-project', namespace: namespace) }
+    let(:issue)     { build(:issue, iid: 1, project: project) }
+    let(:group)     { create(:group, name: 'Group', path: 'sample-group') }
+
+    context 'when nil argument' do
+      it 'returns issue id' do
+        expect(issue.to_reference).to eq "#1"
+      end
+    end
 
-    it 'returns a String reference to the object' do
-      expect(issue.to_reference).to eq "#1"
+    context 'when full is true' do
+      it 'returns complete path to the issue' do
+        expect(issue.to_reference(full: true)).to          eq 'sample-namespace/sample-project#1'
+        expect(issue.to_reference(project, full: true)).to eq 'sample-namespace/sample-project#1'
+        expect(issue.to_reference(group, full: true)).to   eq 'sample-namespace/sample-project#1'
+      end
     end
 
-    it 'returns a String reference with the full path' do
-      expect(issue.to_reference(full: true)).to eq(project.path_with_namespace + '#1')
+    context 'when same project argument' do
+      it 'returns issue id' do
+        expect(issue.to_reference(project)).to eq("#1")
+      end
+    end
+
+    context 'when cross namespace project argument' do
+      let(:another_namespace_project) { create(:empty_project, name: 'another-project') }
+
+      it 'returns complete path to the issue' do
+        expect(issue.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project#1'
+      end
     end
 
     it 'supports a cross-project reference' do
-      another_project = build(:project, name: 'another-project', namespace: project.namespace)
+      another_project = build(:empty_project, name: 'another-project', namespace: project.namespace)
       expect(issue.to_reference(another_project)).to eq "sample-project#1"
     end
+
+    context 'when same namespace / cross-project argument' do
+      let(:another_project) { create(:empty_project, namespace: namespace) }
+
+      it 'returns path to the issue with the project name' do
+        expect(issue.to_reference(another_project)).to eq 'sample-project#1'
+      end
+    end
+
+    context 'when different namespace / cross-project argument' do
+      let(:another_namespace) { create(:namespace, path: 'another-namespace') }
+      let(:another_project)   { create(:empty_project, path: 'another-project', namespace: another_namespace) }
+
+      it 'returns full path to the issue' do
+        expect(issue.to_reference(another_project)).to eq 'sample-namespace/sample-project#1'
+      end
+    end
+
+    context 'when argument is a namespace' do
+      context 'with same project path' do
+        it 'returns path to the issue with the project name' do
+          expect(issue.to_reference(namespace)).to eq 'sample-project#1'
+        end
+      end
+
+      context 'with different project path' do
+        it 'returns full path to the issue' do
+          expect(issue.to_reference(group)).to eq 'sample-namespace/sample-project#1'
+        end
+      end
+    end
   end
 
   describe '#is_being_reassigned?' do
@@ -60,9 +113,9 @@ describe Issue, models: true do
   end
 
   describe '#closed_by_merge_requests' do
-    let(:project) { create(:project) }
-    let(:issue)   { create(:issue, project: project, state: "opened")}
-    let(:closed_issue) { build(:issue, project: project, state: "closed")}
+    let(:project) { create(:project, :repository) }
+    let(:issue) { create(:issue, project: project)}
+    let(:closed_issue) { build(:issue, :closed, project: project)}
 
     let(:mr) do
       opts = {
@@ -104,7 +157,7 @@ describe Issue, models: true do
 
   describe '#referenced_merge_requests' do
     it 'returns the referenced merge requests' do
-      project = create(:project, :public)
+      project = create(:empty_project, :public)
 
       mr1 = create(:merge_request,
                    source_project: project,
@@ -137,7 +190,7 @@ describe Issue, models: true do
     end
 
     context 'user is reporter in project issue belongs to' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
       let(:issue) { create(:issue, project: project) }
 
       before { project.team << [user, :reporter] }
@@ -151,7 +204,7 @@ describe Issue, models: true do
 
       context 'checking destination project also' do
         subject { issue.can_move?(user, to_project) }
-        let(:to_project) { create(:project) }
+        let(:to_project) { create(:empty_project) }
 
         context 'destination project allowed' do
           before { to_project.team << [user, :reporter] }
@@ -217,7 +270,7 @@ describe Issue, models: true do
   end
 
   it_behaves_like 'an editable mentionable' do
-    subject { create(:issue) }
+    subject { create(:issue, project: create(:project, :repository)) }
 
     let(:backref_text) { "issue #{subject.to_reference}" }
     let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
@@ -246,7 +299,7 @@ describe Issue, models: true do
 
   describe '#participants' do
     context 'using a public project' do
-      let(:project) { create(:project, :public) }
+      let(:project) { create(:empty_project, :public) }
       let(:issue) { create(:issue, project: project) }
 
       let!(:note1) do
@@ -268,7 +321,7 @@ describe Issue, models: true do
 
     context 'using a private project' do
       it 'does not include mentioned users that do not have access to the project' do
-        project = create(:project)
+        project = create(:empty_project)
         user = create(:user)
         issue = create(:issue, project: project)
 
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 5eaddd822befbdbc9c2e609de802b69d4d114063..7c40cfd82531b9028a2ffd0f785381356584a768 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -30,11 +30,30 @@ describe Key, models: true do
     end
 
     describe "#update_last_used_at" do
-      it "enqueues a UseKeyWorker job" do
-        key = create(:key)
+      let(:key) { create(:key) }
+
+      context 'when key was not updated during the last day' do
+        before do
+          allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+            and_return('000000')
+        end
+
+        it 'enqueues a UseKeyWorker job' do
+          expect(UseKeyWorker).to receive(:perform_async).with(key.id)
+          key.update_last_used_at
+        end
+      end
+
+      context 'when key was updated during the last day' do
+        before do
+          allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+            and_return(false)
+        end
 
-        expect(UseKeyWorker).to receive(:perform_async).with(key.id)
-        key.update_last_used_at
+        it 'does not enqueue a UseKeyWorker job' do
+          expect(UseKeyWorker).not_to receive(:perform_async)
+          key.update_last_used_at
+        end
       end
     end
   end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 4f7c8a36cb56f90bd7e206affd9cea6405034034..16e2144d6a16a653733fa7d798f81ae30fbea990 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -481,7 +481,7 @@ describe Member, models: true do
 
   describe "destroying a record", truncate: true do
     it "refreshes user's authorized projects" do
-      project = create(:project, :private)
+      project = create(:empty_project, :private)
       user    = create(:user)
       member  = project.team << [user, :reporter]
 
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 68f72f5c86ea9abf3bb9d9a725f3f8c1963f036e..e4be0aba7a6a3711947cd41a4b94708e4f415acb 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -83,8 +83,8 @@ describe ProjectMember, models: true do
 
   describe '.import_team' do
     before do
-      @project_1 = create :project
-      @project_2 = create :project
+      @project_1 = create(:empty_project)
+      @project_2 = create(:empty_project)
 
       @user_1 = create :user
       @user_2 = create :user
@@ -117,7 +117,7 @@ describe ProjectMember, models: true do
       users = create_list(:user, 2)
 
       described_class.add_users_to_projects(
-        [projects.first.id, projects.second],
+        [projects.first.id, projects.second.id],
         [users.first.id, users.second],
         described_class::MASTER)
 
@@ -131,8 +131,8 @@ describe ProjectMember, models: true do
 
   describe '.truncate_teams' do
     before do
-      @project_1 = create :project
-      @project_2 = create :project
+      @project_1 = create(:empty_project)
+      @project_2 = create(:empty_project)
 
       @user_1 = create :user
       @user_2 = create :user
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 255db41cb19a364823a48026dbcd0925ed8ed285..9afed311e279deafb1d9914db85b3a4c7f0b8eef 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -1,9 +1,7 @@
 require 'spec_helper'
 
 describe MergeRequest::Metrics, models: true do
-  let(:project) { create(:project) }
-
-  subject { create(:merge_request, source_project: project) }
+  subject { create(:merge_request) }
 
   describe "when recording the default set of metrics on merge request save" do
     it "records the merge time" do
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 064f29d2d66a8a6dac340e23a4a1215c0b1b5fa2..3cee2b7714f29b88371e1cb50c24c9239f3d4163 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -24,7 +24,7 @@ describe Milestone, models: true do
     it { is_expected.to have_many(:issues) }
   end
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
   let(:milestone) { create(:milestone, project: project) }
   let(:issue) { create(:issue, project: project) }
   let(:user) { create(:user) }
@@ -44,7 +44,7 @@ describe Milestone, models: true do
     end
 
     it "accepts the same title in another project" do
-      project = build(:project)
+      project = build(:empty_project)
       new_milestone = Milestone.new(project: project, title: milestone.title)
 
       expect(new_milestone).to be_valid
@@ -257,7 +257,7 @@ describe Milestone, models: true do
     end
 
     it 'supports a cross-project reference' do
-      another_project = build(:project, name: 'another-project', namespace: project.namespace)
+      another_project = build(:empty_project, name: 'another-project', namespace: project.namespace)
       expect(milestone.to_reference(another_project)).to eq "sample-project%1"
     end
   end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 600538ff5f435f01e6b2d9b6bbe858efeaa200eb..4e96f19eb6fbcd44517d3f654e636e5dbb60e267 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -5,6 +5,8 @@ describe Namespace, models: true do
 
   it { is_expected.to have_many :projects }
   it { is_expected.to have_many :project_statistics }
+  it { is_expected.to belong_to :parent }
+  it { is_expected.to have_many :children }
 
   it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
@@ -105,7 +107,7 @@ describe Namespace, models: true do
   describe '#move_dir' do
     before do
       @namespace = create :namespace
-      @project = create :project, namespace: @namespace
+      @project = create(:empty_project, namespace: @namespace)
       allow(@namespace).to receive(:path_changed?).and_return(true)
     end
 
@@ -117,6 +119,7 @@ describe Namespace, models: true do
       new_path = @namespace.path + "_new"
       allow(@namespace).to receive(:path_was).and_return(@namespace.path)
       allow(@namespace).to receive(:path).and_return(new_path)
+      expect(@namespace).to receive(:remove_exports!)
       expect(@namespace.move_dir).to be_truthy
     end
 
@@ -136,14 +139,20 @@ describe Namespace, models: true do
   end
 
   describe :rm_dir do
-    let!(:project) { create(:project, namespace: namespace) }
+    let!(:project) { create(:empty_project, namespace: namespace) }
     let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) }
 
-    before { namespace.destroy }
-
     it "removes its dirs when deleted" do
+      namespace.destroy
+
       expect(File.exist?(path)).to be(false)
     end
+
+    it 'removes the exports folder' do
+      expect(namespace).to receive(:remove_exports!)
+
+      namespace.destroy
+    end
   end
 
   describe '.find_by_path_or_name' do
@@ -182,17 +191,31 @@ describe Namespace, models: true do
     it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
   end
 
-  describe '#parents' do
+  describe '#ancestors' do
     let(:group) { create(:group) }
     let(:nested_group) { create(:group, parent: group) }
     let(:deep_nested_group) { create(:group, parent: nested_group) }
     let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
 
-    it 'returns the correct parents' do
-      expect(very_deep_nested_group.parents).to eq([group, nested_group, deep_nested_group])
-      expect(deep_nested_group.parents).to eq([group, nested_group])
-      expect(nested_group.parents).to eq([group])
-      expect(group.parents).to eq([])
+    it 'returns the correct ancestors' do
+      expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group])
+      expect(deep_nested_group.ancestors).to eq([group, nested_group])
+      expect(nested_group.ancestors).to eq([group])
+      expect(group.ancestors).to eq([])
+    end
+  end
+
+  describe '#descendants' do
+    let!(:group) { create(:group) }
+    let!(:nested_group) { create(:group, parent: group) }
+    let!(:deep_nested_group) { create(:group, parent: nested_group) }
+    let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+
+    it 'returns the correct descendants' do
+      expect(very_deep_nested_group.descendants.to_a).to eq([])
+      expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group])
+      expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group])
+      expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group])
     end
   end
 end
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index b76513d2a3c7cb040c1cec1a95149f5a8f1bd0c8..492c4e01bd8a9d4df8771d16452fc442cae63e4d 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Network::Graph, models: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let!(:note_on_commit) { create(:note_on_commit, project: project) }
 
   it '#initialize' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 310fecd8a5cc5f05a190730814ca89ce1e6f9b4c..1cde9e049518f2dc1e614d4f872a62c096b6eb23 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -42,7 +42,7 @@ describe Note, models: true do
     context 'when noteable and note project differ' do
       subject do
         build(:note, noteable: build_stubbed(:issue),
-                     project: build_stubbed(:project))
+                     project: build_stubbed(:empty_project))
       end
 
       it { is_expected.to be_invalid }
@@ -52,6 +52,19 @@ describe Note, models: true do
       subject { create(:note) }
       it { is_expected.to be_valid }
     end
+
+    context 'when project is missing for a project related note' do
+      subject { build(:note, project: nil, noteable: build_stubbed(:issue)) }
+      it { is_expected.to be_invalid }
+    end
+
+    context 'when noteable is a personal snippet' do
+      subject { build(:note_on_personal_snippet) }
+
+      it 'is valid without project' do
+        is_expected.to be_valid
+      end
+    end
   end
 
   describe "Commit notes" do
@@ -80,8 +93,8 @@ describe Note, models: true do
 
   describe 'authorization' do
     before do
-      @p1 = create(:project)
-      @p2 = create(:project)
+      @p1 = create(:empty_project)
+      @p2 = create(:empty_project)
       @u1 = create(:user)
       @u2 = create(:user)
       @u3 = create(:user)
@@ -125,7 +138,7 @@ describe Note, models: true do
   it_behaves_like 'an editable mentionable' do
     subject { create :note, noteable: issue, project: issue.project }
 
-    let(:issue) { create :issue }
+    let(:issue) { create(:issue, project: create(:project, :repository)) }
     let(:backref_text) { issue.gfm_reference }
     let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
   end
@@ -139,6 +152,7 @@ describe Note, models: true do
         with([{
           text: note1.note,
           context: {
+            skip_project_check: false,
             pipeline: :note,
             cache_key: [note1, "note"],
             project: note1.project,
@@ -150,6 +164,7 @@ describe Note, models: true do
         with([{
           text: note2.note,
           context: {
+            skip_project_check: false,
             pipeline: :note,
             cache_key: [note2, "note"],
             project: note2.project,
@@ -176,10 +191,10 @@ describe Note, models: true do
 
   describe "cross_reference_not_visible_for?" do
     let(:private_user)    { create(:user) }
-    let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } }
+    let(:private_project) { create(:empty_project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } }
     let(:private_issue)   { create(:issue, project: private_project) }
 
-    let(:ext_proj)  { create(:project, :public) }
+    let(:ext_proj)  { create(:empty_project, :public) }
     let(:ext_issue) { create(:issue, project: ext_proj) }
 
     let(:note) do
@@ -222,7 +237,7 @@ describe Note, models: true do
 
   describe '#participants' do
     it 'includes the note author' do
-      project = create(:project, :public)
+      project = create(:empty_project, :public)
       issue = create(:issue, project: project)
       note = create(:note_on_issue, noteable: issue, project: project)
 
@@ -306,4 +321,70 @@ describe Note, models: true do
       end
     end
   end
+
+  describe '#for_personal_snippet?' do
+    it 'returns false for a project snippet note' do
+      expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy
+    end
+
+    it 'returns true for a personal snippet note' do
+      expect(build(:note_on_personal_snippet).for_personal_snippet?).to be_truthy
+    end
+  end
+
+  describe '#to_ability_name' do
+    it 'returns snippet for a project snippet note' do
+      expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet')
+    end
+
+    it 'returns personal_snippet for a personal snippet note' do
+      expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet')
+    end
+
+    it 'returns merge_request for an MR note' do
+      expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request')
+    end
+
+    it 'returns issue for an issue note' do
+      expect(build(:note_on_issue).to_ability_name).to eq('issue')
+    end
+
+    it 'returns issue for a commit note' do
+      expect(build(:note_on_commit).to_ability_name).to eq('commit')
+    end
+  end
+
+  describe '#cache_markdown_field' do
+    let(:html) { '<p>some html</p>'}
+
+    context 'note for a project snippet' do
+      let(:note) { build(:note_on_project_snippet) }
+
+      before do
+        expect(Banzai::Renderer).to receive(:cacheless_render_field).
+          with(note, :note, { skip_project_check: false }).and_return(html)
+
+        note.save
+      end
+
+      it 'creates a note' do
+        expect(note.note_html).to eq(html)
+      end
+    end
+
+    context 'note for a personal snippet' do
+      let(:note) { build(:note_on_personal_snippet) }
+
+      before do
+        expect(Banzai::Renderer).to receive(:cacheless_render_field).
+          with(note, :note, { skip_project_check: true }).and_return(html)
+
+        note.save
+      end
+
+      it 'creates a note' do
+        expect(note.note_html).to eq(html)
+      end
+    end
+  end
 end
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index a55d43ab2f9ac819a62e900b145385881d368d1d..8589f1eb712de8a3aa61e89afa47851b5dbf883b 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ProjectFeature do
-  let(:project) { create(:project) }
+  let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
   describe '#feature_available?' do
@@ -35,7 +35,7 @@ describe ProjectFeature do
 
       it "returns true when user is a member of project group" do
         group = create(:group)
-        project = create(:project, namespace: group)
+        project = create(:empty_project, namespace: group)
         group.add_developer(user)
 
         features.each do |feature|
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 47397a822c18ed07a9634e8143cef79b6d12aefb..59a4ae1b79900894bf9a7a49f69427eef141e3c6 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -17,7 +17,7 @@ describe ProjectGroupLink do
 
   describe "destroying a record", truncate: true do
     it "refreshes group users' authorized projects" do
-      project     = create(:project, :private)
+      project     = create(:empty_project, :private)
       group       = create(:group)
       reporter    = create(:user)
       group_users = group.users
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 4d538cac0078ec84638b7f1ef7ce1718115640fb..9cdbfa44e5b0f3ed00d3ad4181500831f1e45fb1 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -100,7 +100,7 @@ describe ProjectLabel, models: true do
     end
 
     context 'cross project reference' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
 
       context 'using name' do
         it 'returns cross reference with label name' do
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 8e5145e824b09eb597a60fe1c58af265e55e2754..48aef3a93f2b699db05a708fbb67ee23572313eb 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -18,7 +18,7 @@ describe AsanaService, models: true do
 
   describe 'Execute' do
     let(:user) { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     def create_data_for_commits(*messages)
       {
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 4c5acb7990be089af3a01435e30e929950a9b177..96f00af898e687993c97d6865ea02164d88239f4 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -8,7 +8,7 @@ describe AssemblaService, models: true do
 
   describe "Execute" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     before do
       @assembla_service = AssemblaService.new
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index a3b9d084a755c21afd558fa02ffc023a995b63ca..953e664fb666e0507f1d234848a2e346e84862e0 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -22,7 +22,7 @@ describe CampfireService, models: true do
 
   describe "#execute" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     before do
       @campfire_service = CampfireService.new
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 42c2ed668bcf78efb08f859c3d3d5dc4c2d6a28b..f9307d6de7b11181e9a7a2dbbb6edb57c633f47a 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -27,7 +27,7 @@ describe DroneCiService, models: true, caching: true do
 
   shared_context :drone_ci_service do
     let(:drone)      { DroneCiService.new }
-    let(:project)    { create(:project, name: 'project') }
+    let(:project)    { create(:project, :repository, name: 'project') }
     let(:path)       { "#{project.namespace.path}/#{project.path}" }
     let(:drone_url)  { 'http://drone.example.com' }
     let(:sha)        { '2ab7834c' }
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 342d86aeca974e3d16e22de5e324760979c9f40d..bdeea1db1e3b9a724a2db6d5562cfa09cde2c855 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -23,7 +23,7 @@ describe ExternalWikiService, models: true do
   end
 
   describe 'External wiki' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context 'when it is active' do
       before do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index d6db02d6e7666bd69a373baa4df0e9a863d92631..a97e8c6e4cef18e011c9599a90c594977d4c2b52 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -22,7 +22,7 @@ describe FlowdockService, models: true do
 
   describe "Execute" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     before do
       @flowdock_service = FlowdockService.new
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 529044d1d8bba734d988eec3caea9655f24867bf..a13fbae03eb637052b459a942fd959738ddcab11 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -24,7 +24,7 @@ describe GemnasiumService, models: true do
 
   describe "Execute" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     before do
       @gemnasium_service = GemnasiumService.new
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 9b80f0e72963aac0bd071004c41fe0dea2e1adc6..dcb70ee28a8a8d0d779e1c6cd440bb6fa423f55f 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -8,21 +8,21 @@ describe GitlabIssueTrackerService, models: true do
 
   describe 'Validations' do
     context 'when service is active' do
-      subject { described_class.new(project: create(:project), active: true) }
+      subject { described_class.new(project: create(:empty_project), active: true) }
 
       it { is_expected.to validate_presence_of(:issues_url) }
       it_behaves_like 'issue tracker service URL attribute', :issues_url
     end
 
     context 'when service is inactive' do
-      subject { described_class.new(project: create(:project), active: false) }
+      subject { described_class.new(project: create(:empty_project), active: false) }
 
       it { is_expected.not_to validate_presence_of(:issues_url) }
     end
   end
 
   describe 'project and issue urls' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context 'with absolute urls' do
       before do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 2da3a9cb09f446530092627b80fffe20e579aa67..bf422ac7ce1a82c076b38a813be337cba3ed30e3 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -22,8 +22,8 @@ describe HipchatService, models: true do
 
   describe "Execute" do
     let(:hipchat) { HipchatService.new }
-    let(:user)    { create(:user, username: 'username') }
-    let(:project) { create(:project, name: 'project') }
+    let(:user)    { create(:user) }
+    let(:project) { create(:project, :repository) }
     let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
     let(:project_name) { project.name_with_namespace.gsub(/\s/, '') }
     let(:token) { 'verySecret' }
@@ -165,7 +165,7 @@ describe HipchatService, models: true do
 
     context "Note events" do
       let(:user) { create(:user) }
-      let(:project) { create(:project, creator_id: user.id) }
+      let(:project) { create(:project, :repository, creator: user) }
 
       context 'when commit comment event triggered' do
         let(:commit_note) do
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index f8c45b3756190612ec4e53395366f60d498d818f..b9fb6f3f6f487ac0a47e0bbcb2ea15744c5fdc46 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -25,7 +25,7 @@ describe IrkerService, models: true do
   describe 'Execute' do
     let(:irker) { IrkerService.new }
     let(:user) { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:sample_data) do
       Gitlab::DataBuilder::Push.build_sample(project, user)
     end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 862e3a72a737d8fba70b1858be8f39a6099ef187..2f6b159d76e8b28d850b3bca7b48da83deab59f9 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -71,7 +71,7 @@ describe JiraService, models: true do
   describe '#close_issue' do
     let(:custom_base_url) { 'http://custom_url' }
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let(:merge_request) { create(:merge_request) }
 
     before do
@@ -207,12 +207,12 @@ describe JiraService, models: true do
   end
 
   describe "Stored password invalidation" do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context "when a password was previously set" do
       before do
         @jira_service = JiraService.create!(
-          project: create(:project),
+          project: project,
           properties: {
             url: 'http://jira.example.com/rest/api/2',
             username: 'mic',
@@ -252,7 +252,7 @@ describe JiraService, models: true do
     context "when no password was previously set" do
       before do
         @jira_service = JiraService.create(
-          project: create(:project),
+          project: project,
           properties: {
             url: 'http://jira.example.com/rest/api/2',
             username: 'mic'
@@ -281,7 +281,7 @@ describe JiraService, models: true do
   end
 
   describe 'description and title' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context 'when it is not set' do
       before do
@@ -316,7 +316,7 @@ describe JiraService, models: true do
   end
 
   describe 'project and issue urls' do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
 
     context 'when gitlab.yml was initialized' do
       before do
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index c879edddfdd4fdbae7ef6ffb8c0dcb2e5bfbfab3..98f3d420c8a29b31a3ad626b7f65fb2e34e5d745 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -113,10 +113,7 @@ describe MattermostSlashCommandsService, :models do
         end
 
         it 'shows error messages' do
-          teams, message = subject
-
-          expect(teams).to be_empty
-          expect(message).to eq('Failed to get team list.')
+          expect(subject).to eq([[], "Failed to get team list."])
         end
       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 7c8824485f5046eacb26ca45215fc4e5a1d11f7e..03932895b0e8f2edb9bd139d05d04305f48cc912 100644
--- a/spec/models/project_services/pipeline_email_service_spec.rb
+++ b/spec/models/project_services/pipeline_email_service_spec.rb
@@ -7,7 +7,7 @@ describe PipelinesEmailService do
     create(:ci_pipeline, project: project, sha: project.commit('master').sha)
   end
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:recipient) { 'test@gitlab.com' }
 
   let(:data) do
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 8fc92a9ab51aa4c5b25787f36e9dfa9bd300b2ef..a7e7594a7d5b51e8ecd1fd8531a67174ef171a8e 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -27,7 +27,7 @@ describe PushoverService, models: true do
   describe 'Execute' do
     let(:pushover) { PushoverService.new }
     let(:user) { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:sample_data) do
       Gitlab::DataBuilder::Push.build_sample(project, user)
     end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8048e86fc3ad3b8570aff9f2e7fec6f1983b9832..48b085781e713c2a64172edde5bdfc265e4e1859 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -282,9 +282,10 @@ describe Project, models: true do
   end
 
   describe '#to_reference' do
-    let(:owner) { create(:user, name: 'Gitlab') }
+    let(:owner)     { create(:user, name: 'Gitlab') }
     let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
-    let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) }
+    let(:project)   { create(:empty_project, path: 'sample-project', namespace: namespace) }
+    let(:group)     { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
 
     context 'when nil argument' do
       it 'returns nil' do
@@ -292,6 +293,14 @@ describe Project, models: true do
       end
     end
 
+    context 'when full is true' do
+      it 'returns complete path to the project' do
+        expect(project.to_reference(full: true)).to          eq 'sample-namespace/sample-project'
+        expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
+        expect(project.to_reference(group, full: true)).to   eq 'sample-namespace/sample-project'
+      end
+    end
+
     context 'when same project argument' do
       it 'returns nil' do
         expect(project.to_reference(project)).to be_nil
@@ -309,10 +318,33 @@ describe Project, models: true do
     context 'when same namespace / cross-project argument' do
       let(:another_project) { create(:empty_project, namespace: namespace) }
 
-      it 'returns complete path to the project' do
+      it 'returns path to the project' do
         expect(project.to_reference(another_project)).to eq 'sample-project'
       end
     end
+
+    context 'when different namespace / cross-project argument' do
+      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
+      let(:another_project)   { create(:empty_project, path: 'another-project', namespace: another_namespace) }
+
+      it 'returns full path to the project' do
+        expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project'
+      end
+    end
+
+    context 'when argument is a namespace' do
+      context 'with same project path' do
+        it 'returns path to the project' do
+          expect(project.to_reference(namespace)).to eq 'sample-project'
+        end
+      end
+
+      context 'with different project path' do
+        it 'returns full path to the project' do
+          expect(project.to_reference(group)).to eq 'sample-namespace/sample-project'
+        end
+      end
+    end
   end
 
   describe '#to_human_reference' do
@@ -832,6 +864,26 @@ describe Project, models: true do
     it { expect(project.builds_enabled?).to be_truthy }
   end
 
+  describe '.with_shared_runners' do
+    subject { Project.with_shared_runners }
+
+    context 'when shared runners are enabled for project' do
+      let!(:project) { create(:empty_project, shared_runners_enabled: true) }
+
+      it "returns a project" do
+        is_expected.to eq([project])
+      end
+    end
+
+    context 'when shared runners are disabled for project' do
+      let!(:project) { create(:empty_project, shared_runners_enabled: false) }
+
+      it "returns an empty array" do
+        is_expected.to be_empty
+      end
+    end
+  end
+
   describe '.cached_count', caching: true do
     let(:group)     { create(:group, :public) }
     let!(:project1) { create(:empty_project, :public, group: group) }
@@ -974,6 +1026,28 @@ describe Project, models: true do
     end
   end
 
+  describe '#shared_runners' do
+    let!(:runner) { create(:ci_runner, :shared) }
+
+    subject { project.shared_runners }
+
+    context 'when shared runners are enabled for project' do
+      let!(:project) { create(:empty_project, shared_runners_enabled: true) }
+
+      it "returns a list of shared runners" do
+        is_expected.to eq([runner])
+      end
+    end
+
+    context 'when shared runners are disabled for project' do
+      let!(:project) { create(:empty_project, shared_runners_enabled: false) }
+
+      it "returns a empty list" do
+        is_expected.to be_empty
+      end
+    end
+  end
+
   describe '#visibility_level_allowed?' do
     let(:project) { create(:empty_project, :internal) }
 
@@ -1759,6 +1833,14 @@ describe Project, models: true do
     end
   end
 
+  describe 'inside_path' do
+    let!(:project1) { create(:empty_project) }
+    let!(:project2) { create(:empty_project) }
+    let!(:path) { project1.namespace.path }
+
+    it { expect(Project.inside_path(path)).to eq([project1]) }
+  end
+
   def enable_lfs
     allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
   end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 0475cecaa2da4f9f9f0d33b0e80edb5127b5f3a7..942eeab251daad845926c655ec84f3c064461d6c 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -265,10 +265,10 @@ describe ProjectTeam, models: true do
     let(:group) { create(:group) }
     let(:developer) { create(:user) }
     let(:master) { create(:user) }
-    let(:personal_project) { create(:project, namespace: developer.namespace) }
-    let(:group_project) { create(:project, namespace: group) }
-    let(:members_project) { create(:project) }
-    let(:shared_project) { create(:project) }
+    let(:personal_project) { create(:empty_project, namespace: developer.namespace) }
+    let(:group_project) { create(:empty_project, namespace: group) }
+    let(:members_project) { create(:empty_project) }
+    let(:shared_project) { create(:empty_project) }
 
     before do
       group.add_master(master)
@@ -330,7 +330,7 @@ describe ProjectTeam, models: true do
         reporter = create(:user)
         promoted_guest = create(:user)
         guest = create(:user)
-        project = create(:project)
+        project = create(:empty_project)
 
         project.add_master(master)
         project.add_reporter(reporter)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 99ca53938c8f38518a3d015e4abf163bdfea4848..53b98ba05f87a084795d4b09589e98170024bd61 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -4,7 +4,7 @@ describe Repository, models: true do
   include RepoHelpers
   TestBlob = Struct.new(:name)
 
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:repository) { project.repository }
   let(:user) { create(:user) }
 
@@ -15,7 +15,12 @@ describe Repository, models: true do
 
   let(:merge_commit) do
     merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
-    merge_commit_id = repository.merge(user, merge_request, commit_options)
+
+    merge_commit_id = repository.merge(user,
+                                       merge_request.diff_head_sha,
+                                       merge_request,
+                                       commit_options)
+
     repository.commit(merge_commit_id)
   end
 
@@ -90,6 +95,30 @@ describe Repository, models: true do
 
         it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
       end
+
+      context 'annotated tag pointing to a blob' do
+        let(:annotated_tag_name) { 'annotated-tag' }
+
+        subject { repository.tags_sorted_by('updated_asc').map(&:name) }
+
+        before do
+          options = { message: 'test tag message\n',
+                      tagger: { name: 'John Smith', email: 'john@gmail.com' } }
+          repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
+
+          double_first = double(committed_date: Time.now - 1.second)
+          double_last = double(committed_date: Time.now)
+
+          allow(tag_a).to receive(:dereferenced_target).and_return(double_last)
+          allow(tag_b).to receive(:dereferenced_target).and_return(double_first)
+        end
+
+        it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) }
+
+        after do
+          repository.rugged.tags.delete(annotated_tag_name)
+        end
+      end
     end
   end
 
@@ -265,17 +294,39 @@ describe Repository, models: true do
   describe "#commit_dir" do
     it "commits a change that creates a new directory" do
       expect do
-        repository.commit_dir(user, 'newdir', 'Create newdir', 'master')
+        repository.commit_dir(user, 'newdir',
+          message: 'Create newdir', branch_name: 'master')
       end.to change { repository.commits('master').count }.by(1)
 
       newdir = repository.tree('master', 'newdir')
       expect(newdir.path).to eq('newdir')
     end
 
+    context "when committing to another project" do
+      let(:forked_project) { create(:project) }
+
+      it "creates a fork and commit to the forked project" do
+        expect do
+          repository.commit_dir(user, 'newdir',
+            message: 'Create newdir', branch_name: 'patch',
+            start_branch_name: 'master', start_project: forked_project)
+        end.to change { repository.commits('master').count }.by(0)
+
+        expect(repository.branch_exists?('patch')).to be_truthy
+        expect(forked_project.repository.branch_exists?('patch')).to be_falsy
+
+        newdir = repository.tree('patch', 'newdir')
+        expect(newdir.path).to eq('newdir')
+      end
+    end
+
     context "when an author is specified" do
       it "uses the given email/name to set the commit's author" do
         expect do
-          repository.commit_dir(user, "newdir", "Add newdir", 'master', author_email: author_email, author_name: author_name)
+          repository.commit_dir(user, 'newdir',
+            message: 'Add newdir',
+            branch_name: 'master',
+            author_email: author_email, author_name: author_name)
         end.to change { repository.commits('master').count }.by(1)
 
         last_commit = repository.commit
@@ -290,8 +341,9 @@ describe Repository, models: true do
     it 'commits change to a file successfully' do
       expect do
         repository.commit_file(user, 'CHANGELOG', 'Changelog!',
-                              'Updates file content',
-                              'master', true)
+                               message: 'Updates file content',
+                               branch_name: 'master',
+                               update: true)
       end.to change { repository.commits('master').count }.by(1)
 
       blob = repository.blob_at('master', 'CHANGELOG')
@@ -302,8 +354,12 @@ describe Repository, models: true do
     context "when an author is specified" do
       it "uses the given email/name to set the commit's author" do
         expect do
-          repository.commit_file(user, "README", 'README!', 'Add README',
-                                'master', true, author_email: author_email, author_name: author_name)
+          repository.commit_file(user, 'README', 'README!',
+                                 message: 'Add README',
+                                 branch_name: 'master',
+                                 update: true,
+                                 author_email: author_email,
+                                 author_name: author_name)
         end.to change { repository.commits('master').count }.by(1)
 
         last_commit = repository.commit
@@ -318,7 +374,7 @@ describe Repository, models: true do
     it 'updates filename successfully' do
       expect do
         repository.update_file(user, 'NEWLICENSE', 'Copyright!',
-                                     branch: 'master',
+                                     branch_name: 'master',
                                      previous_path: 'LICENSE',
                                      message: 'Changes filename')
       end.to change { repository.commits('master').count }.by(1)
@@ -331,15 +387,16 @@ describe Repository, models: true do
 
     context "when an author is specified" do
       it "uses the given email/name to set the commit's author" do
-        repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+        repository.commit_file(user, 'README', 'README!',
+          message: 'Add README', branch_name: 'master', update: true)
 
         expect do
-          repository.update_file(user, 'README', "Updated README!",
-                                branch: 'master',
-                                previous_path: 'README',
-                                message: 'Update README',
-                                author_email: author_email,
-                                author_name: author_name)
+          repository.update_file(user, 'README', 'Updated README!',
+                                 branch_name: 'master',
+                                 previous_path: 'README',
+                                 message: 'Update README',
+                                 author_email: author_email,
+                                 author_name: author_name)
         end.to change { repository.commits('master').count }.by(1)
 
         last_commit = repository.commit
@@ -352,10 +409,12 @@ describe Repository, models: true do
 
   describe "#remove_file" do
     it 'removes file successfully' do
-      repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+      repository.commit_file(user, 'README', 'README!',
+        message: 'Add README', branch_name: 'master', update: true)
 
       expect do
-        repository.remove_file(user, "README", "Remove README", 'master')
+        repository.remove_file(user, 'README',
+          message: 'Remove README', branch_name: 'master')
       end.to change { repository.commits('master').count }.by(1)
 
       expect(repository.blob_at('master', 'README')).to be_nil
@@ -363,10 +422,13 @@ describe Repository, models: true do
 
     context "when an author is specified" do
       it "uses the given email/name to set the commit's author" do
-        repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+        repository.commit_file(user, 'README', 'README!',
+          message: 'Add README', branch_name: 'master', update: true)
 
         expect do
-          repository.remove_file(user, "README", "Remove README", 'master', author_email: author_email, author_name: author_name)
+          repository.remove_file(user, 'README',
+            message: 'Remove README', branch_name: 'master',
+            author_email: author_email, author_name: author_name)
         end.to change { repository.commits('master').count }.by(1)
 
         last_commit = repository.commit
@@ -514,11 +576,14 @@ describe Repository, models: true do
 
   describe "#license_blob", caching: true do
     before do
-      repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
+      repository.remove_file(
+        user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
     end
 
     it 'handles when HEAD points to non-existent ref' do
-      repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+      repository.commit_file(
+        user, 'LICENSE', 'Copyright!',
+        message: 'Add LICENSE', branch_name: 'master', update: false)
 
       allow(repository).to receive(:file_on_head).
         and_raise(Rugged::ReferenceError)
@@ -527,21 +592,27 @@ describe Repository, models: true do
     end
 
     it 'looks in the root_ref only' do
-      repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown')
-      repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false)
+      repository.remove_file(user, 'LICENSE',
+        message: 'Remove LICENSE', branch_name: 'markdown')
+      repository.commit_file(user, 'LICENSE',
+        Licensee::License.new('mit').content,
+        message: 'Add LICENSE', branch_name: 'markdown', update: false)
 
       expect(repository.license_blob).to be_nil
     end
 
     it 'detects license file with no recognizable open-source license content' do
-      repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+      repository.commit_file(user, 'LICENSE', 'Copyright!',
+        message: 'Add LICENSE', branch_name: 'master', update: false)
 
       expect(repository.license_blob.name).to eq('LICENSE')
     end
 
     %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
       it "detects '#{filename}'" do
-        repository.commit_file(user, filename, Licensee::License.new('mit').content, "Add #{filename}", 'master', false)
+        repository.commit_file(user, filename,
+          Licensee::License.new('mit').content,
+          message: "Add #{filename}", branch_name: 'master', update: false)
 
         expect(repository.license_blob.name).to eq(filename)
       end
@@ -550,7 +621,8 @@ describe Repository, models: true do
 
   describe '#license_key', caching: true do
     before do
-      repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
+      repository.remove_file(user, 'LICENSE',
+        message: 'Remove LICENSE', branch_name: 'master')
     end
 
     it 'returns nil when no license is detected' do
@@ -564,13 +636,16 @@ describe Repository, models: true do
     end
 
     it 'detects license file with no recognizable open-source license content' do
-      repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+      repository.commit_file(user, 'LICENSE', 'Copyright!',
+        message: 'Add LICENSE', branch_name: 'master', update: false)
 
       expect(repository.license_key).to be_nil
     end
 
     it 'returns the license key' do
-      repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
+      repository.commit_file(user, 'LICENSE',
+        Licensee::License.new('mit').content,
+        message: 'Add LICENSE', branch_name: 'master', update: false)
 
       expect(repository.license_key).to eq('mit')
     end
@@ -683,7 +758,7 @@ describe Repository, models: true do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
 
         expect do
-          repository.rm_branch(user, 'new_feature')
+          repository.rm_branch(user, 'feature')
         end.to raise_error(GitHooksService::PreReceiveError)
       end
 
@@ -704,36 +779,51 @@ describe Repository, models: true do
 
     context 'when pre hooks were successful' do
       before do
-        expect_any_instance_of(GitHooksService).to receive(:execute).
-          with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature').
-          and_yield.and_return(true)
+        service = GitHooksService.new
+        expect(GitHooksService).to receive(:new).and_return(service)
+        expect(service).to receive(:execute).
+          with(
+            user,
+            repository.path_to_repo,
+            old_rev,
+            new_rev,
+            'refs/heads/feature').
+          and_yield(service).and_return(true)
       end
 
       it 'runs without errors' do
         expect do
-          repository.update_branch_with_hooks(user, 'feature') { new_rev }
+          GitOperationService.new(user, repository).with_branch('feature') do
+            new_rev
+          end
         end.not_to raise_error
       end
 
       it 'ensures the autocrlf Git option is set to :input' do
-        expect(repository).to receive(:update_autocrlf_option)
+        service = GitOperationService.new(user, repository)
 
-        repository.update_branch_with_hooks(user, 'feature') { new_rev }
+        expect(service).to receive(:update_autocrlf_option)
+
+        service.with_branch('feature') { new_rev }
       end
 
       context "when the branch wasn't empty" do
         it 'updates the head' do
           expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
-          repository.update_branch_with_hooks(user, 'feature') { new_rev }
+
+          GitOperationService.new(user, repository).with_branch('feature') do
+            new_rev
+          end
+
           expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev)
         end
       end
     end
 
     context 'when the update adds more than one commit' do
-      it 'runs without errors' do
-        old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+      let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
 
+      it 'runs without errors' do
         # old_rev is an ancestor of new_rev
         expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)
 
@@ -743,22 +833,28 @@ describe Repository, models: true do
         branch = 'feature-ff-target'
         repository.add_branch(user, branch, old_rev)
 
-        expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error
+        expect do
+          GitOperationService.new(user, repository).with_branch(branch) do
+            new_rev
+          end
+        end.not_to raise_error
       end
     end
 
     context 'when the update would remove commits from the target branch' do
-      it 'raises an exception' do
-        branch = 'master'
-        old_rev = repository.find_branch(branch).dereferenced_target.sha
+      let(:branch) { 'master' }
+      let(:old_rev) { repository.find_branch(branch).dereferenced_target.sha }
 
+      it 'raises an exception' do
         # The 'master' branch is NOT an ancestor of new_rev.
         expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)
 
         # Updating 'master' to new_rev would lose the commits on 'master' that
         # are not contained in new_rev. This should not be allowed.
         expect do
-          repository.update_branch_with_hooks(user, branch) { new_rev }
+          GitOperationService.new(user, repository).with_branch(branch) do
+            new_rev
+          end
         end.to raise_error(Repository::CommitError)
       end
     end
@@ -768,7 +864,9 @@ describe Repository, models: true do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
 
         expect do
-          repository.update_branch_with_hooks(user, 'feature') { new_rev }
+          GitOperationService.new(user, repository).with_branch('feature') do
+            new_rev
+          end
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
@@ -776,7 +874,6 @@ describe Repository, models: true do
     context 'when target branch is different from source branch' do
       before do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
-        allow(repository).to receive(:update_ref!)
       end
 
       it 'expires branch cache' do
@@ -785,7 +882,10 @@ describe Repository, models: true do
         expect(repository).not_to receive(:expire_emptiness_caches)
         expect(repository).to     receive(:expire_branches_cache)
 
-        repository.update_branch_with_hooks(user, 'new-feature') { new_rev }
+        GitOperationService.new(user, repository).
+          with_branch('new-feature') do
+            new_rev
+          end
       end
     end
 
@@ -803,7 +903,9 @@ describe Repository, models: true do
         expect(empty_repository).to receive(:expire_branches_cache)
 
         empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
-                                     'Updates file content', 'master', false)
+                                     message: 'Updates file content',
+                                     branch_name: 'master',
+                                     update: false)
       end
     end
   end
@@ -853,7 +955,7 @@ describe Repository, models: true do
       end
 
       it 'sets autocrlf to :input' do
-        repository.update_autocrlf_option
+        GitOperationService.new(nil, repository).send(:update_autocrlf_option)
 
         expect(repository.raw_repository.autocrlf).to eq(:input)
       end
@@ -868,7 +970,7 @@ describe Repository, models: true do
         expect(repository.raw_repository).not_to receive(:autocrlf=).
           with(:input)
 
-        repository.update_autocrlf_option
+        GitOperationService.new(nil, repository).send(:update_autocrlf_option)
       end
     end
   end
@@ -985,8 +1087,11 @@ describe Repository, models: true do
 
     it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
       merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
-      merge_commit_id = repository.merge(user, merge_request, commit_options)
-      repository.commit(merge_commit_id)
+
+      merge_commit_id = repository.merge(user,
+                                         merge_request.diff_head_sha,
+                                         merge_request,
+                                         commit_options)
 
       expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
     end
@@ -1364,9 +1469,10 @@ describe Repository, models: true do
   describe '#rm_tag' do
     it 'removes a tag' do
       expect(repository).to receive(:before_remove_tag)
-      expect(repository.rugged.tags).to receive(:delete).with('v1.1.0')
 
-      repository.rm_tag('v1.1.0')
+      repository.rm_tag(create(:user), 'v1.1.0')
+
+      expect(repository.find_tag('v1.1.0')).to be_nil
     end
   end
 
@@ -1434,16 +1540,16 @@ describe Repository, models: true do
     end
   end
 
-  describe '#update_ref!' do
+  describe '#update_ref' do
     it 'can create a ref' do
-      repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+      GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
 
       expect(repository.find_branch('foobar')).not_to be_nil
     end
 
     it 'raises CommitError when the ref update fails' do
       expect do
-        repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+        GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
       end.to raise_error(Repository::CommitError)
     end
   end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 8481a9bef1632a81f6126b9de20798fb700ef043..dd2a5109abcca7c2dabd0878e6daf1a7c6dade68 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -14,7 +14,7 @@ describe Route, models: true do
     it { is_expected.to validate_uniqueness_of(:path) }
   end
 
-  describe '#rename_children' do
+  describe '#rename_descendants' do
     let!(:nested_group) { create(:group, path: "test", parent: group) }
     let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
     let!(:similar_group) { create(:group, path: 'gitlab-org') }
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 691511cd93f369105ad2807954075af2f21e8ad5..0e2f07e945ff94a62eb2f9808a3ebdfaf9ff63ce 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -12,7 +12,7 @@ describe Service, models: true do
     end
 
     describe "Testable" do
-      let(:project) { create :project }
+      let(:project) { create(:project, :repository) }
 
       before do
         allow(@service).to receive(:project).and_return(project)
@@ -35,7 +35,7 @@ describe Service, models: true do
     end
 
     describe "With commits" do
-      let(:project) { create :project }
+      let(:project) { create(:project, :repository) }
 
       before do
         allow(@service).to receive(:project).and_return(project)
@@ -60,7 +60,7 @@ describe Service, models: true do
             api_key: '123456789'
           })
       end
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
 
       describe 'is prefilled for projects pushover service' do
         it "has all fields prefilled" do
@@ -79,7 +79,7 @@ describe Service, models: true do
   describe "{property}_changed?" do
     let(:service) do
       BambooService.create(
-        project: create(:project),
+        project: create(:empty_project),
         properties: {
           bamboo_url: 'http://gitlab.com',
           username: 'mic',
@@ -119,7 +119,7 @@ describe Service, models: true do
   describe "{property}_touched?" do
     let(:service) do
       BambooService.create(
-        project: create(:project),
+        project: create(:empty_project),
         properties: {
           bamboo_url: 'http://gitlab.com',
           username: 'mic',
@@ -159,7 +159,7 @@ describe Service, models: true do
   describe "{property}_was" do
     let(:service) do
       BambooService.create(
-        project: create(:project),
+        project: create(:empty_project),
         properties: {
           bamboo_url: 'http://gitlab.com',
           username: 'mic',
@@ -199,7 +199,7 @@ describe Service, models: true do
   describe 'initialize service with no properties' do
     let(:service) do
       GitlabIssueTrackerService.create(
-        project: create(:project),
+        project: create(:empty_project),
         title: 'random title'
       )
     end
@@ -214,7 +214,7 @@ describe Service, models: true do
   end
 
   describe "callbacks" do
-    let(:project) { create(:project) }
+    let(:project) { create(:empty_project) }
     let!(:service) do
       RedmineService.new(
         project: project,
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 7425a897769d2b6810c9b8f1332f13edd9dbf316..219ab1989ea125c46f96ae4c1f4ea1cdcbbe5a4c 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -42,7 +42,7 @@ describe Snippet, models: true do
       end
 
       it 'supports a cross-project reference' do
-        another_project = build(:project, name: 'another-project', namespace: project.namespace)
+        another_project = build(:empty_project, name: 'another-project', namespace: project.namespace)
         expect(snippet.to_reference(another_project)).to eq "sample-project$1"
       end
     end
@@ -55,7 +55,7 @@ describe Snippet, models: true do
       end
 
       it 'still returns shortest reference when project arg present' do
-        another_project = build(:project, name: 'another-project')
+        another_project = build(:empty_project, name: 'another-project')
         expect(snippet.to_reference(another_project)).to eq "$1"
       end
     end
@@ -173,7 +173,7 @@ describe Snippet, models: true do
   end
 
   describe '#participants' do
-    let(:project) { create(:project, :public) }
+    let(:project) { create(:empty_project, :public) }
     let(:snippet) { create(:snippet, content: 'foo', project: project) }
 
     let!(:note1) do
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 623b82c01d82c8dd1488c1d76554f5c8a251522e..581305ad39f06e6f03b1a4382764ec42d990c1ee 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Todo, models: true do
-  let(:project) { create(:project) }
-  let(:commit) { project.commit }
   let(:issue) { create(:issue) }
 
   describe 'relationships' do
@@ -82,6 +80,9 @@ describe Todo, models: true do
 
   describe '#target' do
     context 'for commits' do
+      let(:project) { create(:project, :repository) }
+      let(:commit) { project.commit }
+
       it 'returns an instance of Commit when exists' do
         subject.project = project
         subject.target_type = 'Commit'
@@ -108,17 +109,20 @@ describe Todo, models: true do
   end
 
   describe '#target_reference' do
-    it 'returns the short commit id for commits' do
+    it 'returns commit full reference with short id' do
+      project = create(:project, :repository)
+      commit = project.commit
+
       subject.project = project
       subject.target_type = 'Commit'
       subject.commit_id = commit.id
 
-      expect(subject.target_reference).to eq commit.short_id
+      expect(subject.target_reference).to eq commit.reference_link_text(full: true)
     end
 
-    it 'returns reference for issuables' do
+    it 'returns full reference for issuables' do
       subject.target = issue
-      expect(subject.target_reference).to eq issue.to_reference
+      expect(subject.target_reference).to eq issue.to_reference(full: true)
     end
   end
 end
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 0737999e1254e17562764a6631ec4a9ae6a5214c..a87983b749210d3bf38a794d4b7b4a34ddf3b745 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Tree, models: true do
-  let(:repository) { create(:project).repository }
+  let(:repository) { create(:project, :repository).repository }
   let(:sha) { repository.root_ref }
 
   subject { described_class.new(repository, '54fcc214') }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ca3d4ff0aa9cf813953e111bc8d3645e32e20967..6ca5ad747d123bce779a380d7c3b3febbe0488a8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -797,14 +797,14 @@ describe User, models: true do
   describe '#avatar_type' do
     let(:user) { create(:user) }
 
-    it "is true if avatar is image" do
+    it 'is true if avatar is image' do
       user.update_attribute(:avatar, 'uploads/avatar.png')
       expect(user.avatar_type).to be_truthy
     end
 
-    it "is false if avatar is html page" do
+    it 'is false if avatar is html page' do
       user.update_attribute(:avatar, 'uploads/avatar.html')
-      expect(user.avatar_type).to eq(["only images allowed"])
+      expect(user.avatar_type).to eq(['only images allowed'])
     end
   end
 
@@ -926,8 +926,8 @@ describe User, models: true do
     end
   end
 
-  describe "#starred?" do
-    it "determines if user starred a project" do
+  describe '#starred?' do
+    it 'determines if user starred a project' do
       user = create :user
       project1 = create(:empty_project, :public)
       project2 = create(:empty_project, :public)
@@ -953,8 +953,8 @@ describe User, models: true do
     end
   end
 
-  describe "#toggle_star" do
-    it "toggles stars" do
+  describe '#toggle_star' do
+    it 'toggles stars' do
       user = create :user
       project = create(:empty_project, :public)
 
@@ -966,31 +966,44 @@ describe User, models: true do
     end
   end
 
-  describe "#sort" do
+  describe '#sort' do
     before do
       User.delete_all
       @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
       @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
+      @user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta'
     end
 
-    it "sorts users by the recent sign-in time" do
-      expect(User.sort('recent_sign_in').first).to eq(@user)
+    context 'when sort by recent_sign_in' do
+      it 'sorts users by the recent sign-in time' do
+        expect(User.sort('recent_sign_in').first).to eq(@user)
+      end
+
+      it 'pushes users who never signed in to the end' do
+        expect(User.sort('recent_sign_in').third).to eq(@user2)
+      end
     end
 
-    it "sorts users by the oldest sign-in time" do
-      expect(User.sort('oldest_sign_in').first).to eq(@user1)
+    context 'when sort by oldest_sign_in' do
+      it 'sorts users by the oldest sign-in time' do
+        expect(User.sort('oldest_sign_in').first).to eq(@user1)
+      end
+
+      it 'pushes users who never signed in to the end' do
+        expect(User.sort('oldest_sign_in').third).to eq(@user2)
+      end
     end
 
-    it "sorts users in descending order by their creation time" do
+    it 'sorts users in descending order by their creation time' do
       expect(User.sort('created_desc').first).to eq(@user)
     end
 
-    it "sorts users in ascending order by their creation time" do
-      expect(User.sort('created_asc').first).to eq(@user1)
+    it 'sorts users in ascending order by their creation time' do
+      expect(User.sort('created_asc').first).to eq(@user2)
     end
 
-    it "sorts users by id in descending order when nil is passed" do
-      expect(User.sort(nil).first).to eq(@user1)
+    it 'sorts users by id in descending order when nil is passed' do
+      expect(User.sort(nil).first).to eq(@user2)
     end
   end
 
@@ -1350,6 +1363,39 @@ describe User, models: true do
     end
   end
 
+  describe '#nested_groups' do
+    let!(:user) { create(:user) }
+    let!(:group) { create(:group) }
+    let!(:nested_group) { create(:group, parent: group) }
+
+    before do
+      group.add_owner(user)
+
+      # Add more data to ensure method does not include wrong groups
+      create(:group).add_owner(create(:user))
+    end
+
+    it { expect(user.nested_groups).to eq([nested_group]) }
+  end
+
+  describe '#nested_projects' do
+    let!(:user) { create(:user) }
+    let!(:group) { create(:group) }
+    let!(:nested_group) { create(:group, parent: group) }
+    let!(:project) { create(:empty_project, namespace: group) }
+    let!(:nested_project) { create(:empty_project, namespace: nested_group) }
+
+    before do
+      group.add_owner(user)
+
+      # Add more data to ensure method does not include wrong projects
+      other_project = create(:empty_project, namespace: create(:group, :nested))
+      other_project.add_developer(create(:user))
+    end
+
+    it { expect(user.nested_projects).to eq([nested_project]) }
+  end
+
   describe '#refresh_authorized_projects', redis: true do
     let(:project1) { create(:empty_project) }
     let(:project2) { create(:empty_project) }
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0f280f32eac91fed932385e62e3176e018f44e91
--- /dev/null
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Ci::BuildPolicy, :models do
+  let(:user) { create(:user) }
+  let(:build) { create(:ci_build, pipeline: pipeline) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+  let(:policies) do
+    described_class.abilities(user, build).to_set
+  end
+
+  shared_context 'public pipelines disabled' do
+    before { project.update_attribute(:public_builds, false) }
+  end
+
+  describe '#rules' do
+    context 'when user does not have access to the project' do
+      let(:project) { create(:empty_project, :private) }
+
+      context 'when public builds are enabled' do
+        it 'does not include ability to read build' do
+          expect(policies).not_to include :read_build
+        end
+      end
+
+      context 'when public builds are disabled' do
+        include_context 'public pipelines disabled'
+
+        it 'does not include ability to read build' do
+          expect(policies).not_to include :read_build
+        end
+      end
+    end
+
+    context 'when anonymous user has access to the project' do
+      let(:project) { create(:empty_project, :public) }
+
+      context 'when public builds are enabled' do
+        it 'includes ability to read build' do
+          expect(policies).to include :read_build
+        end
+      end
+
+      context 'when public builds are disabled' do
+        include_context 'public pipelines disabled'
+
+        it 'does not include ability to read build' do
+          expect(policies).not_to include :read_build
+        end
+      end
+    end
+
+    context 'when team member has access to the project' do
+      let(:project) { create(:empty_project, :public) }
+
+      context 'team member is a guest' do
+        before { project.team << [user, :guest] }
+
+        context 'when public builds are enabled' do
+          it 'includes ability to read build' do
+            expect(policies).to include :read_build
+          end
+        end
+
+        context 'when public builds are disabled' do
+          include_context 'public pipelines disabled'
+
+          it 'does not include ability to read build' do
+            expect(policies).not_to include :read_build
+          end
+        end
+      end
+
+      context 'team member is a reporter' do
+        before { project.team << [user, :reporter] }
+
+        context 'when public builds are enabled' do
+          it 'includes ability to read build' do
+            expect(policies).to include :read_build
+          end
+        end
+
+        context 'when public builds are disabled' do
+          include_context 'public pipelines disabled'
+
+          it 'does not include ability to read build' do
+            expect(policies).to include :read_build
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 2878e0cb59b1a4e28c874843e14ba0c41104ed00..5a3ffc284f212341dd9ae3a60927aa86a87e1d45 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -6,7 +6,7 @@ describe API::Branches, api: true  do
 
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
-  let!(:project) { create(:project, creator_id: user.id) }
+  let!(:project) { create(:project, :repository, creator: user) }
   let!(:master) { create(:project_member, :master, user: user, project: project) }
   let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
   let!(:branch_name) { 'feature' }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 7be7acebb194aa65a66ffb8eec5b9d748bac7c69..f197fadebabaeef4a2ead3766f237f26234fbed1 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -5,7 +5,7 @@ describe API::Builds, api: true do
 
   let(:user) { create(:user) }
   let(:api_user) { user }
-  let!(:project) { create(:project, creator_id: user.id, public_builds: false) }
+  let!(:project) { create(:project, :repository, creator: user, public_builds: false) }
   let!(:developer) { create(:project_member, :developer, user: user, project: project) }
   let(:reporter) { create(:project_member, :reporter, project: project) }
   let(:guest) { create(:project_member, :guest, project: project) }
@@ -67,7 +67,7 @@ describe API::Builds, api: true do
     context 'unauthorized user' do
       let(:api_user) { nil }
 
-      it 'should not return project builds' do
+      it 'does not return project builds' do
         expect(response).to have_http_status(401)
       end
     end
@@ -86,7 +86,7 @@ describe API::Builds, api: true do
 
     context 'when commit exists in repository' do
       context 'when user is authorized' do
-        context 'when pipeline has builds' do
+        context 'when pipeline has jobs' do
           before do
             create(:ci_pipeline, project: project, sha: project.commit.id)
             create(:ci_build, pipeline: pipeline)
@@ -95,7 +95,7 @@ describe API::Builds, api: true do
             get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
           end
 
-          it 'returns project builds for specific commit' do
+          it 'returns project jobs for specific commit' do
             expect(response).to have_http_status(200)
             expect(json_response).to be_an Array
             expect(json_response.size).to eq 2
@@ -111,7 +111,7 @@ describe API::Builds, api: true do
           end
         end
 
-        context 'when pipeline has no builds' do
+        context 'when pipeline has no jobs' do
           before do
             branch_head = project.commit('feature').id
             get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user)
@@ -133,7 +133,7 @@ describe API::Builds, api: true do
           get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
         end
 
-        it 'does not return project builds' do
+        it 'does not return project jobs' do
           expect(response).to have_http_status(401)
           expect(json_response.except('message')).to be_empty
         end
@@ -147,7 +147,7 @@ describe API::Builds, api: true do
     end
 
     context 'authorized user' do
-      it 'returns specific build data' do
+      it 'returns specific job data' do
         expect(response).to have_http_status(200)
         expect(json_response['name']).to eq('test')
       end
@@ -165,7 +165,7 @@ describe API::Builds, api: true do
     context 'unauthorized user' do
       let(:api_user) { nil }
 
-      it 'does not return specific build data' do
+      it 'does not return specific job data' do
         expect(response).to have_http_status(401)
       end
     end
@@ -176,7 +176,7 @@ describe API::Builds, api: true do
       get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
     end
 
-    context 'build with artifacts' do
+    context 'job with artifacts' do
       let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
 
       context 'authorized user' do
@@ -185,7 +185,7 @@ describe API::Builds, api: true do
             'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
         end
 
-        it 'returns specific build artifacts' do
+        it 'returns specific job artifacts' do
           expect(response).to have_http_status(200)
           expect(response.headers).to include(download_headers)
         end
@@ -194,13 +194,13 @@ describe API::Builds, api: true do
       context 'unauthorized user' do
         let(:api_user) { nil }
 
-        it 'does not return specific build artifacts' do
+        it 'does not return specific job artifacts' do
           expect(response).to have_http_status(401)
         end
       end
     end
 
-    it 'does not return build artifacts if not uploaded' do
+    it 'does not return job artifacts if not uploaded' do
       expect(response).to have_http_status(404)
     end
   end
@@ -241,7 +241,7 @@ describe API::Builds, api: true do
       end
     end
 
-    context 'non-existing build' do
+    context 'non-existing job' do
       shared_examples 'not found' do
         it { expect(response).to have_http_status(:not_found) }
       end
@@ -254,7 +254,7 @@ describe API::Builds, api: true do
         it_behaves_like 'not found'
       end
 
-      context 'has no such build' do
+      context 'has no such job' do
         before do
           get path_for_ref(pipeline.ref, 'NOBUILD')
         end
@@ -263,7 +263,7 @@ describe API::Builds, api: true do
       end
     end
 
-    context 'find proper build' do
+    context 'find proper job' do
       shared_examples 'a valid file' do
         let(:download_headers) do
           { 'Content-Transfer-Encoding' => 'binary',
@@ -311,7 +311,7 @@ describe API::Builds, api: true do
     end
 
     context 'authorized user' do
-      it 'returns specific build trace' do
+      it 'returns specific job trace' do
         expect(response).to have_http_status(200)
         expect(response.body).to eq(build.trace)
       end
@@ -320,7 +320,7 @@ describe API::Builds, api: true do
     context 'unauthorized user' do
       let(:api_user) { nil }
 
-      it 'does not return specific build trace' do
+      it 'does not return specific job trace' do
         expect(response).to have_http_status(401)
       end
     end
@@ -333,7 +333,7 @@ describe API::Builds, api: true do
 
     context 'authorized user' do
       context 'user with :update_build persmission' do
-        it 'cancels running or pending build' do
+        it 'cancels running or pending job' do
           expect(response).to have_http_status(201)
           expect(project.builds.first.status).to eq('canceled')
         end
@@ -342,7 +342,7 @@ describe API::Builds, api: true do
       context 'user without :update_build permission' do
         let(:api_user) { reporter.user }
 
-        it 'does not cancel build' do
+        it 'does not cancel job' do
           expect(response).to have_http_status(403)
         end
       end
@@ -351,7 +351,7 @@ describe API::Builds, api: true do
     context 'unauthorized user' do
       let(:api_user) { nil }
 
-      it 'does not cancel build' do
+      it 'does not cancel job' do
         expect(response).to have_http_status(401)
       end
     end
@@ -366,7 +366,7 @@ describe API::Builds, api: true do
 
     context 'authorized user' do
       context 'user with :update_build permission' do
-        it 'retries non-running build' do
+        it 'retries non-running job' do
           expect(response).to have_http_status(201)
           expect(project.builds.first.status).to eq('canceled')
           expect(json_response['status']).to eq('pending')
@@ -376,7 +376,7 @@ describe API::Builds, api: true do
       context 'user without :update_build permission' do
         let(:api_user) { reporter.user }
 
-        it 'does not retry build' do
+        it 'does not retry job' do
           expect(response).to have_http_status(403)
         end
       end
@@ -385,7 +385,7 @@ describe API::Builds, api: true do
     context 'unauthorized user' do
       let(:api_user) { nil }
 
-      it 'does not retry build' do
+      it 'does not retry job' do
         expect(response).to have_http_status(401)
       end
     end
@@ -396,23 +396,23 @@ describe API::Builds, api: true do
       post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
     end
 
-    context 'build is erasable' do
+    context 'job is erasable' do
       let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
 
-      it 'erases build content' do
+      it 'erases job content' do
         expect(response.status).to eq 201
         expect(build.trace).to be_empty
         expect(build.artifacts_file.exists?).to be_falsy
         expect(build.artifacts_metadata.exists?).to be_falsy
       end
 
-      it 'updates build' do
+      it 'updates job' do
         expect(build.reload.erased_at).to be_truthy
         expect(build.reload.erased_by).to eq user
       end
     end
 
-    context 'build is not erasable' do
+    context 'job is not erasable' do
       let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
 
       it 'responds with forbidden' do
@@ -452,20 +452,20 @@ describe API::Builds, api: true do
       post api("/projects/#{project.id}/builds/#{build.id}/play", user)
     end
 
-    context 'on an playable build' do
+    context 'on an playable job' do
       let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
 
-      it 'plays the build' do
+      it 'plays the job' do
         expect(response).to have_http_status 200
         expect(json_response['user']['id']).to eq(user.id)
         expect(json_response['id']).to eq(build.id)
       end
     end
 
-    context 'on a non-playable build' do
+    context 'on a non-playable job' do
       it 'returns a status code 400, Bad Request' do
         expect(response).to have_http_status 400
-        expect(response.body).to match("Unplayable Build")
+        expect(response.body).to match("Unplayable Job")
       end
     end
   end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index c1c7c0882decb8fbe2fc0233fad07744b433e57e..88361def3cf18999a64e5ded985a8f288a662bf9 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe API::CommitStatuses, api: true do
   include ApiHelpers
 
-  let!(:project) { create(:project) }
+  let!(:project) { create(:project, :repository) }
   let(:commit) { project.repository.commit }
   let(:commit_status) { create(:commit_status, pipeline: pipeline) }
   let(:guest) { create_user(:guest) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 7f8ea5251f0dabbc8bb48993d3e1eff2c937345b..af9028a89788d100d4b6a9eda18b8f69b30c18fe 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -5,7 +5,7 @@ describe API::Commits, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
-  let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+  let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
   let!(:master) { create(:project_member, :master, user: user, project: project) }
   let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
   let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 5c14db067a8cab9e6031d67c3ac128de36059430..766234d7104d0cd2f99451df9e979603e16fa90f 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -73,19 +73,14 @@ describe API::DeployKeys, api: true  do
       post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' }
 
       expect(response).to have_http_status(400)
-      expect(json_response['message']['key']).to eq([
-        'can\'t be blank',
-        'is invalid'
-      ])
+      expect(json_response['error']).to eq('key is missing')
     end
 
     it 'should not create a key without title' do
       post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key'
 
       expect(response).to have_http_status(400)
-      expect(json_response['message']['title']).to eq([
-        'can\'t be blank'
-      ])
+      expect(json_response['error']).to eq('title is missing')
     end
 
     it 'should create new ssh key' do
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 685da28c6737c41936697b62cdf1a12a531ebd38..5e26e779366d6e2182fc4851e5187979449f3b69 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
 describe API::Files, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
-  let!(:project) { create(:project, namespace: user.namespace ) }
-  let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
+  let!(:project) { create(:project, :repository, namespace: user.namespace ) }
+  let(:guest) { create(:user) { |u| project.add_guest(u) } }
   let(:file_path) { 'files/ruby/popen.rb' }
   let(:params) do
     {
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index df29099bc2fc17339a942a771a360b0bb6b97c51..92ac4fd334d2f697dbcf22b07515d9e974c71f20 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -4,7 +4,6 @@ describe API::Projects, api: true  do
   include ApiHelpers
   let(:user)  { create(:user) }
   let(:user2) { create(:user) }
-  let(:user3) { create(:user) }
   let(:admin) { create(:admin) }
   let(:group) { create(:group) }
   let(:group2) do
@@ -13,17 +12,14 @@ describe API::Projects, api: true  do
     group
   end
 
-  let(:project) do
-    create(:project, creator_id: user.id, namespace: user.namespace)
-  end
-
-  let(:project_user2) do
-    create(:project_member, :reporter, user: user2, project: project)
-  end
-
   describe 'POST /projects/fork/:id' do
-    before { project_user2 }
-    before { user3 }
+    let(:project) do
+      create(:project, :repository, creator: user, namespace: user.namespace)
+    end
+
+    before do
+      project.add_reporter(user2)
+    end
 
     context 'when authenticated' do
       it 'forks if user has sufficient access to project' do
@@ -49,7 +45,8 @@ describe API::Projects, api: true  do
       end
 
       it 'fails on missing project access for the project to fork' do
-        post api("/projects/fork/#{project.id}", user3)
+        new_user = create(:user)
+        post api("/projects/fork/#{project.id}", new_user)
 
         expect(response).to have_http_status(404)
         expect(json_response['message']).to eq('404 Project Not Found')
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index edbf014058327ed6a464484f336f052c2e97590b..a027c23bb8808dbbe3e49091807865ef6601562b 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -176,6 +176,9 @@ describe API::Groups, api: true  do
         expect(json_response['visibility_level']).to eq(group1.visibility_level)
         expect(json_response['avatar_url']).to eq(group1.avatar_url)
         expect(json_response['web_url']).to eq(group1.web_url)
+        expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
+        expect(json_response['full_name']).to eq(group1.full_name)
+        expect(json_response['full_path']).to eq(group1.full_path)
         expect(json_response['projects']).to be_an Array
         expect(json_response['projects'].length).to eq(2)
         expect(json_response['shared_projects']).to be_an Array
@@ -323,7 +326,7 @@ describe API::Groups, api: true  do
         expect(response).to have_http_status(404)
       end
 
-      it "should only return projects to which user has access" do
+      it "only returns projects to which user has access" do
         project3.team << [user3, :developer]
 
         get api("/groups/#{group1.id}/projects", user3)
@@ -335,7 +338,7 @@ describe API::Groups, api: true  do
     end
 
     context "when authenticated as admin" do
-      it "should return any existing group" do
+      it "returns any existing group" do
         get api("/groups/#{group2.id}/projects", admin)
 
         expect(response).to have_http_status(200)
@@ -343,7 +346,7 @@ describe API::Groups, api: true  do
         expect(json_response.first['name']).to eq(project2.name)
       end
 
-      it "should not return a non existing group" do
+      it "does not return a non existing group" do
         get api("/groups/1328/projects", admin)
 
         expect(response).to have_http_status(404)
@@ -351,7 +354,7 @@ describe API::Groups, api: true  do
     end
 
     context 'when using group path in URL' do
-      it 'should return any existing group' do
+      it 'returns any existing group' do
         get api("/groups/#{group1.path}/projects", admin)
 
         expect(response).to have_http_status(200)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index a3798c8cd6c86f03e24019a9804249b46fb51685..ffeacb15f17a81843b4a7bf1fa7e45c7bf83f28c 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -4,7 +4,7 @@ describe API::Internal, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:key) { create(:key, user: user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
   let(:secret_token) { Gitlab::Shell.secret_token }
 
   describe "GET /internal/check", no_db: true do
@@ -337,8 +337,7 @@ describe API::Internal, api: true  do
 
     context 'ssh access has been disabled' do
       before do
-        settings = ::ApplicationSetting.create_from_defaults
-        settings.update_attribute(:enabled_git_access_protocol, 'http')
+        stub_application_setting(enabled_git_access_protocol: 'http')
       end
 
       it 'rejects the SSH push' do
@@ -360,8 +359,7 @@ describe API::Internal, api: true  do
 
     context 'http access has been disabled' do
       before do
-        settings = ::ApplicationSetting.create_from_defaults
-        settings.update_attribute(:enabled_git_access_protocol, 'ssh')
+        stub_application_setting(enabled_git_access_protocol: 'ssh')
       end
 
       it 'rejects the HTTP push' do
@@ -383,8 +381,7 @@ describe API::Internal, api: true  do
 
     context 'web actions are always allowed' do
       it 'allows WEB push' do
-        settings = ::ApplicationSetting.create_from_defaults
-        settings.update_attribute(:enabled_git_access_protocol, 'ssh')
+        stub_application_setting(enabled_git_access_protocol: 'ssh')
         project.team << [user, :developer]
         push(key, project, 'web')
 
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 6f20ac4926949ce2062d8796ec088e6255d3623f..21a2c583aa85eaa72998a1d364e588a0319bf6bc 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -6,12 +6,10 @@ describe API::MergeRequests, api: true  do
   let(:user)        { create(:user) }
   let(:admin)       { create(:user, :admin) }
   let(:non_member)  { create(:user) }
-  let!(:project)    { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
-  let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
-  let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
-  let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
-  let!(:note)       { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
-  let!(:note2)      { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+  let!(:project)    { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
+  let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, title: "Test", created_at: base_time) }
+  let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, title: "Closed test", created_at: base_time + 1.second) }
+  let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
   let(:milestone)   { create(:milestone, title: '1.0.0', project: project) }
 
   before do
@@ -556,11 +554,12 @@ describe API::MergeRequests, api: true  do
       original_count = merge_request.notes.size
 
       post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
+
       expect(response).to have_http_status(201)
       expect(json_response['note']).to eq('My comment')
       expect(json_response['author']['name']).to eq(user.name)
       expect(json_response['author']['username']).to eq(user.username)
-      expect(merge_request.notes.size).to eq(original_count + 1)
+      expect(merge_request.reload.notes.size).to eq(original_count + 1)
     end
 
     it "returns 400 if note is missing" do
@@ -576,6 +575,9 @@ describe API::MergeRequests, api: true  do
   end
 
   describe "GET :id/merge_requests/:merge_request_id/comments" do
+    let!(:note)  { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+    let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+
     it "returns merge_request comments ordered by created_at" do
       get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
       expect(response).to have_http_status(200)
@@ -627,6 +629,17 @@ describe API::MergeRequests, api: true  do
       expect(json_response.first['title']).to eq(issue.title)
       expect(json_response.first['id']).to eq(issue.id)
     end
+
+    it 'returns 403 if the user has no access to the merge request' do
+      project = create(:empty_project, :private)
+      merge_request = create(:merge_request, :simple, source_project: project)
+      guest = create(:user)
+      project.team << [guest, :guest]
+
+      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest)
+
+      expect(response).to have_http_status(403)
+    end
   end
 
   describe 'POST :id/merge_requests/:merge_request_id/subscription' do
@@ -648,6 +661,15 @@ describe API::MergeRequests, api: true  do
 
       expect(response).to have_http_status(404)
     end
+
+    it 'returns 403 if user has no access to read code' do
+      guest = create(:user)
+      project.team << [guest, :guest]
+
+      post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
+
+      expect(response).to have_http_status(403)
+    end
   end
 
   describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do
@@ -669,6 +691,15 @@ describe API::MergeRequests, api: true  do
 
       expect(response).to have_http_status(404)
     end
+
+    it 'returns 403 if user has no access to read code' do
+      guest = create(:user)
+      project.team << [guest, :guest]
+
+      delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
+
+      expect(response).to have_http_status(403)
+    end
   end
 
   describe 'Time tracking' do
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 0f8d054b31e97787d8ad59bbdacdbb9c0b314b04..0353ebea9e59b3c518ea17bcf01a98e24c0f3aa1 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -264,6 +264,18 @@ describe API::Notes, api: true  do
       end
     end
 
+    context 'when user does not have access to read the noteable' do
+      it 'responds with 404' do
+        project = create(:empty_project, :private) { |p| p.add_guest(user) }
+        issue = create(:issue, :confidential, project: project)
+
+        post api("/projects/#{project.id}/issues/#{issue.id}/notes", user),
+          body: 'Foo'
+
+        expect(response).to have_http_status(404)
+      end
+    end
+
     context 'when user does not have access to create noteable' do
       let(:private_issue) { create(:issue, project: create(:empty_project, :private)) }
 
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 9a01f7fa1c4cbb194151f639f5a94b657fa63858..b7a0b5a9e1302e6b5be4913491e81d1a43597665 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -5,7 +5,7 @@ describe API::Pipelines, api: true do
 
   let(:user)        { create(:user) }
   let(:non_member)  { create(:user) }
-  let(:project)     { create(:project, creator_id: user.id) }
+  let(:project)     { create(:project, :repository, creator: user) }
 
   let!(:pipeline) do
     create(:ci_empty_pipeline, project: project, sha: project.commit.id,
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 01032c0929bd403645998fff3ebcbc7a38d54622..45d5ae267c547acf1900f94f4e8920ccf0ebcea9 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do
   include ApiHelpers
 
   let(:project) { create(:empty_project, :public) }
+  let(:user) { create(:user) }
   let(:admin) { create(:admin) }
 
   describe 'GET /projects/:project_id/snippets/:id' do
@@ -22,7 +23,7 @@ describe API::ProjectSnippets, api: true do
     let(:user) { create(:user) }
 
     it 'returns all snippets available to team member' do
-      project.team << [user, :developer]
+      project.add_developer(user)
       public_snippet = create(:project_snippet, :public, project: project)
       internal_snippet = create(:project_snippet, :internal, project: project)
       private_snippet = create(:project_snippet, :private, project: project)
@@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do
         title: 'Test Title',
         file_name: 'test.rb',
         code: 'puts "hello world"',
-        visibility_level: Gitlab::VisibilityLevel::PUBLIC
+        visibility_level: Snippet::PUBLIC
       }
     end
 
@@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do
 
       expect(response).to have_http_status(400)
     end
+
+    context 'when the snippet is spam' do
+      def create_snippet(project, snippet_params = {})
+        project.add_developer(user)
+
+        post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
+      end
+
+      before do
+        allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+      end
+
+      context 'when the project is private' do
+        let(:private_project) { create(:project_empty_repo, :private) }
+
+        context 'when the snippet is public' do
+          it 'creates the snippet' do
+            expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+              to change { Snippet.count }.by(1)
+          end
+        end
+      end
+
+      context 'when the project is public' do
+        context 'when the snippet is private' do
+          it 'creates the snippet' do
+            expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+              to change { Snippet.count }.by(1)
+          end
+        end
+
+        context 'when the snippet is public' do
+          it 'rejects the shippet' do
+            expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+              not_to change { Snippet.count }
+            expect(response).to have_http_status(400)
+          end
+
+          it 'creates a spam log' do
+            expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+              to change { SpamLog.count }.by(1)
+          end
+        end
+      end
+    end
   end
 
   describe 'PUT /projects/:project_id/snippets/:id/' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index cc5c532de83ba0d8c6c0ef50eb87a3c9ac6917a7..753dde0ca3afedd0044296e3ff35b27f7e369264 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -17,6 +17,7 @@ describe API::Projects, api: true  do
   let(:project3) do
     create(:project,
     :private,
+    :repository,
     name: 'second_project',
     path: 'second_project',
     creator_id: user.id,
@@ -458,7 +459,7 @@ describe API::Projects, api: true  do
     before { project }
     before { admin }
 
-    it 'should create new project without path and return 201' do
+    it 'creates new project without path and return 201' do
       expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
       expect(response).to have_http_status(201)
     end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 0b19fa38c555ea4b41f6b411a9a67f0d3064199f..c61208e395c4a0dbe38d3305b0af2c1a034d044c 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -8,7 +8,7 @@ describe API::Repositories, api: true  do
 
   let(:user) { create(:user) }
   let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
-  let!(:project) { create(:project, creator_id: user.id) }
+  let!(:project) { create(:project, :repository, creator: user) }
   let!(:master) { create(:project_member, :master, user: user, project: project) }
 
   describe "GET /projects/:id/repository/tree" do
@@ -74,7 +74,7 @@ describe API::Repositories, api: true  do
 
     context 'when unauthenticated', 'and project is public' do
       it_behaves_like 'repository tree' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
@@ -144,7 +144,7 @@ describe API::Repositories, api: true  do
 
       context 'when unauthenticated', 'and project is public' do
         it_behaves_like 'repository blob' do
-          let(:project) { create(:project, :public) }
+          let(:project) { create(:project, :public, :repository) }
           let(:current_user) { nil }
         end
       end
@@ -198,7 +198,7 @@ describe API::Repositories, api: true  do
 
     context 'when unauthenticated', 'and project is public' do
       it_behaves_like 'repository raw blob' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
@@ -273,7 +273,7 @@ describe API::Repositories, api: true  do
 
     context 'when unauthenticated', 'and project is public' do
       it_behaves_like 'repository archive' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
@@ -347,7 +347,7 @@ describe API::Repositories, api: true  do
 
     context 'when unauthenticated', 'and project is public' do
       it_behaves_like 'repository compare' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
@@ -394,7 +394,7 @@ describe API::Repositories, api: true  do
 
     context 'when unauthenticated', 'and project is public' do
       it_behaves_like 'repository contributors' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 39c9e0505d1a9c200c42eae28ba9f15294c30d05..776dc6556506bad9e8b4b94380bdb93e22b7388e 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -6,7 +6,7 @@ describe API::Services, api: true  do
   let(:user) { create(:user) }
   let(:admin) { create(:admin) }
   let(:user2) { create(:user) }
-  let(:project) {create(:empty_project, creator_id: user.id, namespace: user.namespace) }
+  let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
 
   Service.available_services_names.each do |service|
     describe "PUT /projects/:id/services/#{service.dasherize}" do
@@ -16,6 +16,15 @@ describe API::Services, api: true  do
         put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
 
         expect(response).to have_http_status(200)
+
+        current_service = project.services.first
+        event = current_service.event_names.empty? ? "foo" : current_service.event_names.first
+        state = current_service[event] || false
+
+        put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs
+
+        expect(response).to have_http_status(200)
+        expect(project.services.first[event]).not_to eq(state) unless event == "foo"
       end
 
       it "returns if required fields missing" do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index f6fb6ea5506cbd44fba039d4d02b11870dd0c474..6b9a739b4390b94703e744cd4ea608f9af07a171 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -80,7 +80,7 @@ describe API::Snippets, api: true do
         title: 'Test Title',
         file_name: 'test.rb',
         content: 'puts "hello world"',
-        visibility_level: Gitlab::VisibilityLevel::PUBLIC
+        visibility_level: Snippet::PUBLIC
       }
     end
 
@@ -101,6 +101,36 @@ describe API::Snippets, api: true do
 
       expect(response).to have_http_status(400)
     end
+
+    context 'when the snippet is spam' do
+      def create_snippet(snippet_params = {})
+        post api('/snippets', user), params.merge(snippet_params)
+      end
+
+      before do
+        allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+      end
+
+      context 'when the snippet is private' do
+        it 'creates the snippet' do
+          expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+            to change { Snippet.count }.by(1)
+        end
+      end
+
+      context 'when the snippet is public' do
+        it 'rejects the shippet' do
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+            not_to change { Snippet.count }
+          expect(response).to have_http_status(400)
+        end
+
+        it 'creates a spam log' do
+          expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+            to change { SpamLog.count }.by(1)
+        end
+      end
+    end
   end
 
   describe 'PUT /snippets/:id' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index a1c32ae65ba85ad72a8439014f25a5b35312f181..898d2b27e5c31322ef1212c5ce06ffc01e58cab6 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -7,7 +7,7 @@ describe API::Tags, api: true  do
 
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
-  let!(:project) { create(:project, creator_id: user.id) }
+  let!(:project) { create(:project, :repository, creator: user) }
   let!(:master) { create(:project_member, :master, user: user, project: project) }
   let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
 
@@ -29,7 +29,7 @@ describe API::Tags, api: true  do
 
     context 'when unauthenticated' do
       it_behaves_like 'repository tags' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
@@ -88,7 +88,7 @@ describe API::Tags, api: true  do
 
     context 'when unauthenticated' do
       it_behaves_like 'repository tag' do
-        let(:project) { create(:project, :public) }
+        let(:project) { create(:project, :public, :repository) }
         let(:current_user) { nil }
       end
     end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 6fe695626caa762b5cfe8bb7a0bb44c3460bf0eb..56dc017ce54ba8c9e17e39717e75024f2ac3fb43 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -183,12 +183,25 @@ describe API::Todos, api: true do
 
       expect(response.status).to eq(404)
     end
+
+    it 'returns an error if the issuable is not accessible' do
+      guest = create(:user)
+      project_1.team << [guest, :guest]
+
+      post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest)
+
+      if issuable_type == 'merge_requests'
+        expect(response).to have_http_status(403)
+      else
+        expect(response).to have_http_status(404)
+      end
+    end
   end
 
   describe 'POST :id/issuable_type/:issueable_id/todo' do
     context 'for an issue' do
       it_behaves_like 'an issuable', 'issues' do
-        let(:issuable) { create(:issue, author: author_1, project: project_1) }
+        let(:issuable) { create(:issue, :confidential, author: author_1, project: project_1) }
       end
     end
 
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index cd01283b655ada043eb24a85293342e7b5346027..84104aa66ee94cd1a38a5bbf6319283f4038342a 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -7,7 +7,7 @@ describe API::Triggers do
   let(:user2) { create(:user) }
   let!(:trigger_token) { 'secure_token' }
   let!(:trigger_token_2) { 'secure_token_2' }
-  let!(:project) { create(:project, creator_id: user.id) }
+  let!(:project) { create(:project, :repository, creator: user) }
   let!(:master) { create(:project_member, :master, user: user, project: project) }
   let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
   let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5bf5bf0739e4b871c32097239a93a2bf636ff505..8692f9da976eb149ad5757288f367bd323dbcf09 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -305,6 +305,13 @@ describe API::Users, api: true  do
       expect(user.reload.bio).to eq('new test bio')
     end
 
+    it "updates user with new password and forces reset on next login" do
+      put api("/users/#{user.id}", admin), password: '12345678'
+
+      expect(response).to have_http_status(200)
+      expect(user.reload.password_expires_at).to be <= Time.now
+    end
+
     it "updates user with organization" do
       put api("/users/#{user.id}", admin), { organization: 'GitLab' }
 
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 270c23e3f19b97458ee7de7964f6becb6ca40c35..d85afdeab4244ae1dd4f78bac32162dbeb7e7f25 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -91,6 +91,20 @@ describe Ci::API::Builds do
           expect { register_builds }.to change { runner.reload.contacted_at }
         end
 
+        context 'when concurrently updating build' do
+          before do
+            expect_any_instance_of(Ci::Build).to receive(:run!).
+              and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
+          end
+
+          it 'returns a conflict' do
+            register_builds info: { platform: :darwin }
+
+            expect(response).to have_http_status(409)
+            expect(response.headers).not_to have_key('X-GitLab-Last-Update')
+          end
+        end
+
         context 'registry credentials' do
           let(:registry_credentials) do
             { 'type' => 'registry',
@@ -274,7 +288,7 @@ describe Ci::API::Builds do
         expect(build.reload.trace).to eq 'BUILD TRACE'
       end
 
-      context 'build has been erased' do
+      context 'job has been erased' do
         let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
 
         it 'responds with forbidden' do
@@ -444,7 +458,7 @@ describe Ci::API::Builds do
       before { build.run! }
 
       describe "POST /builds/:id/artifacts/authorize" do
-        context "should authorize posting artifact to running build" do
+        context "authorizes posting artifact to running build" do
           it "using token as parameter" do
             post authorize_url, { token: build.token }, headers
 
@@ -478,7 +492,7 @@ describe Ci::API::Builds do
           end
         end
 
-        context "should fail to post too large artifact" do
+        context "fails to post too large artifact" do
           it "using token as parameter" do
             stub_application_setting(max_artifacts_size: 0)
 
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index 2d434ab5dd81e4731f441d072af1f601258c8640..a30be767119cb1bca4e2127177fa9a1ec8fd6d66 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -5,9 +5,9 @@ describe Ci::API::Triggers do
 
   describe 'POST /projects/:project_id/refs/:ref/trigger' do
     let!(:trigger_token) { 'secure token' }
-    let!(:project) { FactoryGirl.create(:project, ci_id: 10) }
-    let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) }
-    let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
+    let!(:project) { create(:project, :repository, ci_id: 10) }
+    let!(:project2) { create(:empty_project, ci_id: 11) }
+    let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
     let(:options) do
       {
         token: trigger_token
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 5abda28e26fa94b3842d1694c3ade0bc82b23309..4a16824de04df861ee1b771ffd1cce8957708884 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -12,7 +12,7 @@ describe 'Git HTTP requests', lib: true do
 
   describe "User with no identities" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project, path: 'project.git-project') }
+    let(:project) { create(:project, :repository, path: 'project.git-project') }
 
     context "when the project doesn't exist" do
       context "when no authentication is provided" do
@@ -55,6 +55,28 @@ describe 'Git HTTP requests', lib: true do
           expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
         end
       end
+
+      context 'but the repo is disabled' do
+        let(:project) { create(:project, repository_access_level: ProjectFeature::DISABLED, wiki_access_level: ProjectFeature::ENABLED) }
+        let(:wiki) { ProjectWiki.new(project) }
+        let(:path) { "/#{wiki.repository.path_with_namespace}.git" }
+
+        before do
+          project.team << [user, :developer]
+        end
+
+        it 'allows clones' do
+          download(path, user: user.username, password: user.password) do |response|
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        it 'allows pushes' do
+          upload(path, user: user.username, password: user.password) do |response|
+            expect(response).to have_http_status(200)
+          end
+        end
+      end
     end
 
     context "when the project exists" do
diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb
index e02f0eacc9349a8ac9edcb2702c5db6966aa8fd4..d20866c0d44d41227b7a8eacae0a3cd49622c1b6 100644
--- a/spec/requests/projects/artifacts_controller_spec.rb
+++ b/spec/requests/projects/artifacts_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe Projects::ArtifactsController do
   let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :repository) }
 
   let(:pipeline) do
     create(:ci_pipeline,
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index e0368e6001f433943d57fca25482b5106a126dc9..0edbffbcd3b1e451f650b858447cc626181cad0a 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -1,8 +1,10 @@
 require 'spec_helper'
 
 describe 'cycle analytics events' do
+  include ApiHelpers
+
   let(:user) { create(:user) }
-  let(:project) { create(:project, public_builds: false) }
+  let(:project) { create(:project, :repository, public_builds: false) }
   let(:issue) {  create(:issue, project: project, created_at: 2.days.ago) }
 
   describe 'GET /:namespace/:project/cycle_analytics/events/issues' do
@@ -11,7 +13,12 @@ describe 'cycle analytics events' do
 
       allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
 
-      3.times { create_cycle }
+      3.times do |count|
+        Timecop.freeze(Time.now + count.days) do
+          create_cycle
+        end
+      end
+
       deploy_master
 
       login_as(user)
@@ -20,19 +27,19 @@ describe 'cycle analytics events' do
     it 'lists the issue events' do
       get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
 
-      expect(json_response['events']).not_to be_empty
-
-      first_issue_iid = Issue.order(created_at: :desc).pluck(:iid).first.to_s
+      first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
 
+      expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_issue_iid)
     end
 
     it 'lists the plan events' do
       get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json)
 
-      expect(json_response['events']).not_to be_empty
+      first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id
 
-      expect(json_response['events'].first['short_sha']).to eq(MergeRequest.last.commits.first.short_id)
+      expect(json_response['events']).not_to be_empty
+      expect(json_response['events'].first['short_sha']).to eq(first_mr_short_sha)
     end
 
     it 'lists the code events' do
@@ -40,7 +47,7 @@ describe 'cycle analytics events' do
 
       expect(json_response['events']).not_to be_empty
 
-      first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s
+      first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
 
       expect(json_response['events'].first['iid']).to eq(first_mr_iid)
     end
@@ -49,17 +56,15 @@ describe 'cycle analytics events' do
       get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
 
       expect(json_response['events']).not_to be_empty
-
       expect(json_response['events'].first['date']).not_to be_empty
     end
 
     it 'lists the review events' do
       get namespace_project_cycle_analytics_review_path(project.namespace, project, format: :json)
 
-      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.sort(:created_desc).pluck(:iid).first.to_s
 
+      expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_mr_iid)
     end
 
@@ -67,35 +72,32 @@ describe 'cycle analytics events' do
       get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
 
       expect(json_response['events']).not_to be_empty
-
       expect(json_response['events'].first['date']).not_to be_empty
     end
 
     it 'lists the production events' do
       get namespace_project_cycle_analytics_production_path(project.namespace, project, format: :json)
 
-      expect(json_response['events']).not_to be_empty
-
-      first_issue_iid = Issue.order(created_at: :desc).pluck(:iid).first.to_s
+      first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
 
+      expect(json_response['events']).not_to be_empty
       expect(json_response['events'].first['iid']).to eq(first_issue_iid)
     end
 
     context 'specific branch' do
       it 'lists the test events' do
-        branch = MergeRequest.first.source_branch
+        branch = project.merge_requests.first.source_branch
 
         get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch)
 
         expect(json_response['events']).not_to be_empty
-
         expect(json_response['events'].first['date']).not_to be_empty
       end
     end
 
     context 'with private project and builds' do
       before do
-        ProjectMember.first.update(access_level: Gitlab::Access::GUEST)
+        project.members.first.update(access_level: Gitlab::Access::GUEST)
       end
 
       it 'does not list the test events' do
@@ -118,10 +120,6 @@ describe 'cycle analytics events' do
     end
   end
 
-  def json_response
-    JSON.parse(response.body)
-  end
-
   def create_cycle
     milestone = create(:milestone, project: project)
     issue.update(milestone: milestone)
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 77549db29277b5c15a40290f9fd43c8892287d1e..96889abee7933dbf5eefd34f633e11e211cc0ef3 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe 'project routing' do
   before do
-    allow(Project).to receive(:find_with_namespace).and_return(false)
-    allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq').and_return(true)
+    allow(Project).to receive(:find_by_full_path).and_return(false)
+    allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq').and_return(true)
   end
 
   # Shared examples for a resource inside a Project
@@ -86,13 +86,13 @@ describe 'project routing' do
       end
 
       context 'name with dot' do
-        before { allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq.keys').and_return(true) }
+        before { allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq.keys').and_return(true) }
 
         it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') }
       end
 
       context 'with nested group' do
-        before { allow(Project).to receive(:find_with_namespace).with('gitlab/subgroup/gitlabhq').and_return(true) }
+        before { allow(Project).to receive(:find_by_full_path).with('gitlab/subgroup/gitlabhq').and_return(true) }
 
         it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') }
       end
diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb
index f0551c78671308b6cbc2c942afe1ad8916f15f99..e3b1dd93dc2b8c3517a50ce6312ed50ac00efaf8 100644
--- a/spec/serializers/analytics_build_serializer_spec.rb
+++ b/spec/serializers/analytics_build_serializer_spec.rb
@@ -1,17 +1,13 @@
 require 'spec_helper'
 
 describe AnalyticsBuildSerializer do
-  let(:serializer) do
-    described_class
-      .new.represent(resource)
-  end
-
-  let(:json) { serializer.as_json }
   let(:resource) { create(:ci_build) }
 
+  subject { described_class.new.represent(resource) }
+
   context 'when there is a single object provided' do
     it 'contains important elements of analyticsBuild' do
-      expect(json)
+      expect(subject)
         .to include(:name, :branch, :short_sha, :date, :total_time, :url, :author)
     end
   end
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 6afbb2df35c0164f41040464dbd08beb396bb153..2f08958a783d4b1a55f6c484915628f8233041eb 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -1,14 +1,13 @@
 require 'spec_helper'
 
 describe AnalyticsIssueSerializer do
-  let(:serializer) do
+  subject do
     described_class
       .new(project: project, entity: :merge_request)
       .represent(resource)
   end
 
   let(:user) { create(:user) }
-  let(:json) { serializer.as_json }
   let(:project) { create(:project) }
   let(:resource) do
     {
@@ -23,7 +22,7 @@ describe AnalyticsIssueSerializer do
 
   context 'when there is a single object provided' do
     it 'contains important elements of the issue' do
-      expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author)
+      expect(subject).to include(:title, :iid, :created_at, :total_time, :url, :author)
     end
   end
 end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index cdfae27193fbe3449f11f09ab289499174d77c13..62067cc0ef20518958dc38b9200def9f389b7375 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -1,14 +1,13 @@
 require 'spec_helper'
 
 describe AnalyticsMergeRequestSerializer do
-  let(:serializer) do
+  subject do
     described_class
       .new(project: project, entity: :merge_request)
       .represent(resource)
   end
 
   let(:user) { create(:user) }
-  let(:json) { serializer.as_json }
   let(:project) { create(:project) }
   let(:resource) do
     {
@@ -24,7 +23,7 @@ describe AnalyticsMergeRequestSerializer do
 
   context 'when there is a single object provided' do
     it 'contains important elements of the merge request' do
-      expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author, :state)
+      expect(subject).to include(:title, :iid, :created_at, :total_time, :url, :author, :state)
     end
   end
 end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index f99518266830c1349b24bbcb88016811b90d0d1a..be6aa7c65c3746434eea636d13bf29ca6dd2edcb 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -1,13 +1,13 @@
 require 'spec_helper'
 
 describe AnalyticsStageSerializer do
-  let(:serializer) do
-    described_class
-      .new.represent(resource)
+  subject do
+    described_class.new.represent(resource)
   end
 
-  let(:json) { serializer.as_json }
-  let(:resource) { Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {}) }
+  let(:resource) do
+    Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {})
+  end
 
   before do
     allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
@@ -15,10 +15,10 @@ describe AnalyticsStageSerializer do
   end
 
   it 'it generates payload for single object' do
-    expect(json).to be_kind_of Hash
+    expect(subject).to be_kind_of Hash
   end
 
   it 'contains important elements of AnalyticsStage' do
-    expect(json).to include(:title, :description, :value)
+    expect(subject).to include(:title, :description, :value)
   end
 end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
index 7a84c8b0b40401da5f60d149e4cd4e350d0b7df3..5d7a94c2d0267ebc2ec6692859e7690987300f0f 100644
--- a/spec/serializers/analytics_summary_serializer_spec.rb
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -1,29 +1,28 @@
 require 'spec_helper'
 
 describe AnalyticsSummarySerializer do
-  let(:serializer) do
-    described_class
-      .new.represent(resource)
+  subject do
+    described_class.new.represent(resource)
   end
 
-  let(:json) { serializer.as_json }
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
+
   let(:resource) do
-    Gitlab::CycleAnalytics::Summary::Issue.new(project: double,
-                                               from: 1.day.ago,
-                                               current_user: user)
+    Gitlab::CycleAnalytics::Summary::Issue
+      .new(project: double, from: 1.day.ago, current_user: user)
   end
 
   before do
-    allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue).to receive(:value).and_return(1.12)
+    allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue)
+      .to receive(:value).and_return(1.12)
   end
 
   it 'it generates payload for single object' do
-    expect(json).to be_kind_of Hash
+    expect(subject).to be_kind_of Hash
   end
 
   it 'contains important elements of AnalyticsStage' do
-    expect(json).to include(:title, :value)
+    expect(subject).to include(:title, :value)
   end
 end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index b7ed4eb0239d76547aeb28cf77b925300a1fce02..3c37660885d1af13769fa02d575d2a821e764bea 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -1,16 +1,15 @@
 require 'spec_helper'
 
 describe EnvironmentSerializer do
-  let(:serializer) do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  let(:json) do
     described_class
       .new(user: user, project: project)
       .represent(resource)
   end
 
-  let(:json) { serializer.as_json }
-  let(:user) { create(:user) }
-  let(:project) { create(:project) }
-
   context 'when there is a single object provided' do
     before do
       create(:ci_build, :manual, name: 'manual1',
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index b19464c7117aefd2390f7cd4b69698b83fef3725..ccb72973f9c2de19db795e56c85b70206fbaf8d0 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -134,5 +134,17 @@ describe PipelineEntity do
         expect(subject).not_to have_key(:yaml_errors)
       end
     end
+
+    context 'when pipeline ref is empty' do
+      let(:pipeline) { create(:ci_empty_pipeline) }
+
+      before do
+        allow(pipeline).to receive(:ref).and_return(nil)
+      end
+
+      it 'does not generate branch path' do
+        expect(subject[:ref][:path]).to be_nil
+      end
+    end
   end
 end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 3a32cb394dd08026fee2fde23ddff1003b4d170a..7cbf131e41e3ee1ae028c5aa13c5e87506c7ebaf 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -7,11 +7,7 @@ describe PipelineSerializer do
     described_class.new(user: user)
   end
 
-  let(:entity) do
-    serializer.represent(resource)
-  end
-
-  subject { entity.as_json }
+  subject { serializer.represent(resource) }
 
   describe '#represent' do
     context 'when used without pagination' do
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index a3fc23ba17783fa63d8fb9f34c401690ebb1d772..d9f774a1095f2b4807373a998c66b2a3afde11cd 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
 
 module Ci
   describe RegisterBuildService, services: true do
-    let!(:service) { RegisterBuildService.new }
     let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
     let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
     let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline }
@@ -19,29 +18,29 @@ module Ci
           pending_build.tag_list = ["linux"]
           pending_build.save
           specific_runner.tag_list = ["linux"]
-          expect(service.execute(specific_runner)).to eq(pending_build)
+          expect(execute(specific_runner)).to eq(pending_build)
         end
 
         it "does not pick build with different tag" do
           pending_build.tag_list = ["linux"]
           pending_build.save
           specific_runner.tag_list = ["win32"]
-          expect(service.execute(specific_runner)).to be_falsey
+          expect(execute(specific_runner)).to be_falsey
         end
 
         it "picks build without tag" do
-          expect(service.execute(specific_runner)).to eq(pending_build)
+          expect(execute(specific_runner)).to eq(pending_build)
         end
 
         it "does not pick build with tag" do
           pending_build.tag_list = ["linux"]
           pending_build.save
-          expect(service.execute(specific_runner)).to be_falsey
+          expect(execute(specific_runner)).to be_falsey
         end
 
         it "pick build without tag" do
           specific_runner.tag_list = ["win32"]
-          expect(service.execute(specific_runner)).to eq(pending_build)
+          expect(execute(specific_runner)).to eq(pending_build)
         end
       end
 
@@ -56,13 +55,13 @@ module Ci
           end
 
           it 'does not pick a build' do
-            expect(service.execute(shared_runner)).to be_nil
+            expect(execute(shared_runner)).to be_nil
           end
         end
 
         context 'for specific runner' do
           it 'does not pick a build' do
-            expect(service.execute(specific_runner)).to be_nil
+            expect(execute(specific_runner)).to be_nil
           end
         end
       end
@@ -86,34 +85,34 @@ module Ci
 
           it 'prefers projects without builds first' do
             # it gets for one build from each of the projects
-            expect(service.execute(shared_runner)).to eq(build1_project1)
-            expect(service.execute(shared_runner)).to eq(build1_project2)
-            expect(service.execute(shared_runner)).to eq(build1_project3)
+            expect(execute(shared_runner)).to eq(build1_project1)
+            expect(execute(shared_runner)).to eq(build1_project2)
+            expect(execute(shared_runner)).to eq(build1_project3)
 
             # then it gets a second build from each of the projects
-            expect(service.execute(shared_runner)).to eq(build2_project1)
-            expect(service.execute(shared_runner)).to eq(build2_project2)
+            expect(execute(shared_runner)).to eq(build2_project1)
+            expect(execute(shared_runner)).to eq(build2_project2)
 
             # in the end the third build
-            expect(service.execute(shared_runner)).to eq(build3_project1)
+            expect(execute(shared_runner)).to eq(build3_project1)
           end
 
           it 'equalises number of running builds' do
             # after finishing the first build for project 1, get a second build from the same project
-            expect(service.execute(shared_runner)).to eq(build1_project1)
+            expect(execute(shared_runner)).to eq(build1_project1)
             build1_project1.reload.success
-            expect(service.execute(shared_runner)).to eq(build2_project1)
+            expect(execute(shared_runner)).to eq(build2_project1)
 
-            expect(service.execute(shared_runner)).to eq(build1_project2)
+            expect(execute(shared_runner)).to eq(build1_project2)
             build1_project2.reload.success
-            expect(service.execute(shared_runner)).to eq(build2_project2)
-            expect(service.execute(shared_runner)).to eq(build1_project3)
-            expect(service.execute(shared_runner)).to eq(build3_project1)
+            expect(execute(shared_runner)).to eq(build2_project2)
+            expect(execute(shared_runner)).to eq(build1_project3)
+            expect(execute(shared_runner)).to eq(build3_project1)
           end
         end
 
         context 'shared runner' do
-          let(:build) { service.execute(shared_runner) }
+          let(:build) { execute(shared_runner) }
 
           it { expect(build).to be_kind_of(Build) }
           it { expect(build).to be_valid }
@@ -122,7 +121,7 @@ module Ci
         end
 
         context 'specific runner' do
-          let(:build) { service.execute(specific_runner) }
+          let(:build) { execute(specific_runner) }
 
           it { expect(build).to be_kind_of(Build) }
           it { expect(build).to be_valid }
@@ -137,13 +136,13 @@ module Ci
         end
 
         context 'shared runner' do
-          let(:build) { service.execute(shared_runner) }
+          let(:build) { execute(shared_runner) }
 
           it { expect(build).to be_nil }
         end
 
         context 'specific runner' do
-          let(:build) { service.execute(specific_runner) }
+          let(:build) { execute(specific_runner) }
 
           it { expect(build).to be_kind_of(Build) }
           it { expect(build).to be_valid }
@@ -159,17 +158,21 @@ module Ci
         end
 
         context 'and uses shared runner' do
-          let(:build) { service.execute(shared_runner) }
+          let(:build) { execute(shared_runner) }
 
           it { expect(build).to be_nil }
         end
 
         context 'and uses specific runner' do
-          let(:build) { service.execute(specific_runner) }
+          let(:build) { execute(specific_runner) }
 
           it { expect(build).to be_nil }
         end
       end
+
+      def execute(runner)
+        described_class.new(runner).execute.build
+      end
     end
   end
 end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f01a388b895ea31a6031725f78d33c9137e7eec7
--- /dev/null
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Ci::UpdateBuildQueueService, :services do
+  let(:project) { create(:project) }
+  let(:build) { create(:ci_build, pipeline: pipeline) }
+  let(:pipeline) { create(:ci_pipeline, project: project) }
+
+  context 'when updating specific runners' do
+    let(:runner) { create(:ci_runner) }
+
+    context 'when there are runner that can pick build' do
+      before { build.project.runners << runner }
+
+      it 'ticks runner queue value' do
+        expect { subject.execute(build) }
+          .to change { runner.ensure_runner_queue_value }
+      end
+    end
+
+    context 'when there are no runners that can pick build' do
+      it 'does not tick runner queue value' do
+        expect { subject.execute(build) }
+          .not_to change { runner.ensure_runner_queue_value }
+      end
+    end
+  end
+
+  context 'when updating shared runners' do
+    let(:runner) { create(:ci_runner, :shared) }
+
+    context 'when there are runner that can pick build' do
+      it 'ticks runner queue value' do
+        expect { subject.execute(build) }
+          .to change { runner.ensure_runner_queue_value }
+      end
+    end
+
+    context 'when there are no runners that can pick build' do
+      before { build.tag_list = [:docker] }
+
+      it 'does not tick runner queue value' do
+        expect { subject.execute(build) }
+          .not_to change { runner.ensure_runner_queue_value }
+      end
+    end
+  end
+end
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index 3760f19aaa2eedfd54da5f6644e5b408dd708250..0a7fc58523f2c20a8809cbdb294063b126859da1 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
 describe CompareService, services: true do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
-  let(:service) { described_class.new }
+  let(:service) { described_class.new(project, 'feature') }
 
   describe '#execute' do
     context 'compare with base, like feature...fix' do
-      subject { service.execute(project, 'feature', project, 'fix', straight: false) }
+      subject { service.execute(project, 'fix', straight: false) }
 
       it { expect(subject.diffs.size).to eq(1) }
     end
 
     context 'straight compare, like feature..fix' do
-      subject { service.execute(project, 'feature', project, 'fix', straight: true) }
+      subject { service.execute(project, 'fix', straight: true) }
 
       it { expect(subject.diffs.size).to eq(3) }
     end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index b7dc99ed88710ed811552dcf7216c316f9506546..f2c2009bcbf97156adf81a28069b377f45d4f1e6 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -9,7 +9,7 @@ describe EventCreateService, services: true do
 
       it { expect(service.open_issue(issue, issue.author)).to be_truthy }
 
-      it "should create new event" do
+      it "creates new event" do
         expect { service.open_issue(issue, issue.author) }.to change { Event.count }
       end
     end
@@ -19,7 +19,7 @@ describe EventCreateService, services: true do
 
       it { expect(service.close_issue(issue, issue.author)).to be_truthy }
 
-      it "should create new event" do
+      it "creates new event" do
         expect { service.close_issue(issue, issue.author) }.to change { Event.count }
       end
     end
@@ -29,7 +29,7 @@ describe EventCreateService, services: true do
 
       it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
 
-      it "should create new event" do
+      it "creates new event" do
         expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
       end
     end
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index d3c37c7820f6ebdcafe4d48fd34d1427506b8cb5..35e6e1392385c2f7a8773345d7e66a111ae98cb5 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -6,7 +6,10 @@ describe Files::UpdateService do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:file_path) { 'files/ruby/popen.rb' }
-  let(:new_contents) { "New Content" }
+  let(:new_contents) { 'New Content' }
+  let(:target_branch) { project.default_branch }
+  let(:last_commit_sha) { nil }
+
   let(:commit_params) do
     {
       file_path: file_path,
@@ -14,9 +17,9 @@ describe Files::UpdateService do
       file_content: new_contents,
       file_content_encoding: "text",
       last_commit_sha: last_commit_sha,
-      source_project: project,
-      source_branch: project.default_branch,
-      target_branch: project.default_branch,
+      start_project: project,
+      start_branch: project.default_branch,
+      target_branch: target_branch
     }
   end
 
@@ -54,18 +57,6 @@ describe Files::UpdateService do
     end
 
     context "when the last_commit_sha is not supplied" do
-      let(:commit_params) do
-        {
-          file_path: file_path,
-          commit_message: "Update File",
-          file_content: new_contents,
-          file_content_encoding: "text",
-          source_project: project,
-          source_branch: project.default_branch,
-          target_branch: project.default_branch,
-        }
-      end
-
       it "returns a hash with the :success status " do
         results = subject.execute
 
@@ -80,5 +71,15 @@ describe Files::UpdateService do
         expect(results.data).to eq(new_contents)
       end
     end
+
+    context 'when target branch is different than source branch' do
+      let(:target_branch) { "#{project.default_branch}-new" }
+
+      it 'fires hooks only once' do
+        expect(GitHooksService).to receive(:new).once.and_call_original
+
+        subject.execute
+      end
+    end
   end
 end
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index 41b0968b8b412a4d01ec0800219ac0d30cd52854..3318dfb22b68c6da67b222bd4bee30bb1943560e 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -21,7 +21,7 @@ describe GitHooksService, services: true do
         hook = double(trigger: [true, nil])
         expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
 
-        expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq([true, nil])
+        service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }
       end
     end
 
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4b90ad19640cf07eb95540eb69b4e172fe950f92
--- /dev/null
+++ b/spec/services/labels/promote_service_spec.rb
@@ -0,0 +1,187 @@
+require 'spec_helper'
+
+describe Labels::PromoteService, services: true do
+  describe '#execute' do
+    let!(:user) { create(:user) }
+
+    context 'project without group' do
+      let!(:project_1)  { create(:empty_project) }
+
+      let!(:project_label_1_1)  { create(:label, project: project_1) }
+
+      subject(:service) { described_class.new(project_1, user) }
+
+      it 'fails on project without group' do
+        expect(service.execute(project_label_1_1)).to be_falsey
+      end
+    end
+
+    context 'project with group' do
+      let!(:promoted_label_name)  { "Promoted Label" }
+      let!(:untouched_label_name) { "Untouched Label" }
+      let!(:promoted_description) { "Promoted Description" }
+      let!(:promoted_color)       { "#0000FF" }
+      let!(:label_2_1_priority)   { 1 }
+      let!(:label_3_1_priority)   { 2 }
+
+      let!(:group_1)  { create(:group) }
+      let!(:group_2)  { create(:group) }
+
+      let!(:project_1)  { create(:empty_project, namespace: group_1) }
+      let!(:project_2)  { create(:empty_project, namespace: group_1) }
+      let!(:project_3)  { create(:empty_project, namespace: group_1) }
+      let!(:project_4)  { create(:empty_project, namespace: group_2) }
+
+      # Labels/issues can't be lazily created so we might as well eager initialize
+      # all other objects too since we use them inside
+      let!(:project_label_1_1)  { create(:label, project: project_1, name: promoted_label_name, color: promoted_color, description: promoted_description) }
+      let!(:project_label_1_2)  { create(:label, project: project_1, name: untouched_label_name) }
+      let!(:project_label_2_1)  { create(:label, project: project_2, priority: label_2_1_priority, name: promoted_label_name, color: "#FF0000") }
+      let!(:project_label_3_1)  { create(:label, project: project_3, priority: label_3_1_priority, name: promoted_label_name) }
+      let!(:project_label_3_2)  { create(:label, project: project_3, priority: 1, name: untouched_label_name) }
+      let!(:project_label_4_1)  { create(:label, project: project_4, name: promoted_label_name) }
+
+      let!(:issue_1_1)  { create(:labeled_issue, project: project_1, labels: [project_label_1_1, project_label_1_2]) }
+      let!(:issue_1_2)  { create(:labeled_issue, project: project_1, labels: [project_label_1_2]) }
+      let!(:issue_2_1)  { create(:labeled_issue, project: project_2, labels: [project_label_2_1]) }
+      let!(:issue_4_1)  { create(:labeled_issue, project: project_4, labels: [project_label_4_1]) }
+
+      let!(:merge_3_1)  { create(:labeled_merge_request, source_project: project_3, target_project: project_3, labels: [project_label_3_1, project_label_3_2]) }
+
+      let!(:issue_board_2_1)      { create(:board, project: project_2) }
+      let!(:issue_board_list_2_1) { create(:list, board: issue_board_2_1, label: project_label_2_1) }
+
+      let(:new_label) { group_1.labels.find_by(title: promoted_label_name) }
+
+      subject(:service) { described_class.new(project_1, user) }
+
+      it 'fails on group label' do
+        group_label = create(:group_label, group: group_1)
+
+        expect(service.execute(group_label)).to be_falsey
+      end
+
+      it 'is truthy on success' do
+        expect(service.execute(project_label_1_1)).to be_truthy
+      end
+
+      it 'recreates the label as a group label' do
+        expect { service.execute(project_label_1_1) }.
+          to change(project_1.labels, :count).by(-1).
+          and change(group_1.labels, :count).by(1)
+        expect(new_label).not_to be_nil
+      end
+
+      it 'copies title, description and color' do
+        service.execute(project_label_1_1)
+
+        expect(new_label.title).to eq(promoted_label_name)
+        expect(new_label.description).to eq(promoted_description)
+        expect(new_label.color).to eq(promoted_color)
+      end
+
+      it 'merges labels with the same name in group' do
+        expect { service.execute(project_label_1_1) }.to change(project_2.labels, :count).by(-1).and \
+          change(project_3.labels, :count).by(-1)
+      end
+
+      it 'recreates priorities' do
+        service.execute(project_label_1_1)
+
+        expect(new_label.priority(project_1)).to be_nil
+        expect(new_label.priority(project_2)).to eq(label_2_1_priority)
+        expect(new_label.priority(project_3)).to eq(label_3_1_priority)
+      end
+
+      it 'does not touch project out of promoted group' do
+        service.execute(project_label_1_1)
+        project_4_new_label = project_4.labels.find_by(title: promoted_label_name)
+
+        expect(project_4_new_label).not_to be_nil
+        expect(project_4_new_label.id).to eq(project_label_4_1.id)
+      end
+
+      it 'does not touch out of group priority' do
+        service.execute(project_label_1_1)
+
+        expect(new_label.priority(project_4)).to be_nil
+      end
+
+      it 'relinks issue with the promoted label' do
+        service.execute(project_label_1_1)
+        issue_label = issue_1_1.labels.find_by(title: promoted_label_name)
+
+        expect(issue_label).not_to be_nil
+        expect(issue_label.id).to eq(new_label.id)
+      end
+
+      it 'does not remove untouched labels from issue' do
+        expect { service.execute(project_label_1_1) }.not_to change(issue_1_1.labels, :count)
+      end
+
+      it 'does not relink untouched label in issue' do
+        service.execute(project_label_1_1)
+        issue_label = issue_1_2.labels.find_by(title: untouched_label_name)
+
+        expect(issue_label).not_to be_nil
+        expect(issue_label.id).to eq(project_label_1_2.id)
+      end
+
+      it 'relinks issues with merged labels' do
+        service.execute(project_label_1_1)
+        issue_label = issue_2_1.labels.find_by(title: promoted_label_name)
+
+        expect(issue_label).not_to be_nil
+        expect(issue_label.id).to eq(new_label.id)
+      end
+
+      it 'does not relink issues from other group' do
+        service.execute(project_label_1_1)
+        issue_label = issue_4_1.labels.find_by(title: promoted_label_name)
+
+        expect(issue_label).not_to be_nil
+        expect(issue_label.id).to eq(project_label_4_1.id)
+      end
+
+      it 'updates merge request' do
+        service.execute(project_label_1_1)
+        merge_label = merge_3_1.labels.find_by(title: promoted_label_name)
+
+        expect(merge_label).not_to be_nil
+        expect(merge_label.id).to eq(new_label.id)
+      end
+
+      it 'updates board lists' do
+        service.execute(project_label_1_1)
+        list = issue_board_2_1.lists.find_by(label: new_label)
+
+        expect(list).not_to be_nil
+      end
+
+      # In case someone adds a new relation to Label.rb and forgets to relink it
+      # and the database doesn't have foreign key constraints
+      it 'relinks all relations' do
+        service.execute(project_label_1_1)
+
+        Label.reflect_on_all_associations.each do |association|
+          expect(project_label_1_1.send(association.name).any?).to be_falsey
+        end
+      end
+
+      context 'with invalid group label' do
+        before do
+          allow(service).to receive(:clone_label_to_group_label).and_wrap_original do |m, *args|
+            label = m.call(*args)
+            allow(label).to receive(:valid?).and_return(false)
+
+            label
+          end
+        end
+
+        it 'raises an exception' do
+          expect { service.execute(project_label_1_1) }.to raise_error(ActiveRecord::RecordInvalid)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 5f6a7716beb3319b110ec06b2335d31734bdd144..d55a7657c0ea2acb160ae58aa1c448ee427ce660 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do
       it { expect(@merge_request).to be_valid }
       it { expect(@merge_request).to be_closed }
 
-      it 'should execute hooks with close action' do
+      it 'executes hooks with close action' do
         expect(service).to have_received(:execute_hooks).
                                with(@merge_request, 'close')
       end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 00d0e20f47caa53eb96344a3ee20b48b459743d8..2cc21acab7bc9ae5b72b1a9000dec0f3979f3d72 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -89,7 +89,7 @@ describe MergeRequests::RefreshService, services: true do
         # Merge master -> feature branch
         author = { email: 'test@gitlab.com', time: Time.now, name: "Me" }
         commit_options = { message: 'Test message', committer: author, author: author }
-        @project.repository.merge(@user, @merge_request, commit_options)
+        @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, commit_options)
         commit = @project.repository.commit('feature')
         service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
         reload_mrs
@@ -106,23 +106,46 @@ describe MergeRequests::RefreshService, services: true do
 
     context 'push to fork repo source branch' do
       let(:refresh_service) { service.new(@fork_project, @user) }
-      before do
-        allow(refresh_service).to receive(:execute_hooks)
-        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
-        reload_mrs
-      end
 
-      it 'executes hooks with update action' do
-        expect(refresh_service).to have_received(:execute_hooks).
-          with(@fork_merge_request, 'update', @oldrev)
+      context 'open fork merge request' do
+        before do
+          allow(refresh_service).to receive(:execute_hooks)
+          refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+          reload_mrs
+        end
+
+        it 'executes hooks with update action' do
+          expect(refresh_service).to have_received(:execute_hooks).
+            with(@fork_merge_request, 'update', @oldrev)
+        end
+
+        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).to be_open }
+        it { expect(@build_failed_todo).to be_pending }
+        it { expect(@fork_build_failed_todo).to be_pending }
       end
 
-      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).to be_open }
-      it { expect(@build_failed_todo).to be_pending }
-      it { expect(@fork_build_failed_todo).to be_pending }
+      context 'closed fork merge request' do
+        before do
+          @fork_merge_request.close!
+          allow(refresh_service).to receive(:execute_hooks)
+          refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+          reload_mrs
+        end
+
+        it 'do not execute hooks with update action' do
+          expect(refresh_service).not_to have_received(:execute_hooks)
+        end
+
+        it { expect(@merge_request.notes).to be_empty }
+        it { expect(@merge_request).to be_open }
+        it { expect(@fork_merge_request.notes).to be_empty }
+        it { expect(@fork_merge_request).to be_closed }
+        it { expect(@build_failed_todo).to be_pending }
+        it { expect(@fork_build_failed_todo).to be_pending }
+      end
     end
 
     context 'push to fork repo target branch' do
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index 388abb6a0dfc3045f0b82d7bdc0450a9f89fb54a..a0e51681725b8530ef4ca211d2df318abc036300 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -66,7 +66,13 @@ describe MergeRequests::ResolveService do
 
       context 'when the source project is a fork and does not contain the HEAD of the target branch' do
         let!(:target_head) do
-          project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
+          project.repository.commit_file(
+            user,
+            'new-file-in-target',
+            '',
+            message: 'Add new file in target',
+            branch_name: 'conflict-start',
+            update: false)
         end
 
         before do
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index b0cc3ce5f5a5c74b9845e7039070120f0ea5244c..9c92a5080c68584f33500088511e85acc92f05f5 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do
 
     context "valid params" do
       it 'returns a valid note' do
-        note = Notes::CreateService.new(project, user, opts).execute
+        note = described_class.new(project, user, opts).execute
 
         expect(note).to be_valid
       end
 
       it 'returns a persisted note' do
-        note = Notes::CreateService.new(project, user, opts).execute
+        note = described_class.new(project, user, opts).execute
 
         expect(note).to be_persisted
       end
 
       it 'note has valid content' do
-        note = Notes::CreateService.new(project, user, opts).execute
+        note = described_class.new(project, user, opts).execute
 
         expect(note.note).to eq(opts[:note])
       end
 
+      it 'note belongs to the correct project' do
+        note = described_class.new(project, user, opts).execute
+
+        expect(note.project).to eq(project)
+      end
+
       it 'TodoService#new_note is called' do
-        note = build(:note)
-        allow(project).to receive_message_chain(:notes, :new).with(opts) { note }
+        note = build(:note, project: project)
+        allow(Note).to receive(:new).with(opts) { note }
 
         expect_any_instance_of(TodoService).to receive(:new_note).with(note, user)
 
-        Notes::CreateService.new(project, user, opts).execute
+        described_class.new(project, user, opts).execute
       end
 
       it 'enqueues NewNoteWorker' do
-        note = build(:note, id: 999)
-        allow(project).to receive_message_chain(:notes, :new).with(opts) { note }
+        note = build(:note, id: 999, project: project)
+        allow(Note).to receive(:new).with(opts) { note }
 
         expect(NewNoteWorker).to receive(:perform_async).with(note.id)
 
-        Notes::CreateService.new(project, user, opts).execute
+        described_class.new(project, user, opts).execute
       end
     end
 
@@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do
         end
       end
     end
+
+    describe 'personal snippet note' do
+      subject { described_class.new(nil, user, params).execute }
+
+      let(:snippet) { create(:personal_snippet) }
+      let(:params) do
+        { note: 'comment', noteable_type: 'Snippet', noteable_id: snippet.id }
+      end
+
+      it 'returns a valid note' do
+        expect(subject).to be_valid
+      end
+
+      it 'returns a persisted note' do
+        expect(subject).to be_persisted
+      end
+
+      it 'note has valid content' do
+        expect(subject.note).to eq(params[:note])
+      end
+    end
   end
 
   describe "award emoji" do
@@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do
         noteable_type: 'Issue',
         noteable_id: issue.id
       }
-      note = Notes::CreateService.new(project, user, opts).execute
+      note = described_class.new(project, user, opts).execute
 
       expect(note).to be_valid
       expect(note.name).to eq('smile')
@@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do
         noteable_type: 'Issue',
         noteable_id: issue.id
       }
-      note = Notes::CreateService.new(project, user, opts).execute
+      note = described_class.new(project, user, opts).execute
 
       expect(note).to be_valid
       expect(note.note).to eq(opts[:note])
@@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do
 
       expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user)
 
-      Notes::CreateService.new(project, user, opts).execute
+      described_class.new(project, user, opts).execute
     end
   end
 end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f3e80ac22a07468c0b78a4044cf3f10fb7518402..7cf2cd9968fe4e28bf7e2083bcc6daa7a79ccc18 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -33,6 +33,49 @@ describe NotificationService, services: true do
     end
   end
 
+  # Next shared examples are intended to test notifications of "participants"
+  #
+  # they take the following parameters:
+  # * issuable
+  # * notification trigger
+  # * participant
+  #
+  shared_examples 'participating by note notification' do
+    it 'emails the participant' do
+      create(:note_on_issue, noteable: issuable, project_id: project.id, note: 'anything', author: participant)
+
+      notification_trigger
+
+      should_email(participant)
+    end
+  end
+
+  shared_examples 'participating by assignee notification' do
+    it 'emails the participant' do
+      issuable.update_attribute(:assignee, participant)
+
+      notification_trigger
+
+      should_email(participant)
+    end
+  end
+
+  shared_examples 'participating by author notification' do
+    it 'emails the participant' do
+      issuable.author = participant
+
+      notification_trigger
+
+      should_email(participant)
+    end
+  end
+
+  shared_examples_for 'participating notifications' do
+    it_should_behave_like 'participating by note notification'
+    it_should_behave_like 'participating by author notification'
+    it_should_behave_like 'participating by assignee notification'
+  end
+
   describe 'Keys' do
     describe '#new_key' do
       let!(:key) { create(:personal_key) }
@@ -269,6 +312,55 @@ describe NotificationService, services: true do
       end
     end
 
+    context 'personal snippet note' do
+      let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) }
+      let(:note)    { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) }
+
+      before do
+        @u_watcher               = create_global_setting_for(create(:user), :watch)
+        @u_participant           = create_global_setting_for(create(:user), :participating)
+        @u_disabled              = create_global_setting_for(create(:user), :disabled)
+        @u_mentioned             = create_global_setting_for(create(:user, username: 'mentioned'), :mention)
+        @u_mentioned_level       = create_global_setting_for(create(:user, username: 'participator'), :mention)
+        @u_note_author           = create(:user, username: 'note_author')
+        @u_snippet_author        = create(:user, username: 'snippet_author')
+        @u_not_mentioned         = create_global_setting_for(create(:user, username: 'regular'), :participating)
+
+        reset_delivered_emails!
+      end
+
+      let!(:notes) do
+        [
+          create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_watcher),
+          create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant),
+          create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned),
+          create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled),
+          create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author),
+        ]
+      end
+
+      describe '#new_note' do
+        it 'notifies the participants' do
+          notification.new_note(note)
+
+          # it emails participants
+          should_email(@u_watcher)
+          should_email(@u_participant)
+          should_email(@u_watcher)
+          should_email(@u_snippet_author)
+
+          # it emails mentioned users
+          should_email(@u_mentioned)
+
+          # it does not email participants with mention notification level
+          should_not_email(@u_mentioned_level)
+
+          # it does not email note author
+          should_not_email(@u_note_author)
+        end
+      end
+    end
+
     context 'commit note' do
       let(:project) { create(:project, :public) }
       let(:note) { create(:note_on_commit, project: project) }
@@ -351,6 +443,8 @@ describe NotificationService, services: true do
 
     before do
       build_team(issue.project)
+      build_group(issue.project)
+
       add_users_with_subscription(issue.project, issue)
       reset_delivered_emails!
       update_custom_notification(:new_issue, @u_guest_custom, project)
@@ -367,6 +461,8 @@ describe NotificationService, services: true do
         should_email(@u_guest_custom)
         should_email(@u_custom_global)
         should_email(@u_participant_mentioned)
+        should_email(@g_global_watcher)
+        should_email(@g_watcher)
         should_not_email(@u_mentioned)
         should_not_email(@u_participating)
         should_not_email(@u_disabled)
@@ -539,32 +635,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            issue.update_attribute(:assignee, @u_lazy_participant)
-            notification.reassigned_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.reassigned_issue(issue, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            issue.author = @u_lazy_participant
-            notification.reassigned_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { issue }
+        let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled) }
       end
     end
 
@@ -671,32 +745,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            issue.update_attribute(:assignee, @u_lazy_participant)
-            notification.close_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.close_issue(issue, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            issue.author = @u_lazy_participant
-            notification.close_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { issue }
+        let(:notification_trigger) { notification.close_issue(issue, @u_disabled) }
       end
     end
 
@@ -723,32 +775,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            issue.update_attribute(:assignee, @u_lazy_participant)
-            notification.reopen_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.reopen_issue(issue, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            issue.author = @u_lazy_participant
-            notification.reopen_issue(issue, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { issue }
+        let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) }
       end
     end
   end
@@ -809,31 +839,28 @@ describe NotificationService, services: true do
       end
 
       context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.new_merge_request(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
+        it_should_behave_like 'participating by assignee notification' do
+          let(:participant) { create(:user, username: 'user-participant')}
+          let(:issuable) { merge_request }
+          let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
         end
 
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.new_merge_request(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
+        it_should_behave_like 'participating by note notification' do
+          let(:participant) { create(:user, username: 'user-participant')}
+          let(:issuable) { merge_request }
+          let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
         end
 
         context 'by author' do
+          let(:participant) { create(:user, username: 'user-participant')}
+
           before do
-            merge_request.author = @u_lazy_participant
+            merge_request.author = participant
             merge_request.save
             notification.new_merge_request(merge_request, @u_disabled)
           end
 
-          it { should_not_email(@u_lazy_participant) }
+          it { should_not_email(participant) }
         end
       end
     end
@@ -868,33 +895,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.reassigned_merge_request(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.reassigned_merge_request(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            merge_request.author = @u_lazy_participant
-            merge_request.save
-            notification.reassigned_merge_request(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.reassigned_merge_request(merge_request, @u_disabled) }
       end
     end
 
@@ -965,33 +969,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.close_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.close_mr(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            merge_request.author = @u_lazy_participant
-            merge_request.save
-            notification.close_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) }
       end
     end
 
@@ -1032,33 +1013,10 @@ describe NotificationService, services: true do
         should_not_email(@u_watcher)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.merge_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.merge_mr(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            merge_request.author = @u_lazy_participant
-            merge_request.save
-            notification.merge_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) }
       end
     end
 
@@ -1085,33 +1043,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.reopen_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.reopen_mr(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            merge_request.author = @u_lazy_participant
-            merge_request.save
-            notification.reopen_mr(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) }
       end
     end
 
@@ -1131,33 +1066,10 @@ describe NotificationService, services: true do
         should_not_email(@u_lazy_participant)
       end
 
-      context 'participating' do
-        context 'by assignee' do
-          before do
-            merge_request.update_attribute(:assignee, @u_lazy_participant)
-            notification.resolve_all_discussions(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by note' do
-          let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
-
-          before { notification.resolve_all_discussions(merge_request, @u_disabled) }
-
-          it { should_email(@u_lazy_participant) }
-        end
-
-        context 'by author' do
-          before do
-            merge_request.author = @u_lazy_participant
-            merge_request.save
-            notification.resolve_all_discussions(merge_request, @u_disabled)
-          end
-
-          it { should_email(@u_lazy_participant) }
-        end
+      it_behaves_like 'participating notifications' do
+        let(:participant) { create(:user, username: 'user-participant') }
+        let(:issuable) { merge_request }
+        let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
       end
     end
   end
@@ -1310,6 +1222,22 @@ describe NotificationService, services: true do
     project.add_master(@u_custom_global)
   end
 
+  # Users in the project's group but not part of project's team
+  # with different notification settings
+  def build_group(project)
+    group = create(:group, :public)
+    project.group = group
+
+    # Group member: global=disabled, group=watch
+    @g_watcher = create_user_with_notification(:watch, 'group_watcher', project.group)
+    @g_watcher.notification_settings_for(nil).disabled!
+
+    # Group member: global=watch, group=global
+    @g_global_watcher = create_global_setting_for(create(:user), :watch)
+    group.add_users([@g_watcher, @g_global_watcher], :master)
+    group
+  end
+
   def create_global_setting_for(user, level)
     setting = user.global_notification_setting
     setting.level = level
@@ -1318,9 +1246,9 @@ describe NotificationService, services: true do
     user
   end
 
-  def create_user_with_notification(level, username)
+  def create_user_with_notification(level, username, resource = project)
     user = create(:user, username: username)
-    setting = user.notification_settings_for(project)
+    setting = user.notification_settings_for(resource)
     setting.level = level
     setting.save
 
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index bd89c4a7c116bc5db8e49e730cc5a881884bb67b..bed1031e40ad9d7c5e5d3bae27abdeadb40abb9f 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -41,6 +41,25 @@ describe 'Search::GlobalService', services: true do
         results = context.execute
         expect(results.objects('projects')).to match_array [found_project]
       end
+
+      context 'nested group' do
+        let!(:nested_group) { create(:group, :nested) }
+        let!(:project) { create(:project, namespace: nested_group) }
+
+        before { project.add_master(user) }
+
+        it 'returns result from nested group' do
+          context = Search::GlobalService.new(user, search: project.path)
+          results = context.execute
+          expect(results.objects('projects')).to match_array [project]
+        end
+
+        it 'returns result from descendants when search inside group' do
+          context = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent)
+          results = context.execute
+          expect(results.objects('projects')).to match_array [project]
+        end
+      end
     end
   end
 end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index fef211ded50901b0cb266eec118b568e4b09d754..db9f1231682a7c328aa5c0fc1fe7058c7f28cc47 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -12,6 +12,7 @@ describe SystemHooksService, services: true do
     it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
     it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
     it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+    it { expect(event_data(project, :update)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
     it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
     it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
     it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
@@ -68,6 +69,7 @@ describe SystemHooksService, services: true do
     it { expect(event_name(project, :destroy)).to eq "project_destroy" }
     it { expect(event_name(project, :rename)).to eq "project_rename" }
     it { expect(event_name(project, :transfer)).to eq "project_transfer" }
+    it { expect(event_name(project, :update)).to eq "project_update" }
     it { expect(event_name(project_member, :create)).to eq "user_add_to_team" }
     it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" }
     it { expect(event_name(key, :create)).to eq 'key_create' }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 9f5a0ac4ec6b6ded3b832ae76678a2e88d2ea77a..bd7269045e1071bb8a99ae05f6ca1869acc4ce61 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -245,6 +245,8 @@ describe SystemNoteService, services: true do
   end
 
   describe '.change_title' do
+    let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum') }
+
     subject { described_class.change_title(noteable, project, author, 'Old title') }
 
     context 'when noteable responds to `title`' do
@@ -252,7 +254,7 @@ describe SystemNoteService, services: true do
 
       it 'sets the note text' do
         expect(subject.note).
-          to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**"
+          to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**"
       end
     end
   end
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b4efe7de4318cc64147ed9ba6423076861468889
--- /dev/null
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe UserProjectAccessChangedService do
+  describe '#execute' do
+    it 'schedules the user IDs' do
+      expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait).
+        with([[1], [2]])
+
+      described_class.new([1, 2]).execute
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 6ee3307512ddff66db439c892e81069a6401cbaf..ab38dac65c5a5dc8423ed69b3063bb1d9fe81546 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -2,13 +2,17 @@ require './spec/simplecov_env'
 SimpleCovEnv.start!
 
 ENV["RAILS_ENV"] ||= 'test'
+ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
 
 require File.expand_path("../../config/environment", __FILE__)
 require 'rspec/rails'
 require 'shoulda/matchers'
-require 'sidekiq/testing/inline'
 require 'rspec/retry'
 
+if ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']
+  require 'rspec_profiling/rspec'
+end
+
 if ENV['CI'] && !ENV['NO_KNAPSACK']
   require 'knapsack'
   Knapsack::Adapters::RSpecAdapter.bind
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 75c95d70951bed25b429df63fed06a6005712b60..6ed55289ed9ee97ac2c722d509b31f5b0d49079a 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -35,7 +35,13 @@ module CycleAnalyticsHelpers
       project.repository.add_branch(user, source_branch, 'master')
     end
 
-    sha = project.repository.commit_file(user, random_git_name, "content", "commit message", source_branch, false)
+    sha = project.repository.commit_file(
+      user,
+      random_git_name,
+      'content',
+      message: 'commit message',
+      branch_name: source_branch,
+      update: false)
     project.repository.commit(sha)
 
     opts = {
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 35b40d731913c87ca3ba3f7971795630a0dd275e..19b32c84d818650281a978613502aa6cfa40cd53 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -54,7 +54,7 @@ module CycleAnalyticsHelpers
             end
 
             context "when the data belongs to another project" do
-              let(:other_project) { create(:project) }
+              let(:other_project) { create(:project, :repository) }
 
               it "returns nil" do
                 # Use a stub to "trick" the data/condition functions
@@ -63,22 +63,20 @@ module CycleAnalyticsHelpers
                 # test case.
                 allow(self).to receive(:project) { other_project }
 
-                5.times do
-                  data = data_fn[self]
-                  start_time = Time.now
-                  end_time = rand(1..10).days.from_now
-
-                  start_time_conditions.each do |condition_name, condition_fn|
-                    Timecop.freeze(start_time) { condition_fn[self, data] }
-                  end
+                data = data_fn[self]
+                start_time = Time.now
+                end_time = rand(1..10).days.from_now
 
-                  end_time_conditions.each do |condition_name, condition_fn|
-                    Timecop.freeze(end_time) { condition_fn[self, data] }
-                  end
+                start_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(start_time) { condition_fn[self, data] }
+                end
 
-                  Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+                end_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(end_time) { condition_fn[self, data] }
                 end
 
+                Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
                 # Turn off the stub before checking assertions
                 allow(self).to receive(:project).and_call_original
 
@@ -114,17 +112,15 @@ module CycleAnalyticsHelpers
         context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do
           context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
             it "returns nil" do
-              5.times do
-                data = data_fn[self]
-                end_time = rand(1..10).days.from_now
-
-                end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
-                  Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
-                end
+              data = data_fn[self]
+              end_time = rand(1..10).days.from_now
 
-                Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+              end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
+                Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
               end
 
+              Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
               expect(subject[phase].median).to be_nil
             end
           end
@@ -133,17 +129,15 @@ module CycleAnalyticsHelpers
         context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
           context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do
             it "returns nil" do
-              5.times do
-                data = data_fn[self]
-                start_time = Time.now
-
-                start_time_conditions.each do |condition_name, condition_fn|
-                  Timecop.freeze(start_time) { condition_fn[self, data] }
-                end
+              data = data_fn[self]
+              start_time = Time.now
 
-                post_fn[self, data] if post_fn
+              start_time_conditions.each do |condition_name, condition_fn|
+                Timecop.freeze(start_time) { condition_fn[self, data] }
               end
 
+              post_fn[self, data] if post_fn
+
               expect(subject[phase].median).to be_nil
             end
           end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index f57c82809a6b878a65e528af75c7eb823d2ebd77..87936bb485941ee0295659aa6f4234b0fe5c8571 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -12,7 +12,7 @@ shared_context 'mentionable context' do
   let!(:mentioned_mr)     { create(:merge_request, source_project: project) }
   let(:mentioned_commit) { project.commit("HEAD~1") }
 
-  let(:ext_proj)   { create(:project, :public) }
+  let(:ext_proj)   { create(:project, :public, :repository) }
   let(:ext_issue)  { create(:issue, project: ext_proj) }
   let(:ext_mr)     { create(:merge_request, :simple, source_project: ext_proj) }
   let(:ext_commit) { ext_proj.commit("HEAD~2") }
diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..20d5849bcab2ff7d5adcecfaac2e2b1fb250efe3
--- /dev/null
+++ b/spec/support/mobile_helpers.rb
@@ -0,0 +1,13 @@
+module MobileHelpers
+  def resize_screen_sm
+    resize_window(900, 768)
+  end
+
+  def restore_window_size
+    resize_window(1366, 768)
+  end
+
+  def resize_window(width, height)
+    page.driver.resize_window width, height
+  end
+end
diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb
index 49867aa5cc4dbdd0512d9b1d77c10928ee6d6d7e..a3724b801b3f6e0495f413577d2a33ae2cc29012 100644
--- a/spec/support/notify_shared_examples.rb
+++ b/spec/support/notify_shared_examples.rb
@@ -179,9 +179,24 @@ shared_examples 'it should show Gmail Actions View Commit link' do
 end
 
 shared_examples 'an unsubscribeable thread' do
+  it_behaves_like 'an unsubscribeable thread with incoming address without %{key}'
+
+  it 'has a List-Unsubscribe header in the correct format' do
+    is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
+    is_expected.to have_header 'List-Unsubscribe', /mailto/
+    is_expected.to have_header 'List-Unsubscribe', /^<.+,.+>$/
+  end
+
+  it { is_expected.to have_body_text /unsubscribe/ }
+end
+
+shared_examples 'an unsubscribeable thread with incoming address without %{key}' do
+  include_context 'reply-by-email is enabled with incoming address without %{key}'
+
   it 'has a List-Unsubscribe header in the correct format' do
     is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
-    is_expected.to have_header 'List-Unsubscribe', /^<.+>$/
+    is_expected.not_to have_header 'List-Unsubscribe', /mailto/
+    is_expected.to have_header 'List-Unsubscribe', /^<[^,]+>$/
   end
 
   it { is_expected.to have_body_text /unsubscribe/ }
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
new file mode 100644
index 0000000000000000000000000000000000000000..575d345115070f2145b2a200d472d137c3060d70
--- /dev/null
+++ b/spec/support/sidekiq.rb
@@ -0,0 +1,5 @@
+require 'sidekiq/testing/inline'
+
+Sidekiq::Testing.server_middleware do |chain|
+  chain.add Gitlab::SidekiqStatus::ServerMiddleware
+end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 74d9b8c6313094acec5413812521e7db0d6d65e8..704922b6cf4c92b0df48bba134ac089b26f5f0fc 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -26,7 +26,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
   describe "#execute" do
     let(:user)    { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
     let(:username) { 'slack_username' }
     let(:channel)  { 'slack_channel' }
 
@@ -196,7 +196,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
   describe "Note events" do
     let(:user) { create(:user) }
-    let(:project) { create(:project, creator_id: user.id) }
+    let(:project) { create(:project, :repository, creator: user) }
 
     before do
       allow(chat_service).to receive_messages(
@@ -269,7 +269,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
 
   describe 'Pipeline events' do
     let(:user) { create(:user) }
-    let(:project) { create(:project) }
+    let(:project) { create(:project, :repository) }
 
     let(:pipeline) do
       create(:ci_pipeline,
diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb
index ad1c783df4d917fc9c2460d02cd7f71f0cc69654..4056ff06b8475940b79eee4dde02bb029d05330b 100644
--- a/spec/support/taskable_shared_examples.rb
+++ b/spec/support/taskable_shared_examples.rb
@@ -33,6 +33,30 @@ shared_examples 'a Taskable' do
     end
   end
 
+  describe 'with nested tasks' do
+    before do
+      subject.description = <<-EOT.strip_heredoc
+        - [ ] Task a
+          - [x] Task a.1
+          - [ ] Task a.2
+        - [ ] Task b
+
+        1. [ ] Task 1
+          1. [ ] Task 1.1
+          1. [ ] Task 1.2
+        1. [x] Task 2
+          1. [x] Task 2.1
+      EOT
+    end
+
+    it 'returns the correct task status' do
+      expect(subject.task_status).to match('3 of')
+      expect(subject.task_status).to match('9 tasks completed')
+      expect(subject.task_status_short).to match('3/')
+      expect(subject.task_status_short).to match('9 tasks')
+    end
+  end
+
   describe 'with an incomplete task' do
     before do
       subject.description = <<-EOT.strip_heredoc
@@ -48,6 +72,25 @@ shared_examples 'a Taskable' do
     end
   end
 
+  describe 'with tasks that are not formatted correctly' do
+    before do
+      subject.description = <<-EOT.strip_heredoc
+        [ ] task 1
+        [ ] task 2
+
+        - [ ]task 1
+        -[ ] task 2
+      EOT
+    end
+
+    it 'returns the correct task status' do
+      expect(subject.task_status).to match('0 of')
+      expect(subject.task_status).to match('0 tasks completed')
+      expect(subject.task_status_short).to match('0/')
+      expect(subject.task_status_short).to match('0 task')
+    end
+  end
+
   describe 'with a complete task' do
     before do
       subject.description = <<-EOT.strip_heredoc
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 90f1a9c8798090f2ceed0254a536f20259c4c81c..b87232a350b33f137c16c94586f223e1a972c65e 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -36,7 +36,8 @@ module TestEnv
     'conflict-non-utf8'                  => 'd0a293c',
     'conflict-too-large'                 => '39fa04f',
     'deleted-image-test'                 => '6c17798',
-    'wip'                                => 'b9238ee'
+    'wip'                                => 'b9238ee',
+    'csv'                                => '3dd0896'
   }
 
   # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
index 80fc8c48fedcad1d0454e11cbb291da7913118d3..8d1cff7a261706b1a3c83b306ed18a197a071e01 100644
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do
       Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
     end
 
-    it 'should run the task without errors' do
+    it 'runs the task without errors' do
       expect { run_rake_task }.not_to raise_error
     end
   end
diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb
index 2dac5ee23c8de6728ec26e5d046bffccb43110cf..3390ae247ff2305d939b5569e21ff8aa00dd0ca9 100644
--- a/spec/views/ci/lints/show.html.haml_spec.rb
+++ b/spec/views/ci/lints/show.html.haml_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe 'ci/lints/show' do
-  include Devise::TestHelpers
+  include Devise::Test::ControllerHelpers
 
   describe 'XSS protection' do
     let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) }
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 745d0c745bd76e6ec41ee3838a0f715c965facc0..b6f6e7b7a2b00f33863499949ba7d4d9854187a7 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -15,8 +15,38 @@ describe 'projects/builds/show', :view do
     allow(view).to receive(:can?).and_return(true)
   end
 
-  describe 'environment info in build view' do
-    context 'build with latest deployment' do
+  describe 'job information in header' do
+    let(:build) do
+      create(:ci_build, :success, environment: 'staging')
+    end
+
+    before do
+      render
+    end
+
+    it 'shows status name' do
+      expect(rendered).to have_css('.ci-status.ci-success', text: 'passed')
+    end
+
+    it 'does not render a link to the job' do
+      expect(rendered).not_to have_link('passed')
+    end
+
+    it 'shows job id' do
+      expect(rendered).to have_css('.js-build-id', text: build.id)
+    end
+
+    it 'shows a link to the pipeline' do
+      expect(rendered).to have_link(build.pipeline.id)
+    end
+
+    it 'shows a link to the commit' do
+      expect(rendered).to have_link(build.pipeline.short_sha)
+    end
+  end
+
+  describe 'environment info in job view' do
+    context 'job with latest deployment' do
       let(:build) do
         create(:ci_build, :success, environment: 'staging')
       end
@@ -27,7 +57,7 @@ describe 'projects/builds/show', :view do
       end
 
       it 'shows deployment message' do
-        expected_text = 'This build is the most recent deployment'
+        expected_text = 'This job is the most recent deployment'
         render
 
         expect(rendered).to have_css(
@@ -35,7 +65,7 @@ describe 'projects/builds/show', :view do
       end
     end
 
-    context 'build with outdated deployment' do
+    context 'job with outdated deployment' do
       let(:build) do
         create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
       end
@@ -57,7 +87,7 @@ describe 'projects/builds/show', :view do
       end
 
       it 'shows deployment message' do
-        expected_text = 'This build is an out-of-date deployment ' \
+        expected_text = 'This job is an out-of-date deployment ' \
           "to staging.\nView the most recent deployment ##{second_deployment.iid}."
         render
 
@@ -65,7 +95,7 @@ describe 'projects/builds/show', :view do
       end
     end
 
-    context 'build failed to deploy' do
+    context 'job failed to deploy' do
       let(:build) do
         create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
       end
@@ -75,7 +105,7 @@ describe 'projects/builds/show', :view do
       end
 
       it 'shows deployment message' do
-        expected_text = 'The deployment of this build to staging did not succeed.'
+        expected_text = 'The deployment of this job to staging did not succeed.'
         render
 
         expect(rendered).to have_css(
@@ -83,7 +113,7 @@ describe 'projects/builds/show', :view do
       end
     end
 
-    context 'build will deploy' do
+    context 'job will deploy' do
       let(:build) do
         create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
       end
@@ -94,7 +124,7 @@ describe 'projects/builds/show', :view do
         end
 
         it 'shows deployment message' do
-          expected_text = 'This build is creating a deployment to staging'
+          expected_text = 'This job is creating a deployment to staging'
           render
 
           expect(rendered).to have_css(
@@ -107,7 +137,7 @@ describe 'projects/builds/show', :view do
           end
 
           it 'shows that deployment will be overwritten' do
-            expected_text = 'This build is creating a deployment to staging'
+            expected_text = 'This job is creating a deployment to staging'
             render
 
             expect(rendered).to have_css(
@@ -120,7 +150,7 @@ describe 'projects/builds/show', :view do
 
       context 'when environment does not exist' do
         it 'shows deployment message' do
-          expected_text = 'This build is creating a deployment to staging'
+          expected_text = 'This job is creating a deployment to staging'
           render
 
           expect(rendered).to have_css(
@@ -131,7 +161,7 @@ describe 'projects/builds/show', :view do
       end
     end
 
-    context 'build that failed to deploy and environment has not been created' do
+    context 'job that failed to deploy and environment has not been created' do
       let(:build) do
         create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
       end
@@ -141,7 +171,7 @@ describe 'projects/builds/show', :view do
       end
 
       it 'shows deployment message' do
-        expected_text = 'The deployment of this build to staging did not succeed'
+        expected_text = 'The deployment of this job to staging did not succeed'
         render
 
         expect(rendered).to have_css(
@@ -149,7 +179,7 @@ describe 'projects/builds/show', :view do
       end
     end
 
-    context 'build that will deploy and environment has not been created' do
+    context 'job that will deploy and environment has not been created' do
       let(:build) do
         create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
       end
@@ -159,7 +189,7 @@ describe 'projects/builds/show', :view do
       end
 
       it 'shows deployment message' do
-        expected_text = 'This build is creating a deployment to staging'
+        expected_text = 'This job is creating a deployment to staging'
         render
 
         expect(rendered).to have_css(
@@ -170,7 +200,7 @@ describe 'projects/builds/show', :view do
     end
   end
 
-  context 'when build is running' do
+  context 'when job is running' do
     before do
       build.run!
       render
@@ -181,7 +211,7 @@ describe 'projects/builds/show', :view do
     end
   end
 
-  context 'when build is not running' do
+  context 'when job is not running' do
     before do
       build.success!
       render
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index b6591f272f6159d1c5cd1d72115ad36824823db0..97c4bfcd248729b8dc18213c1e36f735642dc638 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -3,6 +3,18 @@ require 'spec_helper'
 describe AuthorizedProjectsWorker do
   let(:worker) { described_class.new }
 
+  describe '.bulk_perform_and_wait' do
+    it 'schedules the ids and waits for the jobs to complete' do
+      project = create(:project)
+
+      project.owner.project_authorizations.delete_all
+
+      described_class.bulk_perform_and_wait([[project.owner.id]])
+
+      expect(project.owner.project_authorizations.count).to eq(1)
+    end
+  end
+
   describe '#perform' do
     it "refreshes user's authorized projects" do
       user = create(:user)
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index e471a68a49afeb0bfe17e1e7f2b032dfb967f6f7..5ef8cf1105b16101f9cb9fd9e7d862ea6d4da0cf 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -107,7 +107,8 @@ describe GitGarbageCollectWorker do
       tree: old_commit.tree,
       parents: [old_commit],
     )
-    project.repository.update_ref!(
+    GitOperationService.new(nil, project.repository).send(
+      :update_ref,
       "refs/heads/#{SecureRandom.hex(6)}",
       new_commit_sha,
       Gitlab::Git::BLANK_SHA
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 984acdade3603c1964a5ed7b28c63267047d3ab7..5919b99a6ed5652f67e37026ddcefbd7b10f1020 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -74,7 +74,7 @@ describe PostReceive do
 
   context "webhook" do
     it "fetches the correct project" do
-      expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project)
+      expect(Project).to receive(:find_by_full_path).with(project.path_with_namespace).and_return(project)
       PostReceive.new.perform(pwd(project), key_id, base64_changes)
     end
 
@@ -89,7 +89,7 @@ describe PostReceive do
     end
 
     it "asks the project to trigger all hooks" do
-      allow(Project).to receive(:find_with_namespace).and_return(project)
+      allow(Project).to receive(:find_by_full_path).and_return(project)
       expect(project).to receive(:execute_hooks).twice
       expect(project).to receive(:execute_services).twice
 
@@ -97,7 +97,7 @@ describe PostReceive do
     end
 
     it "enqueues a UpdateMergeRequestsWorker job" do
-      allow(Project).to receive(:find_with_namespace).and_return(project)
+      allow(Project).to receive(:find_by_full_path).and_return(project)
       expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
 
       PostReceive.new.perform(pwd(project), key_id, base64_changes)
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 1b910d9b91e90463d3376adcafc969cabb8c0f25..1f4c39eb64ac7b7d6748e8f2409104132fae8588 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -8,14 +8,14 @@ describe ProjectDestroyWorker do
 
   describe "#perform" do
     it "deletes the project" do
-      subject.perform(project.id, project.owner, {})
+      subject.perform(project.id, project.owner.id, {})
 
       expect(Project.all).not_to include(project)
       expect(Dir.exist?(path)).to be_falsey
     end
 
     it "deletes the project but skips repo deletion" do
-      subject.perform(project.id, project.owner, { "skip_repo" => true })
+      subject.perform(project.id, project.owner.id, { "skip_repo" => true })
 
       expect(Project.all).not_to include(project)
       expect(Dir.exist?(path)).to be_truthy
diff --git a/vendor/assets/javascripts/jquery.ba-resize.js b/vendor/assets/javascripts/jquery.ba-resize.js
deleted file mode 100644
index 1f41d3791539f48bed836a5e1e8ffc158cf35d0a..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/jquery.ba-resize.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/*!
- * jQuery resize event - v1.1 - 3/14/2010
- * http://benalman.com/projects/jquery-resize-plugin/
- * 
- * Copyright (c) 2010 "Cowboy" Ben Alman
- * Dual licensed under the MIT and GPL licenses.
- * http://benalman.com/about/license/
- */
-
-// Script: jQuery resize event
-//
-// *Version: 1.1, Last updated: 3/14/2010*
-// 
-// Project Home - http://benalman.com/projects/jquery-resize-plugin/
-// GitHub       - http://github.com/cowboy/jquery-resize/
-// Source       - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
-// (Minified)   - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
-// 
-// About: License
-// 
-// Copyright (c) 2010 "Cowboy" Ben Alman,
-// Dual licensed under the MIT and GPL licenses.
-// http://benalman.com/about/license/
-// 
-// About: Examples
-// 
-// This working example, complete with fully commented code, illustrates a few
-// ways in which this plugin can be used.
-// 
-// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
-// 
-// About: Support and Testing
-// 
-// Information about what version or versions of jQuery this plugin has been
-// tested with, what browsers it has been tested in, and where the unit tests
-// reside (so you can test it yourself).
-// 
-// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
-// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
-// Unit Tests      - http://benalman.com/code/projects/jquery-resize/unit/
-// 
-// About: Release History
-// 
-// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
-//       immediately after bind in some circumstances. Also changed $.fn.data
-//       to $.data to improve performance.
-// 1.0 - (2/10/2010) Initial release
-
-(function($,window,undefined){
-  '$:nomunge'; // Used by YUI compressor.
-  
-  // A jQuery object containing all non-window elements to which the resize
-  // event is bound.
-  var elems = $([]),
-    
-    // Extend $.resize if it already exists, otherwise create it.
-    jq_resize = $.resize = $.extend( $.resize, {} ),
-    
-    timeout_id,
-    
-    // Reused strings.
-    str_setTimeout = 'setTimeout',
-    str_resize = 'resize',
-    str_data = str_resize + '-special-event',
-    str_delay = 'delay',
-    str_throttle = 'throttleWindow';
-  
-  // Property: jQuery.resize.delay
-  // 
-  // The numeric interval (in milliseconds) at which the resize event polling
-  // loop executes. Defaults to 250.
-  
-  jq_resize[ str_delay ] = 250;
-  
-  // Property: jQuery.resize.throttleWindow
-  // 
-  // Throttle the native window object resize event to fire no more than once
-  // every <jQuery.resize.delay> milliseconds. Defaults to true.
-  // 
-  // Because the window object has its own resize event, it doesn't need to be
-  // provided by this plugin, and its execution can be left entirely up to the
-  // browser. However, since certain browsers fire the resize event continuously
-  // while others do not, enabling this will throttle the window resize event,
-  // making event behavior consistent across all elements in all browsers.
-  // 
-  // While setting this property to false will disable window object resize
-  // event throttling, please note that this property must be changed before any
-  // window object resize event callbacks are bound.
-  
-  jq_resize[ str_throttle ] = true;
-  
-  // Event: resize event
-  // 
-  // Fired when an element's width or height changes. Because browsers only
-  // provide this event for the window element, for other elements a polling
-  // loop is initialized, running every <jQuery.resize.delay> milliseconds
-  // to see if elements' dimensions have changed. You may bind with either
-  // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
-  // 
-  // Usage:
-  // 
-  // > jQuery('selector').bind( 'resize', function(e) {
-  // >   // element's width or height has changed!
-  // >   ...
-  // > });
-  // 
-  // Additional Notes:
-  // 
-  // * The polling loop is not created until at least one callback is actually
-  //   bound to the 'resize' event, and this single polling loop is shared
-  //   across all elements.
-  // 
-  // Double firing issue in jQuery 1.3.2:
-  // 
-  // While this plugin works in jQuery 1.3.2, if an element's event callbacks
-  // are manually triggered via .trigger( 'resize' ) or .resize() those
-  // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
-  // events system. This is not an issue when using jQuery 1.4+.
-  // 
-  // > // While this works in jQuery 1.4+
-  // > $(elem).css({ width: new_w, height: new_h }).resize();
-  // > 
-  // > // In jQuery 1.3.2, you need to do this:
-  // > var elem = $(elem);
-  // > elem.css({ width: new_w, height: new_h });
-  // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
-  // > elem.resize();
-      
-  $.event.special[ str_resize ] = {
-    
-    // Called only when the first 'resize' event callback is bound per element.
-    setup: function() {
-      // Since window has its own native 'resize' event, return false so that
-      // jQuery will bind the event using DOM methods. Since only 'window'
-      // objects have a .setTimeout method, this should be a sufficient test.
-      // Unless, of course, we're throttling the 'resize' event for window.
-      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-      
-      var elem = $(this);
-      
-      // Add this element to the list of internal elements to monitor.
-      elems = elems.add( elem );
-      
-      // Initialize data store on the element.
-      $.data( this, str_data, { w: elem.width(), h: elem.height() } );
-      
-      // If this is the first element added, start the polling loop.
-      if ( elems.length === 1 ) {
-        loopy();
-      }
-    },
-    
-    // Called only when the last 'resize' event callback is unbound per element.
-    teardown: function() {
-      // Since window has its own native 'resize' event, return false so that
-      // jQuery will unbind the event using DOM methods. Since only 'window'
-      // objects have a .setTimeout method, this should be a sufficient test.
-      // Unless, of course, we're throttling the 'resize' event for window.
-      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-      
-      var elem = $(this);
-      
-      // Remove this element from the list of internal elements to monitor.
-      elems = elems.not( elem );
-      
-      // Remove any data stored on the element.
-      elem.removeData( str_data );
-      
-      // If this is the last element removed, stop the polling loop.
-      if ( !elems.length ) {
-        clearTimeout( timeout_id );
-      }
-    },
-    
-    // Called every time a 'resize' event callback is bound per element (new in
-    // jQuery 1.4).
-    add: function( handleObj ) {
-      // Since window has its own native 'resize' event, return false so that
-      // jQuery doesn't modify the event object. Unless, of course, we're
-      // throttling the 'resize' event for window.
-      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-      
-      var old_handler;
-      
-      // The new_handler function is executed every time the event is triggered.
-      // This is used to update the internal element data store with the width
-      // and height when the event is triggered manually, to avoid double-firing
-      // of the event callback. See the "Double firing issue in jQuery 1.3.2"
-      // comments above for more information.
-      
-      function new_handler( e, w, h ) {
-        var elem = $(this),
-          data = $.data( this, str_data );
-        
-        // If called from the polling loop, w and h will be passed in as
-        // arguments. If called manually, via .trigger( 'resize' ) or .resize(),
-        // those values will need to be computed.
-        data.w = w !== undefined ? w : elem.width();
-        data.h = h !== undefined ? h : elem.height();
-        
-        old_handler.apply( this, arguments );
-      };
-      
-      // This may seem a little complicated, but it normalizes the special event
-      // .add method between jQuery 1.4/1.4.1 and 1.4.2+
-      if ( $.isFunction( handleObj ) ) {
-        // 1.4, 1.4.1
-        old_handler = handleObj;
-        return new_handler;
-      } else {
-        // 1.4.2+
-        old_handler = handleObj.handler;
-        handleObj.handler = new_handler;
-      }
-    }
-    
-  };
-  
-  function loopy() {
-    
-    // Start the polling loop, asynchronously.
-    timeout_id = window[ str_setTimeout ](function(){
-      
-      // Iterate over all elements to which the 'resize' event is bound.
-      elems.each(function(){
-        var elem = $(this),
-          width = elem.width(),
-          height = elem.height(),
-          data = $.data( this, str_data );
-        
-        // If element size has changed since the last time, update the element
-        // data store and trigger the 'resize' event.
-        if ( width !== data.w || height !== data.h ) {
-          elem.trigger( str_resize, [ data.w = width, data.h = height ] );
-        }
-        
-      });
-      
-      // Loop.
-      loopy();
-      
-    }, jq_resize[ str_delay ] );
-    
-  };
-  
-})(jQuery,this);
diff --git a/vendor/assets/javascripts/xterm/fit.js b/vendor/assets/javascripts/xterm/fit.js
index 7e24fd9b36e18c57ca499480d867732044a0ccf4..55438452cad667ae9b8131e9bf86b6cdb8549a30 100644
--- a/vendor/assets/javascripts/xterm/fit.js
+++ b/vendor/assets/javascripts/xterm/fit.js
@@ -16,12 +16,12 @@
     /*
      * CommonJS environment
      */
-    module.exports = fit(require('../../xterm'));
+    module.exports = fit(require('./xterm'));
   } else if (typeof define == 'function') {
     /*
      * Require.js is available
      */
-    define(['../../xterm'], fit);
+    define(['./xterm'], fit);
   } else {
     /*
      * Plain browser environment
diff --git a/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..36dfc539b3b8ea10255d0eb56466405af03e2168
--- /dev/null
+++ b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml
@@ -0,0 +1,76 @@
+# Explaination on the scripts:
+# https://gitlab.com/gitlab-examples/kubernetes-deploy/blob/master/README.md
+image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
+
+variables:
+  # Application deployment domain
+  KUBE_DOMAIN: domain.example.com
+
+stages:
+  - build
+  - test
+  - review
+  - staging
+  - production
+
+build:
+  stage: build
+  script:
+    - command build
+  only:
+    - branches
+
+production:
+  stage: production
+  variables:
+    CI_ENVIRONMENT_URL: http://production.$KUBE_DOMAIN
+  script:
+    - command deploy
+  environment:
+    name: production
+    url: http://production.$KUBE_DOMAIN
+  when: manual
+  only:
+    - master
+
+staging:
+  stage: staging
+  variables:
+    CI_ENVIRONMENT_URL: http://staging.$KUBE_DOMAIN
+  script:
+    - command deploy
+  environment:
+    name: staging
+    url: http://staging.$KUBE_DOMAIN
+  only:
+    - master
+
+review:
+  stage: review
+  variables:
+    CI_ENVIRONMENT_URL: http://$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
+  script:
+    - command deploy
+  environment:
+    name: review/$CI_BUILD_REF_NAME
+    url: http://$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
+    on_stop: stop_review
+  only:
+    - branches
+  except:
+    - master
+
+stop_review:
+  stage: review
+  variables:
+    GIT_STRATEGY: none
+  script:
+    - command destroy
+  environment:
+    name: review/$CI_BUILD_REF_NAME
+    action: stop
+  when: manual
+  only:
+    - branches
+  except:
+    - master